Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
jurisk committed Dec 26, 2024
1 parent bdf7691 commit f7cd117
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 23 deletions.
71 changes: 49 additions & 22 deletions scala2/src/main/scala/jurisk/adventofcode/y2024/Advent24.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import scala.util.Random

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

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

final case class Connections private (map: Map[Wire, Connection]) {
val allWires: Set[Wire] = map.flatMap { case (k, v) =>
val allWires: Set[Wire] = map.flatMap { case (k, v) =>
Set(k, v.a, v.b)
}.toSet
private val allOutputs: Set[Wire] = map.keySet
val allOutputs: Set[Wire] = map.keySet

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

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

private def errorsOnRandomAddition: Int = {
val Samples = 16
val Samples = 8
(for {
_ <- 0 until Samples
r = Values.randomXY
Expand Down Expand Up @@ -194,7 +194,7 @@ object Advent24 extends IOApp.Simple {
}.sum
}

private def isValid: Boolean = topologicallySortedWires.isDefined
def isValid: Boolean = topologicallySortedWires.isDefined

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

def fix: (Connections, Set[SetOfTwo[Wire]]) = {
@tailrec
def applySwaps(swaps: Set[SetOfTwo[Wire]]): Connections =
swaps.foldLeft(this) { case (current, swap) =>
current.swapOutputs(swap)
}

private def errorScore(swaps: Set[SetOfTwo[Wire]]): Int = {
val swapped = applySwaps(swaps)
swapped.errorsOnAddition.orFail("Failed to get errors")
}

def bestSwaps: Set[SetOfTwo[Wire]] = {
def f(
current: Connections,
currentScore: Int,
currentSwaps: Set[SetOfTwo[Wire]],
): (Connections, Set[SetOfTwo[Wire]]) = {
): Set[SetOfTwo[Wire]] = {
def backtracking = {
println("Unexpected: No more improvements, trying to backtrack")
val selected = currentSwaps.toIndexedSeq
.combinations(3)
.map(_.toSet)
.filter(applySwaps(_).isValid)
.minBy(attempt => errorScore(attempt))
val adjusted = applySwaps(selected)
f(
adjusted.errorsOnAddition.orFail("Failed to get errors"),
selected,
)
}

println(s"Current score: $currentScore, Current swaps: $currentSwaps")
if (currentScore == 0) {
(current, currentSwaps)
val ExpectedSwaps = 4
if (currentSwaps.size == ExpectedSwaps) {
currentSwaps
} else {
backtracking
}
} else {
// Note: The swaps for our data are:
// 1. hbk <-> z14
// 2. kvn <-> z18
// 3. dbb <-> z23
// 4. cvh <-> tfn
val current = applySwaps(currentSwaps)
val candidates = current.allOutputs.toIndexedSeq
(for {
aIdx <- candidates.indices
Expand All @@ -258,19 +286,18 @@ object Advent24 extends IOApp.Simple {
if swapped.isValid
} yield (swap, swapped))
.map { case (swap, c) =>
(c, c.errorsOnAddition.orFail("Failed to get errors"), swap)
(c.errorsOnAddition.orFail("Failed to get errors"), swap)
}
.minBy { case (_, score, _) => score } match {
case (c, score, swap) if score < currentScore =>
f(c, score, currentSwaps + swap)
case _ =>
println("No more improvements")
(current, currentSwaps)
.minBy { case (score, _) => score } match {
case (score, swap) if score < currentScore =>
f(score, currentSwaps + swap)
case _ =>
backtracking
}
}
}

f(this, errorsOnAddition.orFail("Failed"), Set.empty)
f(errorsOnAddition.orFail("Failed"), Set.empty)
}
}

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

val (_, swaps) = connections.fix
val swaps = connections.bestSwaps

swaps
.flatMap(_.toSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ final case class SetOfTwo[T](private val underlying: Set[T]) {

def toSet: Set[T] = underlying

override def toString: String = tupleInArbitraryOrder.toString()
override def toString: String = {
val (a, b) = tupleInArbitraryOrder
val aStr = a.toString
val bStr = b.toString
val both = Set(aStr, bStr)
val lowest = both.min
val highest = both.max
s"($lowest, $highest)"
}

def mapUnsafe[B](f: T => B): SetOfTwo[B] = SetOfTwo(toSet.map(f))
def map[B](f: T => B): Set[B] = toSet.map(f)
Expand Down
53 changes: 53 additions & 0 deletions scala2/src/test/scala/jurisk/adventofcode/y2024/Advent24Spec.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package jurisk.adventofcode.y2024

import Advent24._
import cats.implicits.{catsSyntaxFoldableOps0, catsSyntaxOptionId}
import jurisk.collections.immutable.SetOfTwo
import jurisk.utils.Parsing.StringOps
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers._

import scala.annotation.tailrec
import scala.util.Random

class Advent24Spec extends AnyFreeSpec {
private def testData = parseFile(fileName("-test-00"))
private def realData = parseFile(fileName(""))
Expand All @@ -22,5 +28,52 @@ class Advent24Spec extends AnyFreeSpec {
"real" ignore {
part2(realData) shouldEqual "cvh,dbb,hbk,kvn,tfn,z14,z18,z23"
}

// Not fully successful... It is often stuck in local minima.
"can fix arbitrary adders" ignore {
val fixed = {
val (_, input) = realData
input.applySwaps(
Set(
SetOfTwo("hbk", "z14"),
SetOfTwo("kvn", "z18"),
SetOfTwo("dbb", "z23"),
SetOfTwo("cvh", "tfn"),
).map(s => SetOfTwo(s.map(Wire.parse)))
)
}
fixed.errorsOnAddition shouldEqual 0.some

val outputs = fixed.allOutputs.toIndexedSeq

@tailrec
def findValidSwaps(n: Int): Set[SetOfTwo[Wire]] = {
val candidateSwaps = Random
.shuffle(outputs)
.toList
.grouped(2)
.map {
case List(a, b) => SetOfTwo(a, b)
case _ => "Unexpected".fail
}
.take(n)
.toSet
val adjusted = fixed.applySwaps(candidateSwaps)
if (adjusted.isValid) {
candidateSwaps
} else {
findValidSwaps(n)
}
}

val selectedSwaps = findValidSwaps(4)

println(s"Selected random swaps: $selectedSwaps")
val wrongAgain = fixed.applySwaps(selectedSwaps)
val resultSwaps = wrongAgain.bestSwaps
resultSwaps shouldEqual selectedSwaps
val result = wrongAgain.applySwaps(resultSwaps)
result.errorsOnAddition shouldEqual 0.some
}
}
}

0 comments on commit f7cd117

Please sign in to comment.