|
1 | 1 | package jurisk.adventofcode.y2024
|
2 | 2 |
|
| 3 | +import cats.implicits._ |
| 4 | +import jurisk.adventofcode.y2024.Advent08.Square.Antenna |
| 5 | +import jurisk.adventofcode.y2024.Advent08.Square.Empty |
| 6 | +import jurisk.geometry.Coords2D |
| 7 | +import jurisk.geometry.Field2D |
3 | 8 | import jurisk.utils.FileInput._
|
4 |
| -import jurisk.utils.Parsing.StringOps |
| 9 | +import mouse.all._ |
| 10 | + |
| 11 | +import scala.annotation.tailrec |
5 | 12 |
|
6 | 13 | object Advent08 {
|
7 |
| - type Input = List[Command] |
8 |
| - type N = Long |
9 |
| - |
10 |
| - sealed trait Command extends Product with Serializable |
11 |
| - object Command { |
12 |
| - case object Noop extends Command |
13 |
| - final case class Something( |
14 |
| - values: List[N] |
15 |
| - ) extends Command |
16 |
| - final case class Other(value: String) extends Command |
17 |
| - |
18 |
| - def parse(s: String): Command = |
19 |
| - s match { |
20 |
| - case "noop" => Noop |
21 |
| - case s"something $rem" => Something(rem.extractLongList) |
22 |
| - case s if s.nonEmpty => Other(s) |
23 |
| - case _ => s.failedToParse |
24 |
| - } |
| 14 | + private type Frequency = Char |
| 15 | + |
| 16 | + sealed trait Square |
| 17 | + object Square { |
| 18 | + case object Empty extends Square |
| 19 | + final case class Antenna(frequency: Frequency) extends Square |
| 20 | + |
| 21 | + def parse(ch: Char): Square = ch match { |
| 22 | + case '.' => Empty |
| 23 | + case ch => Antenna(ch) |
| 24 | + } |
25 | 25 | }
|
26 | 26 |
|
| 27 | + private type SquareAndAntiNodes = (Square, Set[Frequency]) |
| 28 | + type Input = Field2D[SquareAndAntiNodes] |
| 29 | + type N = Long |
| 30 | + |
27 | 31 | def parse(input: String): Input =
|
28 |
| - input.parseLines(Command.parse) |
| 32 | + Field2D.parseCharField(input).map(ch => (Square.parse(ch), Set.empty)) |
| 33 | + |
| 34 | + @tailrec |
| 35 | + private def helper( |
| 36 | + field: Input, |
| 37 | + frequency: Char, |
| 38 | + current: Coords2D, |
| 39 | + diff: Coords2D, |
| 40 | + limit: Option[Int], |
| 41 | + count: Int = 0, |
| 42 | + ): Input = |
| 43 | + if (field.isValidCoordinate(current) && limit.forall(count <= _)) { |
| 44 | + val updated = if (limit.isEmpty || count != 0) { |
| 45 | + field.modifyIgnoringInvalidCoords( |
| 46 | + current, |
| 47 | + { case (ch, set) => |
| 48 | + (ch, set + frequency) |
| 49 | + }, |
| 50 | + ) |
| 51 | + } else { |
| 52 | + field |
| 53 | + } |
| 54 | + |
| 55 | + helper(updated, frequency, current + diff, diff, limit, count + 1) |
| 56 | + } else { |
| 57 | + field |
| 58 | + } |
| 59 | + |
| 60 | + private def update( |
| 61 | + field: Input, |
| 62 | + frequency: Char, |
| 63 | + a: Coords2D, |
| 64 | + b: Coords2D, |
| 65 | + limit: Option[Int], |
| 66 | + ): Input = |
| 67 | + List((a, a - b), (b, b - a)).foldLeft(field) { |
| 68 | + case (field, (start, diff)) => |
| 69 | + helper(field, frequency, start, diff, limit) |
| 70 | + } |
| 71 | + |
| 72 | + private def processFrequency( |
| 73 | + field: Input, |
| 74 | + frequency: Char, |
| 75 | + limit: Option[Int], |
| 76 | + ): Input = { |
| 77 | + val locations = field.filterCoordsByValue { case (sq, _) => |
| 78 | + sq == Antenna(frequency) |
| 79 | + } |
| 80 | + val pairs = (locations, locations).mapN { case (a, b) => |
| 81 | + (a != b).option((a, b)) |
| 82 | + }.flatten |
| 83 | + |
| 84 | + pairs.foldLeft(field) { case (field, (a, b)) => |
| 85 | + update(field, frequency, a, b, limit) |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + def solve(data: Input, limit: Option[Int]): N = { |
| 90 | + val frequencies = data.values.flatMap { case (sq, _) => |
| 91 | + sq match { |
| 92 | + case Antenna(frequency) => Some(frequency) |
| 93 | + case Empty => None |
| 94 | + } |
| 95 | + }.toSet |
| 96 | + |
| 97 | + val result = frequencies.foldLeft(data) { case (field, frequency) => |
| 98 | + processFrequency(field, frequency, limit) |
| 99 | + } |
| 100 | + |
| 101 | + result.count { case (_, s) => s.nonEmpty } |
| 102 | + } |
29 | 103 |
|
30 | 104 | def part1(data: Input): N =
|
31 |
| - 0 |
| 105 | + solve(data, 1.some) |
32 | 106 |
|
33 | 107 | def part2(data: Input): N =
|
34 |
| - 0 |
| 108 | + solve(data, none) |
35 | 109 |
|
36 | 110 | def parseFile(fileName: String): Input =
|
37 | 111 | parse(readFileText(fileName))
|
|
0 commit comments