From 76427545d6e72d3b04e623a38f7fc0825b207f49 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Sat, 9 Dec 2023 11:26:07 +0100 Subject: [PATCH] day 8 puzzle 2 (naive and not optimized) --- README.md | 4 +- src/day8/Ghost.scala | 80 ++++++++++++++++++++++++++++++++++++ src/day8/Puzzle2.scala | 56 +++++++++++++++++++++++++ tests/day8/Puzzle2Test.scala | 9 ++++ 4 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/day8/Ghost.scala create mode 100644 src/day8/Puzzle2.scala create mode 100644 tests/day8/Puzzle2Test.scala diff --git a/README.md b/README.md index 9dc91b6..5a9ef5b 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ This repo contains my attempt at this year's Advent of Code (2023) I will try and do some problems (probably not all of them) in Scala ## Progress: -#### Stars: 15 / 50 +#### Stars: 16 / 50 | Mon | Tue | Wed | Thu | Fri | Sat | Sun | |:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:|:-----------------:| | | | | | 1
:star::star: | 2
:star::star: | 3
:star::star: | -| 4
:star::star: | 5
:star::star: | 6
:star::star: | 7
:star::star: | 8
:star: | 9 | 10 | +| 4
:star::star: | 5
:star::star: | 6
:star::star: | 7
:star::star: | 8
:star::star: | 9 | 10 | | 11 | 12 | 13 | 14 | 15 | 16 | 17 | | 18 | 19 | 20 | 21 | 22 | 23 | 24 | | 25 | | | | | | | \ No newline at end of file diff --git a/src/day8/Ghost.scala b/src/day8/Ghost.scala new file mode 100644 index 0000000..908216d --- /dev/null +++ b/src/day8/Ghost.scala @@ -0,0 +1,80 @@ +package day8 + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer + +class Ghost(val startId: String) extends Iterator[Long] { + var preLoopOffsets: ArrayBuffer[Int] = new ArrayBuffer() + var loopOffsets: ArrayBuffer[Int] = new ArrayBuffer() + var visited: ArrayBuffer[(String, Int)] = new ArrayBuffer() + private var distIterator: Iterator[Long] = Iterator.empty + + def computePath(nodes: mutable.Map[String, Node], rule: Array[Int]): Unit = { + var id: String = startId + preLoopOffsets.addOne(0) + var ruleI: Int = 0 + + // While not looped + while (!visited.contains((id, ruleI))) { + //println(s" Visited $id (ruleI = $ruleI)") + visited.addOne((id, ruleI)) + id = nodes(id).nextNodes(rule(ruleI)) + preLoopOffsets(preLoopOffsets.length-1) += 1 + ruleI = (ruleI + 1) % rule.length + if (id.last == 'Z') { + preLoopOffsets.addOne(0) + } + } + + val loopStartDist: Int = visited.indexOf((id, ruleI)) + var dist: Int = 0 + var loopStartI: Int = 0 + while (dist < loopStartDist) { + dist += preLoopOffsets(loopStartI) + loopStartI += 1 + } + loopStartI -= 1 + preLoopOffsets(preLoopOffsets.length-1) += preLoopOffsets(loopStartI) - loopStartDist + println(s" Loop from ($id, $ruleI) (index=$loopStartI)") + println(s" # of offsets: ${preLoopOffsets.length}") + + loopOffsets.addAll(preLoopOffsets.drop(loopStartI+1)) + preLoopOffsets = preLoopOffsets.dropRight(preLoopOffsets.length - loopStartI - 1) + println(preLoopOffsets.length) + println(loopOffsets.length) + distIterator = new GhostIterator(preLoopOffsets.toArray, loopOffsets.toArray) + } + + override def hasNext: Boolean = true + + override def next(): Long = distIterator.next() + + private class GhostIterator(val preLoopOffsets: Array[Int], val loopOffsets: Array[Int]) extends Iterator[Long] { + private var inLoop: Boolean = false + private var offsetI: Int = 0 + private var dist: Long = 0 + override def hasNext: Boolean = true + + override def next(): Long = { + var offset: Int = 0 + if (inLoop) { + offset = loopOffsets(offsetI) + } else { + offset = preLoopOffsets(offsetI) + } + dist += offset + + offsetI += 1 + if (!inLoop) { + if (offsetI == preLoopOffsets.length) { + offsetI = 0 + inLoop = true + } + } else { + offsetI %= loopOffsets.length + } + + return dist + } + } +} diff --git a/src/day8/Puzzle2.scala b/src/day8/Puzzle2.scala new file mode 100644 index 0000000..914f3a1 --- /dev/null +++ b/src/day8/Puzzle2.scala @@ -0,0 +1,56 @@ +package day8 + +import scala.collection.mutable +import scala.io.{BufferedSource, Source} +import scala.util.matching.Regex +import scala.util.matching.Regex.Match + +object Puzzle2 { + var rule: Array[Int] = Array.empty + val nodes: mutable.Map[String, Node] = new mutable.HashMap() + var ghosts: Array[Ghost] = Array.empty + def loadInput(path: String): Unit = { + val source: BufferedSource = Source.fromFile(path) + val lines: String = source.getLines().mkString("\n") + val parts: Array[String] = lines.split("\n\n") + + rule = parts(0).map(c => "LR".indexOf(c)).toArray + val regexp: Regex = new Regex("([A-Z]{3}) = \\(([A-Z]{3}), ([A-Z]{3})\\)") + for (line: String <- parts(1).split("\n")) { + val matches: Match = regexp.findFirstMatchIn(line).get + val id: String = matches.group(1) + val left: String = matches.group(2) + val right: String = matches.group(3) + nodes(id) = new Node(id, Array(left, right)) + } + + source.close() + } + + def solve(path: String): Long = { + loadInput(path) + + ghosts = nodes.keys.filter(k => k.last == 'A').map(k => new Ghost(k)).toArray + + val ghostDists: mutable.Map[Ghost, Long] = new mutable.HashMap() + for (ghost: Ghost <- ghosts) { + println(s"Ghost from ${ghost.startId}") + ghost.computePath(nodes, rule) + ghostDists(ghost) = ghost.next() + } + + while (ghostDists.values.toArray.distinct.length != 1) { + val minimum: (Ghost, Long) = ghostDists.toArray.minBy(_._2) + ghostDists(minimum._1) = minimum._1.next() + } + + val dists: Array[Long] = ghostDists.values.toArray + + return dists(0) + } + + def main(args: Array[String]): Unit = { + val solution: Long = solve("./res/day8/input1.txt") + println(solution) + } +} diff --git a/tests/day8/Puzzle2Test.scala b/tests/day8/Puzzle2Test.scala new file mode 100644 index 0000000..12c7134 --- /dev/null +++ b/tests/day8/Puzzle2Test.scala @@ -0,0 +1,9 @@ +package day8 + +import org.scalatest.funsuite.AnyFunSuite + +class Puzzle2Test extends AnyFunSuite { + test("Puzzle2.solve") { + assert(Puzzle2.solve("tests_res/day8/input1.txt") == 6) + } +}