Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 25 additions & 57 deletions 2015/src/main/scala/aoc2015/Day18.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package aoc2015

import nmcb.*
import nmcb.pos.*

import scala.annotation.tailrec

Expand All @@ -15,76 +16,43 @@ object Day18 extends AoC:
def isOn: Boolean = l == on
def isOff: Boolean = l == off

def next(neighbouring: Vector[Light]): Light =
val n = neighbouring.count(_.isOn)

def nextState(neighboursOn: Int): Light =
if isOn then
if n == 2 || n == 3 then on else off
if neighboursOn == 2 || neighboursOn == 3 then on else off
else
if n == 3 then on else off
if neighboursOn == 3 then on else off

type Row = Vector[Light]

object Row:
def empty: Row =
Vector.empty[Light]

/** zero based top-to-bottom-left-to-right indexed, ie. [y0, y1 .. yn][x0, x1 .. xn] */
type Conf = Vector[Row]

object Conf:
def empty: Conf =
Vector.empty[Row]
/** encapsulates the animation of a light configuration */
case class Lights(grid: Grid[Char], overlay: Set[Pos] = Set.empty):

/** grid encapsulates the animation of a light configuration */
case class Grid(conf: Conf, overlay: Set[(Int,Int)] = Set.empty):
val sizeX: Int = conf.map(_.size).max
val sizeY: Int = conf.size
val minX: Int = 0
val minY: Int = 0
val maxX: Int = sizeX - 1
val maxY: Int = sizeY - 1

def withOverlay(on: (Int,Int)*): Grid =
def withOverlay(on: Pos*): Lights =
copy(overlay = Set.from(on))

def fold[A](zero: A)(inc: A => A)(f: (Int,Int,A) => A): A =
(minY to maxY).foldLeft(zero): (z, y) =>
(minX to maxX).foldLeft(inc(z)): (z, x) =>
f(x,y,z)

def count: Int =
fold(0)(identity): (x, y, n) =>
if light(x, y).isOn then n + 1 else n
grid.positions.foldLeft(0): (result, p) =>
if light(p).isOn then result + 1 else result

def light(x: Int, y: Int): Light =
if overlay(y, x) then on else conf(y)(x)
def light(p: Pos): Light =
if overlay.contains(p) then on else grid.peek(p)

def neighbours(x: Int, y: Int): Vector[Light] =
Vector((-1, -1), (0, -1), (1, -1), (-1, 0),(1, 0), (-1, 1), (0, 1), (1, 1))
.map((dx, dy) => (x + dx, y + dy))
.filter((px, py) => px >= minX && px <= maxX && py >= minY && py <= maxY)
.map(light)
def neighbours(p: Pos): Vector[Light] =
p.adjoint8.toVector.filter(grid.within).map(light)

def next: Grid =
val step: Conf =
fold(Conf.empty)(_ :+ Row.empty)((x,y,conf) =>
conf.init :+ (conf.last :+ light(x,y).next(neighbours(x,y))))
copy(conf = step)
def next: Lights =
copy(
grid = grid.mapElement: (p, l) =>
val neighboursOn = neighbours(p).count(_.isOn)
light(p).nextState(neighboursOn)
)

@tailrec
final def animate(steps: Int, grid: Grid = this): Grid =
if steps <= 0 then grid else animate(steps = steps - 1, grid = grid.next)
final def animate(steps: Int, lights: Lights = this): Lights =
if steps <= 0 then lights else animate(steps = steps - 1, lights = lights.next)

val grid: Grid =
val initial =
lines
.foldLeft(Conf.empty)((conf,line) =>
conf :+ line.foldLeft(Row.empty)((row, char) =>
row :+ char))
Grid(initial)
val lights: Lights = Lights(Grid.fromLines(lines))

import grid.*
import lights.grid.*

override lazy val answer1: Int = grid.animate(100).count
override lazy val answer2: Int = grid.withOverlay((minY,minX), (minY,maxX), (maxY,minX), (maxY,maxX)).animate(100).count
override lazy val answer1: Int = lights.animate(100).count
override lazy val answer2: Int = lights.withOverlay(leftUpper, leftBottom, rightUpper, rightBottom).animate(100).count
22 changes: 12 additions & 10 deletions 2015/src/main/scala/aoc2015/Day19.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ import nmcb.*

object Day19 extends AoC:

type Molecule = String

val molecules: String =
lines
.filterNot(_.isBlank)
.filterNot(_.contains("=>"))
.head
val molecule: Molecule =
lines.filterNot(_.isBlank).filterNot(_.contains("=>")).head

val replacements: Vector[(String,String)] = lines.collect:
val replacements: Vector[(Molecule, Molecule)] = lines.collect:
case s"$molecule => $replacement" => molecule -> replacement

override lazy val answer1: Int =
var generations = Set.empty[String]
def solve1(molecules: Molecule, replacements: Vector[(Molecule, Molecule)]): Int =
var generations = Set.empty[Molecule]
for from -> to <- replacements do
for index <- 0 until molecules.length do
if molecules.slice(index, index + from.length) == from then
Expand All @@ -24,13 +22,17 @@ object Day19 extends AoC:
generations += (prefix + to + postfix)
generations.size


/** Luck > Skill : replacements are already in the right order */
override lazy val answer2: Int =
var target = molecules
def solve2(molecule: Molecule, replacements: Vector[(Molecule, Molecule)]): Int =
var target = molecule
var count = 0
while target != "e" do
for from -> to <- replacements do
if target.contains(to) then
target = target.replaceFirst(to, from)
count = count + 1
count

