251 lines
4.5 KiB
Go
251 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/draffensperger/golp"
|
|
|
|
h "git.bullercodeworks.com/brian/adventofcode/helpers"
|
|
)
|
|
|
|
func main() {
|
|
inp := h.StdinToStringSlice()
|
|
part1(inp)
|
|
fmt.Println()
|
|
part2(inp)
|
|
}
|
|
|
|
func part1(inp []string) {
|
|
machines := parseInput(inp)
|
|
var ret int
|
|
for _, m := range machines {
|
|
r := m.Start()
|
|
ret += r
|
|
}
|
|
|
|
fmt.Println("# Part 1")
|
|
fmt.Println(ret)
|
|
}
|
|
|
|
func part2(inp []string) {
|
|
machines := parseInput(inp)
|
|
var ret int
|
|
var wg sync.WaitGroup
|
|
for _, m := range machines {
|
|
wg.Go(func() {
|
|
sol := m.SolveJoltage()
|
|
ret += sol
|
|
})
|
|
}
|
|
wg.Wait()
|
|
fmt.Println("# Part 2")
|
|
fmt.Println(ret)
|
|
}
|
|
|
|
func parseInput(inp []string) []*Machine {
|
|
var ret []*Machine
|
|
for i := range inp {
|
|
ret = append(ret, NewMachine(i, inp[i]))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
type Machine struct {
|
|
id int
|
|
numLights int
|
|
lights int
|
|
wLights int
|
|
buttons [][]int
|
|
joltages []int
|
|
jCounters []int
|
|
}
|
|
|
|
func NewMachine(id int, inp string) *Machine {
|
|
ret := Machine{id: id}
|
|
pts := strings.Fields(inp)
|
|
for _, p := range pts {
|
|
in := p[1 : len(p)-1]
|
|
switch p[0] {
|
|
case '[':
|
|
var l []int
|
|
for i := range in {
|
|
if in[i] == '#' {
|
|
l = append(l, i)
|
|
}
|
|
}
|
|
ret.wLights = intsToBitmask(l)
|
|
ret.numLights = len(in)
|
|
case '(':
|
|
wP := strings.Split(in, ",")
|
|
var w []int
|
|
for i := range wP {
|
|
w = append(w, h.Atoi(wP[i]))
|
|
}
|
|
ret.buttons = append(ret.buttons, w)
|
|
case '{':
|
|
wJ := strings.Split(in, ",")
|
|
for i := range wJ {
|
|
ret.joltages = append(ret.joltages, h.Atoi(wJ[i]))
|
|
ret.jCounters = append(ret.jCounters, 0)
|
|
}
|
|
}
|
|
}
|
|
return &ret
|
|
}
|
|
|
|
// Start presses buttons until the machine is running
|
|
func (m *Machine) Start() int {
|
|
bMasks := []int{}
|
|
for _, b := range m.buttons {
|
|
bMasks = append(bMasks, intsToBitmask(b))
|
|
}
|
|
// Start at the end
|
|
endMask := 0
|
|
current := map[int]bool{m.wLights: true}
|
|
var ret int
|
|
for p := 1; p <= 1000; p++ {
|
|
nextSet := make(map[int]bool)
|
|
for c := range current {
|
|
for _, b := range bMasks {
|
|
nextSet[c^b] = true
|
|
}
|
|
}
|
|
current = nextSet
|
|
if _, exists := current[endMask]; exists {
|
|
return p
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *Machine) SolveJoltage() int {
|
|
if slices.Equal(m.jCounters, m.joltages) {
|
|
return 0
|
|
}
|
|
|
|
nB := len(m.buttons)
|
|
nJ := len(m.joltages)
|
|
|
|
lp := golp.NewLP(0, nB)
|
|
lp.SetVerboseLevel(golp.NEUTRAL)
|
|
|
|
objCoeffs := make([]float64, nB)
|
|
for i := range nB {
|
|
objCoeffs[i] = 1.0
|
|
}
|
|
lp.SetObjFn(objCoeffs)
|
|
|
|
for i := range nB {
|
|
lp.SetInt(i, true)
|
|
lp.SetBounds(i, 0.0, float64(1000))
|
|
}
|
|
for i := 0; i < nJ; i++ {
|
|
var entries []golp.Entry
|
|
for j, btn := range m.buttons {
|
|
if slices.Contains(btn, i) {
|
|
entries = append(entries, golp.Entry{Col: j, Val: 1.0})
|
|
}
|
|
}
|
|
targetValue := float64(m.joltages[i])
|
|
if err := lp.AddConstraintSparse(entries, golp.EQ, targetValue); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
status := lp.Solve()
|
|
if status != golp.OPTIMAL {
|
|
return 0
|
|
}
|
|
|
|
solution := lp.Variables()
|
|
totalPresses := 0
|
|
for _, val := range solution {
|
|
totalPresses += int(val + 0.5)
|
|
}
|
|
return totalPresses
|
|
}
|
|
|
|
func (m *Machine) Reset() { m.lights = 0 }
|
|
|
|
func (m Machine) String() string {
|
|
l := bitmaskToBytes(m.lights, m.numLights)
|
|
wl := bitmaskToBytes(m.wLights, m.numLights)
|
|
return fmt.Sprintf("[%s] -> [%s] %d", string(l), string(wl), m.wLights)
|
|
}
|
|
|
|
func intsToBitmask(i []int) int {
|
|
mask := 0
|
|
for _, v := range i {
|
|
mask |= 1 << v
|
|
}
|
|
return mask
|
|
}
|
|
func bitmaskToInts(in int, length int) []int {
|
|
var ret []int
|
|
for i := 0; i < length; i++ {
|
|
if in&1 == 1 {
|
|
ret = append(ret, i)
|
|
}
|
|
in = in >> 1
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func bitmaskToBytes(in int, length int) []byte {
|
|
var ret []byte
|
|
for i := 1; i <= length; i++ {
|
|
if in&1 == 1 {
|
|
ret = append(ret, '#')
|
|
} else {
|
|
ret = append(ret, '.')
|
|
}
|
|
in = in >> 1
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func elim(mtx [][]int) ([]int, [][]int) {
|
|
m := len(mtx)
|
|
if m == 0 {
|
|
return nil, nil
|
|
}
|
|
n := len(mtx[0]) - 1
|
|
pivCols := []int{}
|
|
cRow := 0
|
|
mat := make([][]int, m)
|
|
for i := range mtx {
|
|
mat[i] = make([]int, n+1)
|
|
copy(mat[i], mtx[i])
|
|
}
|
|
for col := 0; col < n && cRow < m; col++ {
|
|
pivRow := -1
|
|
for row := cRow; row < m; row++ {
|
|
if mat[row][col] != 0 {
|
|
pivRow = row
|
|
break
|
|
}
|
|
}
|
|
if pivRow == -1 {
|
|
continue
|
|
}
|
|
|
|
mat[cRow], mat[pivRow] = mat[pivRow], mat[cRow]
|
|
pivCols = append(pivCols, col)
|
|
|
|
for row := cRow + 1; row < m; row++ {
|
|
if mat[row][col] != 0 {
|
|
factor := mat[row][col]
|
|
pivVal := mat[cRow][col]
|
|
for j := col; j <= n; j++ {
|
|
mat[row][j] = mat[row][j]*pivVal - mat[cRow][j]*factor
|
|
}
|
|
}
|
|
}
|
|
cRow++
|
|
}
|
|
return pivCols, mat
|
|
}
|