feat: D10P2 solution

This commit is contained in:
2026-01-30 20:49:06 +01:00
parent 80f191da7e
commit 106113023b

View File

@@ -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
}