override lazy val answer1: Int = solve1(molecule, replacements)
override lazy val answer2: Int = solve2(molecule, replacements)
43 changes: 28 additions & 15 deletions nmcb/src/main/scala/nmcb/Grid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ case class Grid[+A](matrix: Vector[Vector[A]]):
val sizeX: Int = matrix.head.size
val minPos: Pos = Pos.origin
val maxPos: Pos = (sizeX - 1, sizeY - 1)

assert(matrix.forall(row => row.size == sizeX))
val leftUpper: Pos = minPos
val leftBottom: Pos = (minPos.x, maxPos.y)
val rightUpper: Pos = (maxPos.x, minPos.y)
val rightBottom: Pos = maxPos

lazy val positions: Set[Pos] =
(for {x <- 0 until sizeX; y <- 0 until sizeY} yield (x, y)).toSet

inline def elements[A1 >: A]: Set[(Pos,A1)] =
inline def elements[B >: A]: Set[(Pos, B)] =
positions.map(p => p -> peek(p))

inline def within(p: Pos): Boolean =
Expand All @@ -26,50 +28,58 @@ case class Grid[+A](matrix: Vector[Vector[A]]):
inline def peek(x: Int, y: Int): A =
matrix(y)(x)

inline def contains[A1 >: A](p: Pos, a: A1): Boolean =
inline def contains[B >: A](p: Pos, a: B): Boolean =
peekOption(p).contains(a)

inline def peekOption(p: Pos): Option[A] =
Option.when(p.withinBounds(minPos, maxPos))(peek(p))

inline def peekOrElse[A1 >: A](p: Pos, default: => A1): A1 =
inline def peekOrElse[B >: A](p: Pos, default: => B): B =
peekOption(p).getOrElse(default)

inline def find[A1 >: A](a: A1)(using CanEqual[A, A1]): Option[Pos] =
inline def find[B >: A](a: B)(using CanEqual[A, B]): Option[Pos] =
elements.find(_.element == a).map(_.pos)

inline def findAll[A1 >: A](a: A1)(using CanEqual[A, A1]): Set[Pos] =
inline def findAll[B >: A](a: B)(using CanEqual[A, B]): Set[Pos] =
elements.filter(_.element == a).map(_.pos)

inline def findOne[A1 >: A](a: A1, default: => Pos = sys.error(s"not found"))(using CanEqual[A, A1]): Pos =
inline def findOne[B >: A](a: B, default: => Pos = sys.error(s"not found"))(using CanEqual[A, B]): Pos =
find(a).getOrElse(default)

inline def filter[A1 >: A](f: ((Pos,A1)) => Boolean): Set[(Pos,A1)] =
inline def filter[B >: A](f: ((Pos, B)) => Boolean): Set[(Pos, B)] =
elements.filter(f)

inline def filterNot[A1 >: A](f: ((Pos,A1)) => Boolean): Set[(Pos,A1)] =
inline def filterNot[B >: A](f: ((Pos, B)) => Boolean): Set[(Pos, B)] =
elements.filterNot(f)

inline def map[B](f: A => B): Grid[B] =
Grid(matrix.map(_.map(f)))


inline def mapElement[B](f: (Pos, A) => B): Grid[B] =
Grid(
matrix
.zipWithIndex.map: (row, y) =>
row.zipWithIndex.map: (a, x) =>
f((x, y), a)
)

inline def row(y: Int): Vector[A] =
matrix(y)

inline def updated[A1 >: A](p: Pos, a: A1): Grid[A1] =
inline def updated[B >: A](p: Pos, a: B): Grid[B] =
Grid(matrix.updated(p.y, row(p.y).updated(p.x, a)))

inline def asString: String =
matrix.map(_.mkString("")).mkString("\n")

inline def extractPath[A1 >: A](from: A1, to: A1, node: A1)(using CanEqual[A, A1]): (Pos, Pos, Grid[A1]) =
inline def extractPath[B >: A](from: B, to: B, node: B)(using CanEqual[A, B]): (Pos, Pos, Grid[B]) =
val fromPos = findOne(from)
val toPos = findOne(to)
val cleared = updated(fromPos, node).updated(toPos, node)
(fromPos, toPos, cleared)

inline def dropRow(y: Int): Grid[A] =
Grid(matrix.zipWithIndex.filter((r,i) => i != y).map((r,i) => r))
Grid(matrix.zipWithIndex.filter((r, i) => i != y).map((r, i) => r))

inline def transpose: Grid[A] =
Grid(matrix.transpose)
Expand All @@ -92,6 +102,9 @@ case class Grid[+A](matrix: Vector[Vector[A]]):
positions.map(p => p -> p.adjoint4.filter(within).map(n => n -> peek(n))).toMap

object Grid:

def empty[A]: Grid[A] =
Grid(Vector(Vector.empty[A]))

def fromLines(lines: Iterator[String]): Grid[Char] =
Grid(lines.map(_.toVector).toVector)
Expand All @@ -111,7 +124,7 @@ object Grid:
def fill[A](sizeX: Int, sizeY: Int, default: A): Grid[A] =
Grid(Vector.fill(sizeX, sizeY)(default))

extension [A](g: (Pos,Pos,Grid[A]))
extension [A](g: (Pos, Pos, Grid[A]))
def from: Pos = g._1
def to: Pos = g._2
def cleared: Grid[A] = g._3
Expand Down