diff --git a/2021/src/main/scala/aoc2021/Day08.scala b/2021/src/main/scala/aoc2021/Day08.scala index b416066..27cc22a 100644 --- a/2021/src/main/scala/aoc2021/Day08.scala +++ b/2021/src/main/scala/aoc2021/Day08.scala @@ -1,33 +1,34 @@ package aoc2021 import nmcb.* + import scala.annotation.tailrec object Day08 extends AoC: - val puzzle = - lines - .map: line => - val input = line.split("\\|")(0).trim.split(" ").toList - val output = line.split("\\|")(1).trim.split(" ").toList - (input, output) - .toList - - - val leds0 = Set(0,1,2,4,5,6) - val leds1 = Set(2,5) - val leds2 = Set(0,2,3,4,6) - val leds3 = Set(0,2,3,5,6) - val leds4 = Set(1,2,3,5) - val leds5 = Set(0,1,3,5,6) - val leds6 = Set(0,1,3,4,5,6) - val leds7 = Set(0,2,5) - val leds8 = Set(0,1,2,3,4,5,6) - val leds9 = Set(0,1,2,3,5,6) + type Encoding = (input: Vector[String], output: Vector[String]) + + val encodings: Vector[Encoding] = + lines.collect: line => + val input = line.split("\\|")(0).trim.split(" ").toVector + val output = line.split("\\|")(1).trim.split(" ").toVector + (input = input, output = output) type Wiring = String def decode(wiring: Wiring)(digit: String): Option[Int] = + + val leds0 = Set(0, 1, 2, 4, 5, 6) + val leds1 = Set(2, 5) + val leds2 = Set(0, 2, 3, 4, 6) + val leds3 = Set(0, 2, 3, 5, 6) + val leds4 = Set(1, 2, 3, 5) + val leds5 = Set(0, 1, 3, 5, 6) + val leds6 = Set(0, 1, 3, 4, 5, 6) + val leds7 = Set(0, 2, 5) + val leds8 = Set(0, 1, 2, 3, 4, 5, 6) + val leds9 = Set(0, 1, 2, 3, 5, 6) + val leds = digit.map(code => wiring.indexOf(code)).toSet if (leds == leds0) Some(0) else if (leds == leds1) Some(1) @@ -41,12 +42,12 @@ object Day08 extends AoC: else if (leds == leds9) Some(9) else None - def valid(wiring: Wiring)(input: List[String]): Boolean = + def valid(wiring: Wiring)(input: Vector[String]): Boolean = @tailrec - def loop(todo: List[String], digitsFound: Set[Int] = Set.empty): Boolean = + def loop(todo: Vector[String], digitsFound: Set[Int] = Set.empty): Boolean = - val allDigits: Set[Int] = Set(0,1,2,3,4,5,6,7,8,9) + val allDigits: Set[Int] = Set(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) def inDigitsFound(digit: Int): Boolean = digitsFound.contains(digit) @@ -54,20 +55,20 @@ object Day08 extends AoC: def notInDigitsLeftToValidate(digit: Int): Boolean = !(allDigits -- digitsFound).contains(digit) - todo match - case Nil => digitsFound == allDigits - case encoding :: encodings => decode(wiring)(encoding) match - case None => false - case Some(digit) if inDigitsFound(digit) => false + todo.runtimeChecked match + case Vector() => digitsFound == allDigits + case encoding +: encodings => decode(wiring)(encoding) match + case None => false + case Some(digit) if inDigitsFound(digit) => false case Some(digit) if notInDigitsLeftToValidate(digit) => false - case Some(digit) => loop(encodings, digitsFound + digit) + case Some(digit) => loop(encodings, digitsFound + digit) loop(input) - override lazy val answer1: Int = - puzzle - .map: (_,output) => + def solve1(encodings: Vector[Encoding]): Int = + encodings + .map: (_, output) => output.count: digit => val is1 = digit.length == 2 val is7 = digit.length == 3 @@ -76,28 +77,19 @@ object Day08 extends AoC: is1 || is4 || is7 || is8 .sum - override lazy val answer2: Int = + def solve2(encodings: Vector[Encoding]): Int = @tailrec - def search(wirings: List[String])(input: List[String]): String = - val wiring = wirings.head - if valid(wiring)(input) then - wiring - else - search(wirings.tail)(input) - - val init: List[Wiring] = - "abcdefg".permutations.toList - - val numbers: List[Int] = - puzzle.zipWithIndex.map: - case ((input,output),idx) => - val wiring: Wiring = - search(init)(input) - val number: Int = - output - .foldLeft(""): (num,digit) => - num + decode(wiring)(digit).get - .toInt - number - - numbers.sum + def search(wirings: Vector[String], input: Vector[String]): String = + wirings.runtimeChecked match + case wiring +: rest if valid(wiring)(input) => wiring + case _ +: rest => search(rest, input) + val wirings = "abcdefg".permutations.toVector + encodings + .map: (input, output) => + val wiring: Wiring = search(wirings, input) + output.foldLeft("")((num, digit) => num + decode(wiring)(digit).get).toInt + .sum + + override lazy val answer1: Int = solve1(encodings) + override lazy val answer2: Int = solve2(encodings) + diff --git a/2021/src/main/scala/aoc2021/Day09.scala b/2021/src/main/scala/aoc2021/Day09.scala index 88029ee..9f0052f 100644 --- a/2021/src/main/scala/aoc2021/Day09.scala +++ b/2021/src/main/scala/aoc2021/Day09.scala @@ -1,6 +1,7 @@ package aoc2021 import nmcb.* +import nmcb.predef.* import nmcb.pos.* import scala.annotation.tailrec @@ -9,32 +10,38 @@ object Day09 extends AoC: val floor: Grid[Int] = Grid.fromLines(lines).map(_.toString.toInt) + type PosHeight = (Pos, Int) + extension (floor: Grid[Int]) - def neighbouringHeights(p: Pos): Set[(Pos, Int)] = + def neighbouringHeights(p: Pos): Set[PosHeight] = p.adjoint4.flatMap(p => floor.peekOption(p).map(h => p -> h)) def upstream(p: Pos): Set[Pos] = - neighbouringHeights(p).filter((_, nh) => nh < 9 && nh > floor.peek(p)).map((n, _) => n) - - def heights: List[(Pos, Int)] = - floor.elements.foldLeft(List.empty[(Pos,Int)]): - case (result, (p, h)) if floor.neighbouringHeights(p).forall((_, nh) => h < nh) => (p, h) :: result - case (result, _) => result - - - override lazy val answer1: Int = - floor.heights.map((_, h) => h + 1).sum - - - override lazy val answer2: Int = - @tailrec - def loop(todo: Set[Pos], acc: Set[Pos] = Set.empty): Int = - if todo.isEmpty then - acc.size - else - val current = todo.head - val upstream = floor.upstream(current) - loop(upstream ++ todo.tail, acc + current) - - floor.heights.map((p, _) => loop(Set(p))).sorted.reverse.take(3).product + neighbouringHeights(p).filter(ph => ph.right < 9 && ph.right > floor.peek(p)).map(_.element) + + def posHeights: Vector[PosHeight] = + floor.elements.foldLeft(Vector.empty[PosHeight]): + case (result, ph) if floor.neighbouringHeights(ph.left).forall(nh => ph.right < nh.right) => ph +: result + case (result, _) => result + + def solve1(floor: Grid[Int]): Int = + floor.posHeights.map(ph => ph.right + 1).sum + + def solve2(floor: Grid[Int]): Int = + def basinSize(p: Pos): Int = + @tailrec + def loop(todo: Set[Pos], result: Set[Pos] = Set.empty): Int = + if todo.isEmpty then + result.size + else + val current = todo.head + val next = todo.tail + val upstream = floor.upstream(current) + loop(upstream ++ next, result + current) + loop(Set(p)) + floor.positions.map(basinSize).toVector.sorted.reverse.take(3).product + + + override lazy val answer2: Int = solve2(floor) + override lazy val answer1: Int = solve1(floor)