day 10
This commit is contained in:
parent
4177e1f4e1
commit
3645deda76
269
year25/day10/day.go
Normal file
269
year25/day10/day.go
Normal file
@ -0,0 +1,269 @@
|
||||
package day10
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc"
|
||||
"git.bizdoc.ro/private/devkit.git/collections/dsa"
|
||||
"git.bizdoc.ro/private/devkit.git/gg"
|
||||
)
|
||||
|
||||
func Part1(ctx aoc.Context) (int, error) {
|
||||
inputLines, err := ParseInput(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse input: %w", err)
|
||||
}
|
||||
sum := 0
|
||||
for _, line := range inputLines {
|
||||
if line.IndicatorLight == 0 {
|
||||
continue // zero by default
|
||||
}
|
||||
|
||||
cache2 := make([]int, 1<<line.indicatorSize)
|
||||
|
||||
type Frame struct {
|
||||
indicator uint64
|
||||
step int
|
||||
//history *dsa.ImmutableList[uint64]
|
||||
}
|
||||
|
||||
stack := dsa.NewList[Frame]()
|
||||
stack.Push(Frame{indicator: line.IndicatorLight, step: 0})
|
||||
|
||||
solution := math.MaxInt
|
||||
for !stack.IsEmpty() {
|
||||
item := stack.PopFront()
|
||||
//if item.step != item.history.Len() {
|
||||
// panic("stack overflow")
|
||||
//}
|
||||
if item.indicator == 0 {
|
||||
solution = min(solution, item.step)
|
||||
}
|
||||
if steps := cache2[item.indicator]; steps > 0 {
|
||||
if steps <= item.step {
|
||||
continue
|
||||
}
|
||||
}
|
||||
cache2[item.indicator] = item.step
|
||||
for _, button := range line.ButtonsNum {
|
||||
if item.step <= solution {
|
||||
stack.Push(Frame{
|
||||
indicator: item.indicator ^ button,
|
||||
step: item.step + 1,
|
||||
//history: dsa.NewImmutableList(button, item.history),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
sum += solution
|
||||
}
|
||||
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func compare[S ~[]E, E cmp.Ordered](s1, s2 S) (bool, bool) {
|
||||
isSolution := true
|
||||
|
||||
for i := range len(s1) {
|
||||
canContinue := s1[i] >= s2[i]
|
||||
if !canContinue {
|
||||
return false, false
|
||||
}
|
||||
|
||||
isSolution = isSolution && (s1[i] == s2[i])
|
||||
}
|
||||
return true, isSolution
|
||||
}
|
||||
|
||||
func Part2(ctx aoc.Context) (int, error) {
|
||||
// does not work
|
||||
inputLines, err := ParseInput(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse input: %w", err)
|
||||
}
|
||||
var sum int
|
||||
|
||||
for _, line := range inputLines {
|
||||
type Frame struct {
|
||||
state []byte
|
||||
step int
|
||||
history [][]int
|
||||
}
|
||||
stack := dsa.NewStack[Frame]()
|
||||
|
||||
startFrameJoltage := make([]byte, len(line.Joltages))
|
||||
for i, value := range line.Joltages {
|
||||
startFrameJoltage[i] = byte(value)
|
||||
if value > int(startFrameJoltage[i]) {
|
||||
panic("byte out of range")
|
||||
}
|
||||
}
|
||||
stack.Push(Frame{
|
||||
state: make([]byte, len(line.Joltages)),
|
||||
step: 0,
|
||||
})
|
||||
|
||||
solution := math.MaxInt
|
||||
cache := make(map[string]int)
|
||||
|
||||
for !stack.IsEmpty() {
|
||||
item := stack.Pop()
|
||||
if item.step > solution {
|
||||
continue
|
||||
}
|
||||
|
||||
key := gg.UnsafeString(item.state)
|
||||
if steps, ok := cache[key]; ok {
|
||||
if steps < item.step {
|
||||
continue
|
||||
}
|
||||
}
|
||||
cache[key] = item.step
|
||||
|
||||
canContinue, isSolution := compare(startFrameJoltage, item.state)
|
||||
if !canContinue {
|
||||
continue
|
||||
}
|
||||
if isSolution {
|
||||
//fmt.Println(item.state, item.step)
|
||||
solution = min(solution, item.step)
|
||||
//break
|
||||
}
|
||||
|
||||
for _, button := range line.Buttons {
|
||||
state := slices.Clone(item.state)
|
||||
for _, val := range button {
|
||||
state[val] += 1
|
||||
}
|
||||
stack.Push(Frame{
|
||||
state: state,
|
||||
step: item.step + 1,
|
||||
//history: append(slices.Clone(item.history), state),
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.Println(solution)
|
||||
sum += solution
|
||||
}
|
||||
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func ParseInput(ctx aoc.Context) ([]InputLine, error) {
|
||||
var inputLines []InputLine
|
||||
scanner := ctx.Scanner()
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
input, err := ParseInputLine([]rune(line))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse input: %w", err)
|
||||
}
|
||||
inputLines = append(inputLines, input)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("scanner error: %w", err)
|
||||
}
|
||||
return inputLines, nil
|
||||
}
|
||||
|
||||
type InputLine struct {
|
||||
indicatorSize int
|
||||
IndicatorLight uint64
|
||||
IndicatorLightRaw string
|
||||
Buttons [][]int
|
||||
ButtonsNum []uint64
|
||||
Joltages []int
|
||||
RawLine string
|
||||
}
|
||||
|
||||
func ParseInputLine(line []rune) (input InputLine, err error) {
|
||||
// [...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
|
||||
for i, ch := range line {
|
||||
input.RawLine = string(line)
|
||||
switch ch {
|
||||
case '[': // indicator light
|
||||
if input.IndicatorLight != 0 {
|
||||
return input, fmt.Errorf("duplicate Indicator Light in line: %s", string(line))
|
||||
}
|
||||
|
||||
size := i
|
||||
parseIndicatorLight:
|
||||
for i = i + 1; i < len(line); i++ {
|
||||
switch line[i] {
|
||||
case '#':
|
||||
input.IndicatorLight <<= 1
|
||||
input.IndicatorLight |= 1
|
||||
case ']':
|
||||
input.IndicatorLightRaw = string(line[size+1 : i])
|
||||
size = i - size - 1
|
||||
input.indicatorSize = size
|
||||
break parseIndicatorLight
|
||||
// done
|
||||
case '.':
|
||||
input.IndicatorLight <<= 1
|
||||
default:
|
||||
return input, fmt.Errorf("failed to parse Indicator Light. Unexpected character in IndicatorLight: %s", string(line))
|
||||
}
|
||||
}
|
||||
case '(':
|
||||
i += 1
|
||||
j := strings.IndexByte(string(line[i:]), ')')
|
||||
if j == -1 {
|
||||
return input, fmt.Errorf("failed to parse button in line: %s", string(line))
|
||||
}
|
||||
button := make([]int, 0)
|
||||
parts := strings.Split(string(line[i:i+j]), ",")
|
||||
for _, part := range parts {
|
||||
num, err := strconv.Atoi(part)
|
||||
if err != nil {
|
||||
return input, fmt.Errorf("failed to parse button in line: %s", string(line))
|
||||
}
|
||||
if num < 0 {
|
||||
return input, fmt.Errorf("negative button value in line: %s", string(line))
|
||||
}
|
||||
if num > input.indicatorSize {
|
||||
return input, fmt.Errorf("too many buttons in line: %s", string(line))
|
||||
}
|
||||
button = append(button, num)
|
||||
}
|
||||
input.Buttons = append(input.Buttons, button)
|
||||
i = j
|
||||
case '{':
|
||||
i += 1
|
||||
j := strings.IndexByte(string(line[i:]), '}')
|
||||
if j == -1 {
|
||||
return input, fmt.Errorf("failed to parse joltage in line: %s", string(line))
|
||||
}
|
||||
joltage := make([]int, 0)
|
||||
parts := strings.Split(string(line[i:i+j]), ",")
|
||||
for _, part := range parts {
|
||||
num, err := strconv.Atoi(part)
|
||||
if err != nil {
|
||||
return input, fmt.Errorf("failed to parse joltage in line: %s", string(line))
|
||||
}
|
||||
if num < 0 {
|
||||
return input, fmt.Errorf("negative joltage value in line: %s", string(line))
|
||||
}
|
||||
joltage = append(joltage, num)
|
||||
}
|
||||
input.Joltages = joltage
|
||||
i = j
|
||||
}
|
||||
}
|
||||
|
||||
input.ButtonsNum = make([]uint64, len(input.Buttons))
|
||||
for i, button := range input.Buttons {
|
||||
for _, v := range button {
|
||||
input.ButtonsNum[i] |= 1 << uint64(input.indicatorSize-v-1)
|
||||
}
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
28
year25/day10/day_test.go
Normal file
28
year25/day10/day_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
package day10_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/tests"
|
||||
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/year25/day10"
|
||||
)
|
||||
|
||||
func TestPart1Example(t *testing.T) {
|
||||
const want = 7
|
||||
tests.Test(t, "example.txt", tests.Handler(day10.Part1), want)
|
||||
}
|
||||
|
||||
func TestPart1(t *testing.T) {
|
||||
const want = 447
|
||||
tests.Test(t, "input.txt", tests.Handler(day10.Part1), want)
|
||||
}
|
||||
|
||||
func TestPart2Example(t *testing.T) {
|
||||
const want = 33
|
||||
tests.Test(t, "example.txt", tests.Handler(day10.Part2V3), want)
|
||||
}
|
||||
|
||||
func TestPart2(t *testing.T) {
|
||||
const want = 18960
|
||||
tests.Test(t, "input.txt", tests.Handler(day10.Part2V3), want)
|
||||
}
|
||||
199
year25/day10/v3.go
Normal file
199
year25/day10/v3.go
Normal file
@ -0,0 +1,199 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user