200 lines
3.8 KiB
Go
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|