Advent-of-Code/year25/day10/v3.go
2025-12-23 19:27:19 +02:00

200 lines
3.8 KiB
Go

package day10
import (
"cmp"
"fmt"
"iter"
"math"
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc"
"git.bizdoc.ro/private/devkit.git/collections/combinatorics/v2"
)
func Part2V3(ctx aoc.Context) (out int, err error) {
// https://www.reddit.com/r/adventofcode/comments/1pk87hl/2025_day_10_part_2_bifurcate_your_way_to_victory/
inputLines, err := ParseInput(ctx)
if err != nil {
return 0, fmt.Errorf("parse input: %w", err)
}
for _, line := range inputLines {
parityMap := make(map[uint64][]ButtonSet)
for p := range generateButtonsCombinations(line.Buttons, len(line.Joltages)) {
phash := ParityHash(p.Parity)
parityMap[phash] = append(parityMap[phash], p)
}
var solve func([]int) (int, error)
solve = func(joltage []int) (int, error) {
if isFullOf(joltage, 0) {
return 0, nil
}
h := HashOfParityOf(joltage)
candidates, ok := parityMap[h]
if !ok {
return 0, fmt.Errorf("no candidates for parity")
}
best := math.MaxInt
for _, candidate := range candidates {
next := SubtractAndHalf(joltage, candidate.Joltages)
if !isAllGraterThan(next, 0) {
continue
}
sub, err := solve(next)
if err != nil {
continue
}
cost := candidate.ButtonsPresses + (2 * sub)
if cost < best {
best = cost
}
}
if best == math.MaxInt {
return 0, fmt.Errorf("no valid solution")
}
return best, nil
}
got, err := solve(line.Joltages)
if err != nil {
panic(err)
}
out += got
}
return
}
func isFullOf[T ~[]E, E comparable](s T, t E) bool {
for _, v := range s {
if v != t {
return false
}
}
return true
}
func isAllLessThan[T ~[]E, E cmp.Ordered](s T, t E) bool {
for _, e := range s {
if e >= t {
return false
}
}
return true
}
func isAllGraterThan[T ~[]E, E cmp.Ordered](s T, t E) bool {
for _, e := range s {
if e < t {
return false
}
}
return true
}
type ButtonSet struct {
ButtonsPresses int
Joltages []int
Parity []int
}
func SubtractAndHalf(j1, j2 []int) []int {
if len(j1) != len(j2) {
panic("j1 != j2")
}
j3 := make([]int, len(j1))
for i := range j3 {
j3[i] = j1[i] - j2[i]
if j3[i]&1 != 0 {
panic("j3[i] is not even")
}
j3[i] /= 2
}
return j3
}
func HashOfParityOf(jotages []int) uint64 {
parity := ParityOf(jotages)
return ParityHash(parity)
}
func ParityHash(parity []int) uint64 {
var k uint64
for i, isTrue := range parity {
if i > 60 {
panic("parity out of range")
}
mask := uint64(isTrue) << (len(parity) - i - 1)
k |= mask
}
return k
}
func DebugParityHashString(parity []int) string {
return fmt.Sprintf("%0*b", len(parity), ParityHash(parity))
}
func ParityOf(joltages []int) []int {
parity := make([]int, len(joltages))
for i := range joltages {
parity[i] = joltages[i] & 1
}
return parity
}
func generateButtonsCombinations(s [][]int, joltageLen int) iter.Seq[ButtonSet] {
buttons := make([]int, len(s))
for i := range buttons {
buttons[i] = i
}
return func(yield func(ButtonSet) bool) {
if !yield(ButtonSet{Joltages: make([]int, joltageLen), Parity: make([]int, joltageLen)}) {
return
}
joltages := make([]int, joltageLen)
for pattern := range generateCombinations(buttons) {
for i := range joltages {
joltages[i] = 0
}
p2 := make([]int, len(pattern))
for i := range p2 {
buttonIndex := pattern[i]
button := s[buttonIndex]
for _, b := range button {
joltages[b] += 1
}
}
parity := ParityOf(joltages)
if !yield(ButtonSet{Joltages: joltages, Parity: parity, ButtonsPresses: len(pattern)}) {
return
}
}
}
}
func generateCombinations[T ~[]E, E any](arr T) iter.Seq[T] {
return func(yield func(T) bool) {
for i := range arr {
combs := combinatorics.NewCombinator(arr, i+1)
for value := range combs.Values() {
if !yield(value) {
return
}
}
}
}
}