@@ -21,7 +21,7 @@ import scala.util.Random
21
21
22
22
// Notes:
23
23
// - 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.
25
25
object Advent24 extends IOApp .Simple {
26
26
private val InputBits = 45
27
27
private val OutputBits = InputBits + 1
@@ -90,7 +90,7 @@ object Advent24 extends IOApp.Simple {
90
90
}
91
91
92
92
sealed trait Wire extends Product with Serializable
93
- private object Wire {
93
+ object Wire {
94
94
final case class X (i : Int ) extends Wire {
95
95
override def toString : String = f " x $i%02d "
96
96
}
@@ -135,19 +135,19 @@ object Advent24 extends IOApp.Simple {
135
135
}
136
136
137
137
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) =>
139
139
Set (k, v.a, v.b)
140
140
}.toSet
141
- private val allOutputs : Set [Wire ] = map.keySet
141
+ val allOutputs : Set [Wire ] = map.keySet
142
142
143
143
def foreach (f : Connection => Unit ): Unit = map.values foreach f
144
144
145
- private def errorsOnAddition : Option [Int ] =
145
+ def errorsOnAddition : Option [Int ] =
146
146
// 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)
148
148
149
149
private def errorsOnRandomAddition : Int = {
150
- val Samples = 16
150
+ val Samples = 8
151
151
(for {
152
152
_ <- 0 until Samples
153
153
r = Values .randomXY
@@ -194,7 +194,7 @@ object Advent24 extends IOApp.Simple {
194
194
}.sum
195
195
}
196
196
197
- private def isValid : Boolean = topologicallySortedWires.isDefined
197
+ def isValid : Boolean = topologicallySortedWires.isDefined
198
198
199
199
private val topologicallySortedWires : Option [List [Wire ]] = {
200
200
val edges = map.toSeq.flatMap { case (out, c) =>
@@ -230,22 +230,50 @@ object Advent24 extends IOApp.Simple {
230
230
new Connections (newMap)
231
231
}
232
232
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 ]] = {
235
244
def f (
236
- current : Connections ,
237
245
currentScore : Int ,
238
246
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
+
240
262
println(s " Current score: $currentScore, Current swaps: $currentSwaps" )
241
263
if (currentScore == 0 ) {
242
- (current, currentSwaps)
264
+ val ExpectedSwaps = 4
265
+ if (currentSwaps.size == ExpectedSwaps ) {
266
+ currentSwaps
267
+ } else {
268
+ backtracking
269
+ }
243
270
} else {
244
271
// Note: The swaps for our data are:
245
272
// 1. hbk <-> z14
246
273
// 2. kvn <-> z18
247
274
// 3. dbb <-> z23
248
275
// 4. cvh <-> tfn
276
+ val current = applySwaps(currentSwaps)
249
277
val candidates = current.allOutputs.toIndexedSeq
250
278
(for {
251
279
aIdx <- candidates.indices
@@ -258,19 +286,18 @@ object Advent24 extends IOApp.Simple {
258
286
if swapped.isValid
259
287
} yield (swap, swapped))
260
288
.map { case (swap, c) =>
261
- (c, c .errorsOnAddition.orFail(" Failed to get errors" ), swap)
289
+ (c.errorsOnAddition.orFail(" Failed to get errors" ), swap)
262
290
}
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
269
296
}
270
297
}
271
298
}
272
299
273
- f(this , errorsOnAddition.orFail(" Failed" ), Set .empty)
300
+ f(errorsOnAddition.orFail(" Failed" ), Set .empty)
274
301
}
275
302
}
276
303
@@ -373,7 +400,7 @@ object Advent24 extends IOApp.Simple {
373
400
def part2 (data : Input ): String = {
374
401
val (_, connections) = data
375
402
376
- val (_, swaps) = connections.fix
403
+ val swaps = connections.bestSwaps
377
404
378
405
swaps
379
406
.flatMap(_.toSet)
0 commit comments