Skip to content

Commit f7cd117

Browse files
committed
Refactoring
1 parent bdf7691 commit f7cd117

File tree

3 files changed

+111
-23
lines changed

3 files changed

+111
-23
lines changed

scala2/src/main/scala/jurisk/adventofcode/y2024/Advent24.scala

+49-22
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import scala.util.Random
2121

2222
// Notes:
2323
// - I actually solved this by simplifying the output DOT file and then finding irregularities manually.
24-
// - Later, I tried to apply a genetic algorithm, but failed to get this to converge.
24+
// - I tried to apply a genetic algorithm, but failed to get this to converge.
2525
object Advent24 extends IOApp.Simple {
2626
private val InputBits = 45
2727
private val OutputBits = InputBits + 1
@@ -90,7 +90,7 @@ object Advent24 extends IOApp.Simple {
9090
}
9191

9292
sealed trait Wire extends Product with Serializable
93-
private object Wire {
93+
object Wire {
9494
final case class X(i: Int) extends Wire {
9595
override def toString: String = f"x$i%02d"
9696
}
@@ -135,19 +135,19 @@ object Advent24 extends IOApp.Simple {
135135
}
136136

137137
final case class Connections private (map: Map[Wire, Connection]) {
138-
val allWires: Set[Wire] = map.flatMap { case (k, v) =>
138+
val allWires: Set[Wire] = map.flatMap { case (k, v) =>
139139
Set(k, v.a, v.b)
140140
}.toSet
141-
private val allOutputs: Set[Wire] = map.keySet
141+
val allOutputs: Set[Wire] = map.keySet
142142

143143
def foreach(f: Connection => Unit): Unit = map.values foreach f
144144

145-
private def errorsOnAddition: Option[Int] =
145+
def errorsOnAddition: Option[Int] =
146146
// We care more about `errorsBitByBit`, but since they didn't catch everything, we also care about `errorsOnRandomAddition`
147-
isValid.option(128 * errorsBitByBit + errorsOnRandomAddition)
147+
isValid.option(4096 * errorsBitByBit + errorsOnRandomAddition)
148148

149149
private def errorsOnRandomAddition: Int = {
150-
val Samples = 16
150+
val Samples = 8
151151
(for {
152152
_ <- 0 until Samples
153153
r = Values.randomXY
@@ -194,7 +194,7 @@ object Advent24 extends IOApp.Simple {
194194
}.sum
195195
}
196196

197-
private def isValid: Boolean = topologicallySortedWires.isDefined
197+
def isValid: Boolean = topologicallySortedWires.isDefined
198198

199199
private val topologicallySortedWires: Option[List[Wire]] = {
200200
val edges = map.toSeq.flatMap { case (out, c) =>
@@ -230,22 +230,50 @@ object Advent24 extends IOApp.Simple {
230230
new Connections(newMap)
231231
}
232232

233-
def fix: (Connections, Set[SetOfTwo[Wire]]) = {
234-
@tailrec
233+
def applySwaps(swaps: Set[SetOfTwo[Wire]]): Connections =
234+
swaps.foldLeft(this) { case (current, swap) =>
235+
current.swapOutputs(swap)
236+
}
237+
238+
private def errorScore(swaps: Set[SetOfTwo[Wire]]): Int = {
239+
val swapped = applySwaps(swaps)
240+
swapped.errorsOnAddition.orFail("Failed to get errors")
241+
}
242+
243+
def bestSwaps: Set[SetOfTwo[Wire]] = {
235244
def f(
236-
current: Connections,
237245
currentScore: Int,
238246
currentSwaps: Set[SetOfTwo[Wire]],
239-
): (Connections, Set[SetOfTwo[Wire]]) = {
247+
): Set[SetOfTwo[Wire]] = {
248+
def backtracking = {
249+
println("Unexpected: No more improvements, trying to backtrack")
250+
val selected = currentSwaps.toIndexedSeq
251+
.combinations(3)
252+
.map(_.toSet)
253+
.filter(applySwaps(_).isValid)
254+
.minBy(attempt => errorScore(attempt))
255+
val adjusted = applySwaps(selected)
256+
f(
257+
adjusted.errorsOnAddition.orFail("Failed to get errors"),
258+
selected,
259+
)
260+
}
261+
240262
println(s"Current score: $currentScore, Current swaps: $currentSwaps")
241263
if (currentScore == 0) {
242-
(current, currentSwaps)
264+
val ExpectedSwaps = 4
265+
if (currentSwaps.size == ExpectedSwaps) {
266+
currentSwaps
267+
} else {
268+
backtracking
269+
}
243270
} else {
244271
// Note: The swaps for our data are:
245272
// 1. hbk <-> z14
246273
// 2. kvn <-> z18
247274
// 3. dbb <-> z23
248275
// 4. cvh <-> tfn
276+
val current = applySwaps(currentSwaps)
249277
val candidates = current.allOutputs.toIndexedSeq
250278
(for {
251279
aIdx <- candidates.indices
@@ -258,19 +286,18 @@ object Advent24 extends IOApp.Simple {
258286
if swapped.isValid
259287
} yield (swap, swapped))
260288
.map { case (swap, c) =>
261-
(c, c.errorsOnAddition.orFail("Failed to get errors"), swap)
289+
(c.errorsOnAddition.orFail("Failed to get errors"), swap)
262290
}
263-
.minBy { case (_, score, _) => score } match {
264-
case (c, score, swap) if score < currentScore =>
265-
f(c, score, currentSwaps + swap)
266-
case _ =>
267-
println("No more improvements")
268-
(current, currentSwaps)
291+
.minBy { case (score, _) => score } match {
292+
case (score, swap) if score < currentScore =>
293+
f(score, currentSwaps + swap)
294+
case _ =>
295+
backtracking
269296
}
270297
}
271298
}
272299

273-
f(this, errorsOnAddition.orFail("Failed"), Set.empty)
300+
f(errorsOnAddition.orFail("Failed"), Set.empty)
274301
}
275302
}
276303

@@ -373,7 +400,7 @@ object Advent24 extends IOApp.Simple {
373400
def part2(data: Input): String = {
374401
val (_, connections) = data
375402

376-
val (_, swaps) = connections.fix
403+
val swaps = connections.bestSwaps
377404

378405
swaps
379406
.flatMap(_.toSet)

scala2/src/main/scala/jurisk/collections/immutable/SetOfTwo.scala

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ final case class SetOfTwo[T](private val underlying: Set[T]) {
1010

1111
def toSet: Set[T] = underlying
1212

13-
override def toString: String = tupleInArbitraryOrder.toString()
13+
override def toString: String = {
14+
val (a, b) = tupleInArbitraryOrder
15+
val aStr = a.toString
16+
val bStr = b.toString
17+
val both = Set(aStr, bStr)
18+
val lowest = both.min
19+
val highest = both.max
20+
s"($lowest, $highest)"
21+
}
1422

1523
def mapUnsafe[B](f: T => B): SetOfTwo[B] = SetOfTwo(toSet.map(f))
1624
def map[B](f: T => B): Set[B] = toSet.map(f)

scala2/src/test/scala/jurisk/adventofcode/y2024/Advent24Spec.scala

+53
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package jurisk.adventofcode.y2024
22

33
import Advent24._
4+
import cats.implicits.{catsSyntaxFoldableOps0, catsSyntaxOptionId}
5+
import jurisk.collections.immutable.SetOfTwo
6+
import jurisk.utils.Parsing.StringOps
47
import org.scalatest.freespec.AnyFreeSpec
58
import org.scalatest.matchers.should.Matchers._
69

10+
import scala.annotation.tailrec
11+
import scala.util.Random
12+
713
class Advent24Spec extends AnyFreeSpec {
814
private def testData = parseFile(fileName("-test-00"))
915
private def realData = parseFile(fileName(""))
@@ -22,5 +28,52 @@ class Advent24Spec extends AnyFreeSpec {
2228
"real" ignore {
2329
part2(realData) shouldEqual "cvh,dbb,hbk,kvn,tfn,z14,z18,z23"
2430
}
31+
32+
// Not fully successful... It is often stuck in local minima.
33+
"can fix arbitrary adders" ignore {
34+
val fixed = {
35+
val (_, input) = realData
36+
input.applySwaps(
37+
Set(
38+
SetOfTwo("hbk", "z14"),
39+
SetOfTwo("kvn", "z18"),
40+
SetOfTwo("dbb", "z23"),
41+
SetOfTwo("cvh", "tfn"),
42+
).map(s => SetOfTwo(s.map(Wire.parse)))
43+
)
44+
}
45+
fixed.errorsOnAddition shouldEqual 0.some
46+
47+
val outputs = fixed.allOutputs.toIndexedSeq
48+
49+
@tailrec
50+
def findValidSwaps(n: Int): Set[SetOfTwo[Wire]] = {
51+
val candidateSwaps = Random
52+
.shuffle(outputs)
53+
.toList
54+
.grouped(2)
55+
.map {
56+
case List(a, b) => SetOfTwo(a, b)
57+
case _ => "Unexpected".fail
58+
}
59+
.take(n)
60+
.toSet
61+
val adjusted = fixed.applySwaps(candidateSwaps)
62+
if (adjusted.isValid) {
63+
candidateSwaps
64+
} else {
65+
findValidSwaps(n)
66+
}
67+
}
68+
69+
val selectedSwaps = findValidSwaps(4)
70+
71+
println(s"Selected random swaps: $selectedSwaps")
72+
val wrongAgain = fixed.applySwaps(selectedSwaps)
73+
val resultSwaps = wrongAgain.bestSwaps
74+
resultSwaps shouldEqual selectedSwaps
75+
val result = wrongAgain.applySwaps(resultSwaps)
76+
result.errorsOnAddition shouldEqual 0.some
77+
}
2578
}
2679
}

0 commit comments

Comments
 (0)