From 838eb99ed9910a40cf40486325dce5b085feba8a Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:28:28 -0600 Subject: [PATCH] feat: wip on day 10 part 2 --- cmd/10/machine/machine.go | 215 +++++++++++++++++++++++++++++++++----- cmd/10/main.go | 7 ++ cmd/10/main_test.go | 12 ++- cmd/10/solver.go | 143 +++++++++++++++++++++++++ 4 files changed, 349 insertions(+), 28 deletions(-) create mode 100644 cmd/10/solver.go diff --git a/cmd/10/machine/machine.go b/cmd/10/machine/machine.go index e303b4e..7ab0cb4 100644 --- a/cmd/10/machine/machine.go +++ b/cmd/10/machine/machine.go @@ -2,7 +2,6 @@ package machine import ( - "fmt" "math" "regexp" "slices" @@ -119,49 +118,211 @@ func (m machine) ConfigureLights() int { } func (m machine) ConfigureJoltages() int { - minPresses := 0 - // counters := make([]int, len(m.joltageSettings)) + matrix := m.createMatrix() - // given the target joltage of a counter - // how many times can I press a particular button - // before making one of the counters that the button - // affects invalid - // 3,5,4,7 - // 0 (3) - // 1 (1,3) - // 2 (2) - // 3 (2,3) - // 4 (0,2) - // 5 (0,1) + eliminated := performGaussianElimination(matrix) + pivots, freeVars := analyzeMatrix(eliminated) - // (0n * 1) + (1n * 0) + (1n * 1) + (3n * 1) = 7 - // 2n + 3n + 4n = 4 + numVars := len(matrix[0]) - 1 + values := make([]int, numVars) + bestSolution := Solution{sum: math.MaxInt} - // TODO: I need to use Gausian elimination - // to solve this - // TODO: Or matrix method maybe? + iterativeSearch(freeVars, pivots, eliminated, values, &bestSolution) + return bestSolution.sum +} + +type Solution struct { + values []int + sum int + found bool +} + +func (m machine) createMatrix() [][]float64 { rows := len(m.desiredJoltages) cols := len(m.buttons) - grid := make([][]int, rows) + matrix := make([][]float64, rows) for r := range rows { - grid[r] = make([]int, cols+1) + matrix[r] = make([]float64, cols+1) for i, b := range m.buttons { for _, sw := range b.Switches() { if sw == r { - grid[r][i] = 1 + matrix[r][i] = 1 } } } - grid[r][cols] = m.desiredJoltages[r] - - fmt.Println(grid[r]) + matrix[r][cols] = float64(m.desiredJoltages[r]) } - fmt.Println() + return matrix +} - return minPresses +func performGaussianElimination(m [][]float64) [][]float64 { + rows := len(m) + cols := len(m[0]) + pivotColumn := 0 + + mCopy := make([][]float64, rows) + + for i := range rows { + mCopy[i] = make([]float64, cols) + copy(mCopy[i], m[i]) + } + + for r1 := range rows { + if cols <= pivotColumn { + return mCopy + } + + currentRow := r1 + + for mCopy[currentRow][pivotColumn] == 0 { + currentRow++ + + if rows == currentRow { + currentRow = r1 + pivotColumn++ + + if cols == pivotColumn { + return mCopy + } + } + } + + mCopy[currentRow], mCopy[r1] = mCopy[r1], mCopy[currentRow] + + pivotValue := mCopy[r1][pivotColumn] + + if pivotValue != 0 { + for j := range cols { + mCopy[r1][j] /= pivotValue + } + } + + for r2 := range rows { + if r2 != r1 { + factor := mCopy[r2][pivotColumn] + + for col := range cols { + mCopy[r2][col] -= factor * mCopy[r1][col] + } + } + } + + pivotColumn++ + } + + return mCopy +} + +func analyzeMatrix(m [][]float64) (map[int]int, []int) { + pivots := make(map[int]int) + + cols := len(m[0]) + numVars := cols - 1 + + isFree := make([]bool, numVars) + + for i := range isFree { + isFree[i] = true + } + + rows := len(m) + + for r := range rows { + for c := 0; c < cols-1; c++ { + if math.Abs(m[r][c]-1.0) < 1e-9 { + pivots[c] = r + isFree[c] = false + break + } + } + } + + freeVars := []int{} + + for i, free := range isFree { + if free { + freeVars = append(freeVars, i) + } + } + + return pivots, freeVars +} + +func iterativeSearch(freeVars []int, pivots map[int]int, matrix [][]float64, values []int, best *Solution) { + if len(freeVars) == 0 { + evaluateSolution(pivots, matrix, values, best) + return + } + + counters := make([]int, len(freeVars)) + limit := 250 + + for { + for i, counterVal := range counters { + values[freeVars[i]] = counterVal + } + + evaluateSolution(pivots, matrix, values, best) + + idx := len(counters) - 1 + + for idx >= 0 { + counters[idx]++ + + if counters[idx] > limit { + counters[idx] = 0 + idx-- + } else { + break + } + } + + if idx < 0 { + break + } + } +} + +func evaluateSolution(pivots map[int]int, m [][]float64, values []int, best *Solution) { + isValid := true + currentSum := 0 + cols := len(m[0]) + + for col, row := range pivots { + sum := m[row][cols-1] + + for c := 0; c < cols-1; c++ { + if c != col { + coeff := m[row][c] + sum -= coeff * float64(values[c]) + } + } + + values[col] = int(math.Round(sum)) + } + + for _, v := range values { + if v < 0 { + isValid = false + break + } + + currentSum += v + } + + if isValid { + if currentSum < best.sum { + best.sum = currentSum + + best.values = make([]int, len(values)) + copy(best.values, values) + + best.found = true + } + } } diff --git a/cmd/10/main.go b/cmd/10/main.go index 1ef9b74..37c1a5b 100644 --- a/cmd/10/main.go +++ b/cmd/10/main.go @@ -1,6 +1,8 @@ package main import ( + "fmt" + "github.com/StevanFreeborn/advent-of-code-2025/cmd/10/machine" "github.com/StevanFreeborn/advent-of-code-2025/internal/file" ) @@ -20,9 +22,14 @@ func SolvePartOne(filePath string) int { func SolvePartTwo(filePath string) int { total := 0 + linesRead := 0 for line := range file.ReadLines(filePath) { + linesRead++ machine := machine.From(line) buttonsPressed := machine.ConfigureJoltages() + if buttonsPressed == 0 { + fmt.Printf("No solution found for line %d: %s\n", linesRead, line) + } total += buttonsPressed } diff --git a/cmd/10/main_test.go b/cmd/10/main_test.go index 25a06a8..58cced7 100644 --- a/cmd/10/main_test.go +++ b/cmd/10/main_test.go @@ -27,7 +27,7 @@ func TestSolvePartOneWithInput(t *testing.T) { } func TestSolvePartTwoWithExampleInput(t *testing.T) { - expected := 7 + expected := 33 result := solution.SolvePartTwo("EXAMPLE.txt") @@ -35,3 +35,13 @@ func TestSolvePartTwoWithExampleInput(t *testing.T) { t.Errorf("got %d but wanted %d", result, expected) } } + +func TestSolvePartTwoWithInput(t *testing.T) { + expected := -1 + + result := solution.SolvePartTwo("INPUT.txt") + + if result != expected { + t.Errorf("got %d but wanted %d", result, expected) + } +} diff --git a/cmd/10/solver.go b/cmd/10/solver.go new file mode 100644 index 0000000..dc37819 --- /dev/null +++ b/cmd/10/solver.go @@ -0,0 +1,143 @@ +package main + +import ( + "fmt" + "strings" + "time" +) + +const ( + Reset = "\033[0m" + Red = "\033[31m" + Green = "\033[32m" + Yellow = "\033[33m" + Blue = "\033[34m" + White = "\033[37m" + Gray = "\033[90m" + Clear = "\033[H\033[2J" +) + +type Solution struct { + a, b, c, d, e, f int + sum int + found bool +} + +func main() { + best := Solution{sum: 99999} + delay := 150 * time.Millisecond + + for f := 0; f <= 5; f++ { + for d := 0; d <= 7; d++ { + + a := 2 - d + f + b := 5 - f + c := 4 - d - f + e := 3 - f + + currentSum := a + b + c + d + e + f + + validA := a >= 0 + validB := b >= 0 + validC := c >= 0 + validE := e >= 0 + + isSolution := validA && validB && validC && validE + + isNewBest := false + + if isSolution { + if currentSum < best.sum { + best = Solution{a, b, c, d, e, f, currentSum, true} + isNewBest = true + } + } + + printDashboard(d, f, a, b, c, e, currentSum, best, isSolution) + + if isNewBest { + time.Sleep(1 * time.Second) + } else { + time.Sleep(delay) + } + } + } + + printFinalResult(best) +} + +func printDashboard(d, f, a, b, c, e, sum int, best Solution, valid bool) { + var sb strings.Builder + + sb.WriteString(Clear) + + sb.WriteString(fmt.Sprintf(" %sFREE VARIABLES%s\n", Yellow, Reset)) + sb.WriteString(" ────────────────────────────────────────\n") + sb.WriteString(fmt.Sprintf(" INPUT d: %s%-3d%s %s\n", White, d, Reset, bar(d, 7))) + sb.WriteString(fmt.Sprintf(" INPUT f: %s%-3d%s %s\n\n", White, f, Reset, bar(f, 7))) + + sb.WriteString(fmt.Sprintf(" %sDEPENDENT VARIABLES%s\n", Yellow, Reset)) + sb.WriteString(" ────────────────────────────────────────\n") + sb.WriteString(formatVar("a", "2 - d + f", a)) + sb.WriteString(formatVar("b", "5 - f", b)) + sb.WriteString(formatVar("c", "4 - d - f", c)) + sb.WriteString(formatVar("e", "3 - f", e)) + + sb.WriteString("\n") + + sb.WriteString(fmt.Sprintf(" %sSTATUS%s\n", Yellow, Reset)) + sb.WriteString(" ────────────────────────────────────────\n") + + statusColor := Red + statusText := "INVALID (Constraints Failed)" + + if valid { + statusColor = Green + statusText = "VALID SOLUTION" + } + + sb.WriteString(fmt.Sprintf(" Current Sum: %d\n", sum)) + sb.WriteString(fmt.Sprintf(" Constraint Check: %s%s%s\n\n", statusColor, statusText, Reset)) + + sb.WriteString(fmt.Sprintf(" %sBEST MINIMUM FOUND SO FAR%s\n", Yellow, Reset)) + sb.WriteString(" ────────────────────────────────────────\n") + + if best.found { + sb.WriteString(fmt.Sprintf(" Total Sum: %s%d%s\n", Green, best.sum, Reset)) + sb.WriteString(fmt.Sprintf(" Values: a=%d, b=%d, c=%d, d=%d, e=%d, f=%d\n", best.a, best.b, best.c, best.d, best.e, best.f)) + } else { + sb.WriteString(" Searching...\n") + } + + fmt.Print(sb.String()) +} + +func formatVar(name, eq string, val int) string { + color := Green + check := "OK" + + if val < 0 { + color = Red + check = "FAIL (< 0)" + } + + displayVal := max(val, 0) + visual := bar(displayVal, 10) + + return fmt.Sprintf(" %s = %-10s = %s%-3d%s [%-10s] %s\n", name, eq, color, val, Reset, check, visual) +} + +func bar(val, max int) string { + if val < 0 { + val = 0 + } + if val > max { + val = max + } + return "[" + strings.Repeat("█", val) + strings.Repeat("░", max-val) + "]" +} + +func printFinalResult(s Solution) { + fmt.Printf("Smallest Total Possible: %s%d%s\n", Green, s.sum, Reset) + fmt.Printf("Configuration: a=%d, b=%d, c=%d, d=%d, e=%d, f=%d\n", s.a, s.b, s.c, s.d, s.e, s.f) +}