From 106113023b2c29b129aadc3b7f8758a7bd86b6af Mon Sep 17 00:00:00 2001 From: Kharec Date: Fri, 30 Jan 2026 20:49:06 +0100 Subject: [PATCH] feat: D10P2 solution --- internal/2025/DayTen/code.go | 272 ++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 5 deletions(-) diff --git a/internal/2025/DayTen/code.go b/internal/2025/DayTen/code.go index f95baee..e3c2eba 100644 --- a/internal/2025/DayTen/code.go +++ b/internal/2025/DayTen/code.go @@ -1,11 +1,25 @@ package dayten import ( - "advent-of-code/internal/registry" + "math" "os" "regexp" "strconv" "strings" + + "advent-of-code/internal/registry" +) + +const ( + positiveInfinity = math.MaxFloat64 + negativeInfinity = -math.MaxFloat64 + epsilon = 1e-9 +) + +var ( + bracketRegex = regexp.MustCompile(`\[([.#]+)\]`) + parenthesisRegex = regexp.MustCompile(`\(([^)]+)\)`) + braceRegex = regexp.MustCompile(`\{([^}]+)\}`) ) func init() { @@ -18,9 +32,6 @@ func ParseInput(filepath string) []string { } func parseMachine(line string) ([]bool, [][]int) { - bracketRegex := regexp.MustCompile(`\[([.#]+)\]`) - parenthesisRegex := regexp.MustCompile(`\(([^)]+)\)`) - bracketMatch := bracketRegex.FindStringSubmatch(line) targetString := bracketMatch[1] target := make([]bool, len(targetString)) @@ -45,6 +56,246 @@ func parseMachine(line string) ([]bool, [][]int) { return target, buttons } +func solveIntegerLinearMinimization(constraints [][]float64, objective []float64) int { + best := positiveInfinity + stack := [][][]float64{constraints} + + for len(stack) > 0 { + current := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + numberOfRows := len(current) + numberOfVariables := len(current[0]) - 1 + rightHandSideIdx := numberOfVariables + 1 + + nonBasic := make([]int, numberOfVariables+1) + for idx := range numberOfVariables { + nonBasic[idx] = idx + } + nonBasic[numberOfVariables] = -1 + + basic := make([]int, numberOfRows) + for idx := range numberOfRows { + basic[idx] = numberOfVariables + idx + } + + tableau := make([][]float64, numberOfRows+2) + for idx := range numberOfRows { + tableau[idx] = make([]float64, numberOfVariables+2) + copy(tableau[idx][:numberOfVariables+1], current[idx]) + tableau[idx][rightHandSideIdx] = -1.0 + tableau[idx][numberOfVariables], tableau[idx][rightHandSideIdx] = tableau[idx][rightHandSideIdx], tableau[idx][numberOfVariables] + } + tableau[numberOfRows] = make([]float64, numberOfVariables+2) + copy(tableau[numberOfRows][:numberOfVariables], objective) + tableau[numberOfRows+1] = make([]float64, numberOfVariables+2) + tableau[numberOfRows+1][numberOfVariables] = 1.0 + + pivot := func(row, column int) { + k := 1.0 / tableau[row][column] + for i := 0; i < numberOfRows+2; i++ { + if i == row { + continue + } + for j := 0; j < numberOfVariables+2; j++ { + if j != column { + tableau[i][j] -= tableau[row][j] * tableau[i][column] * k + } + } + } + for j := 0; j < numberOfVariables+2; j++ { + tableau[row][j] *= k + } + for i := 0; i < numberOfRows+2; i++ { + tableau[i][column] *= -k + } + tableau[row][column] = k + basic[row], nonBasic[column] = nonBasic[column], basic[row] + } + + findOptimalPivot := func(phase int) bool { + for { + pivotColumn := -1 + minimumValue := positiveInfinity + minimumIndex := math.MaxInt + + for idx := 0; idx <= numberOfVariables; idx++ { + if phase == 0 && nonBasic[idx] == -1 { + continue + } + value := tableau[numberOfRows+phase][idx] + if pivotColumn == -1 || value < minimumValue-epsilon || (math.Abs(value-minimumValue) <= epsilon && nonBasic[idx] < minimumIndex) { + pivotColumn = idx + minimumValue = value + minimumIndex = nonBasic[idx] + } + } + + if tableau[numberOfRows+phase][pivotColumn] > -epsilon { + return true + } + + pivotRow := -1 + minimumRatio := positiveInfinity + minimumBasicIndex := math.MaxInt + + for idx := range numberOfRows { + if tableau[idx][pivotColumn] > epsilon { + ratio := tableau[idx][rightHandSideIdx] / tableau[idx][pivotColumn] + if pivotRow == -1 || ratio < minimumRatio-epsilon || (math.Abs(ratio-minimumRatio) <= epsilon && basic[idx] < minimumBasicIndex) { + pivotRow = idx + minimumRatio = ratio + minimumBasicIndex = basic[idx] + } + } + } + + if pivotRow == -1 { + return false + } + + pivot(pivotRow, pivotColumn) + } + } + + extractSolution := func() (float64, []float64) { + solution := make([]float64, numberOfVariables) + for idx := range numberOfRows { + if basic[idx] >= 0 && basic[idx] < numberOfVariables { + solution[basic[idx]] = tableau[idx][rightHandSideIdx] + } + } + result := 0.0 + for idx := range numberOfVariables { + result += objective[idx] * solution[idx] + } + return result, solution + } + + var value float64 + var solution []float64 + + mostNegativeRow := 0 + mostNegativeValue := tableau[0][rightHandSideIdx] + for idx := 1; idx < numberOfRows; idx++ { + if tableau[idx][rightHandSideIdx] < mostNegativeValue { + mostNegativeValue = tableau[idx][rightHandSideIdx] + mostNegativeRow = idx + } + } + + if mostNegativeValue < -epsilon { + pivot(mostNegativeRow, numberOfVariables) + if !findOptimalPivot(1) || tableau[numberOfRows+1][rightHandSideIdx] < -epsilon { + value = negativeInfinity + solution = nil + } else { + for i := range numberOfRows { + if basic[i] == -1 { + column := 0 + columnValue := tableau[i][0] + columnIndex := nonBasic[0] + for j := 1; j < numberOfVariables; j++ { + if tableau[i][j] < columnValue-epsilon || (math.Abs(tableau[i][j]-columnValue) <= epsilon && nonBasic[j] < columnIndex) { + column = j + columnValue = tableau[i][j] + columnIndex = nonBasic[j] + } + } + pivot(i, column) + } + } + if findOptimalPivot(0) { + value, solution = extractSolution() + } else { + value = negativeInfinity + solution = nil + } + } + } else { + if findOptimalPivot(0) { + value, solution = extractSolution() + } else { + value = negativeInfinity + solution = nil + } + } + + if value == negativeInfinity || value >= best-epsilon { + continue + } + + fractionalIdx := -1 + var fractionalValue float64 + for idx, value := range solution { + if math.Abs(value-math.Round(value)) > epsilon { + fractionalIdx = idx + fractionalValue = value + break + } + } + + if fractionalIdx == -1 { + best = value + continue + } + + numberOfColumns := len(current[0]) + fractionalPart := fractionalValue - math.Floor(fractionalValue) + + ceilConstraint := make([]float64, numberOfColumns) + ceilConstraint[fractionalIdx] = -1.0 + ceilConstraint[numberOfColumns-1] = -math.Ceil(fractionalValue) + ceilBranch := make([][]float64, len(current)+1) + copy(ceilBranch, current) + ceilBranch[len(current)] = ceilConstraint + + floorConstraint := make([]float64, numberOfColumns) + floorConstraint[fractionalIdx] = 1.0 + floorConstraint[numberOfColumns-1] = math.Floor(fractionalValue) + floorBranch := make([][]float64, len(current)+1) + copy(floorBranch, current) + floorBranch[len(current)] = floorConstraint + + if fractionalPart > 0.5 { + stack = append(stack, ceilBranch, floorBranch) + } else { + stack = append(stack, floorBranch, ceilBranch) + } + } + + return int(math.Round(best)) +} + +func findMinimumJoltagePresses(buttons [][]int, joltages []int) int { + numberOfCounters, numberOfButtons := len(joltages), len(buttons) + numberOfRows, numberOfColumns := 2*numberOfCounters+numberOfButtons, numberOfButtons+1 + constraints := make([][]float64, numberOfRows) + for i := range constraints { + constraints[i] = make([]float64, numberOfColumns) + } + for j := range numberOfButtons { + constraints[numberOfRows-1-j][j] = -1.0 + } + for buttonIdx, button := range buttons { + for _, counterIdx := range button { + constraints[counterIdx][buttonIdx] = 1.0 + constraints[counterIdx+numberOfCounters][buttonIdx] = -1.0 + } + } + for idx, value := range joltages { + value := float64(value) + constraints[idx][numberOfColumns-1] = value + constraints[idx+numberOfCounters][numberOfColumns-1] = -value + } + objective := make([]float64, numberOfButtons) + for idx := range objective { + objective[idx] = 1.0 + } + + return solveIntegerLinearMinimization(constraints, objective) +} + func PartOne(data []string) int { total := 0 for _, line := range data { @@ -87,5 +338,16 @@ func PartOne(data []string) int { } func PartTwo(data []string) int { - return 0 + total := 0 + for _, line := range data { + _, buttons := parseMachine(line) + braceMatch := braceRegex.FindStringSubmatch(line) + parts := strings.Split(braceMatch[1], ",") + joltages := make([]int, len(parts)) + for idx, part := range parts { + joltages[idx], _ = strconv.Atoi(part) + } + total += findMinimumJoltagePresses(buttons, joltages) + } + return total }