From b0ccc37a0ba8cc668bfbb29f5ee6ebf2abe1c627 Mon Sep 17 00:00:00 2001 From: nmcb Date: Fri, 27 Feb 2026 21:55:06 +0100 Subject: [PATCH 1/2] cleanup --- 2015/src/main/scala/aoc2015/Day18.scala | 82 ++++++++----------------- nmcb/src/main/scala/nmcb/Grid.scala | 17 ++++- 2 files changed, 41 insertions(+), 58 deletions(-) diff --git a/2015/src/main/scala/aoc2015/Day18.scala b/2015/src/main/scala/aoc2015/Day18.scala index 31756b90..a30cb3fd 100644 --- a/2015/src/main/scala/aoc2015/Day18.scala +++ b/2015/src/main/scala/aoc2015/Day18.scala @@ -1,6 +1,7 @@ package aoc2015 import nmcb.* +import nmcb.pos.* import scala.annotation.tailrec @@ -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 diff --git a/nmcb/src/main/scala/nmcb/Grid.scala b/nmcb/src/main/scala/nmcb/Grid.scala index b7b5bca2..c47fddff 100644 --- a/nmcb/src/main/scala/nmcb/Grid.scala +++ b/nmcb/src/main/scala/nmcb/Grid.scala @@ -8,6 +8,10 @@ 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) + val leftUpper: Pos = minPos + val leftBottom: Pos = (minPos.x, maxPos.y) + val rightUpper: Pos = (maxPos.x, minPos.y) + val rightBottom: Pos = maxPos assert(matrix.forall(row => row.size == sizeX)) @@ -52,7 +56,15 @@ case class Grid[+A](matrix: Vector[Vector[A]]): 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) @@ -92,6 +104,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) From f3708904429eb2b19e83991a936cb0b35f5cec86 Mon Sep 17 00:00:00 2001 From: nmcb Date: Sat, 28 Feb 2026 08:58:53 +0100 Subject: [PATCH 2/2] cleanup --- 2015/src/main/scala/aoc2015/Day19.scala | 22 +++++++++++---------- nmcb/src/main/scala/nmcb/Grid.scala | 26 ++++++++++++------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/2015/src/main/scala/aoc2015/Day19.scala b/2015/src/main/scala/aoc2015/Day19.scala index 43980b7f..1f6611d1 100644 --- a/2015/src/main/scala/aoc2015/Day19.scala +++ b/2015/src/main/scala/aoc2015/Day19.scala @@ -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 @@ -24,9 +22,10 @@ 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 @@ -34,3 +33,6 @@ object Day19 extends AoC: 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) diff --git a/nmcb/src/main/scala/nmcb/Grid.scala b/nmcb/src/main/scala/nmcb/Grid.scala index c47fddff..3a4fd8c6 100644 --- a/nmcb/src/main/scala/nmcb/Grid.scala +++ b/nmcb/src/main/scala/nmcb/Grid.scala @@ -13,12 +13,10 @@ case class Grid[+A](matrix: Vector[Vector[A]]): val rightUpper: Pos = (maxPos.x, minPos.y) val rightBottom: Pos = maxPos - assert(matrix.forall(row => row.size == sizeX)) - 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 = @@ -30,28 +28,28 @@ 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] = @@ -68,20 +66,20 @@ case class Grid[+A](matrix: Vector[Vector[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) @@ -126,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