diff --git a/year25/day10/day.go b/year25/day10/day.go new file mode 100644 index 0000000..61c7a9d --- /dev/null +++ b/year25/day10/day.go @@ -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< 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 +} diff --git a/year25/day10/day_test.go b/year25/day10/day_test.go new file mode 100644 index 0000000..21bfe5d --- /dev/null +++ b/year25/day10/day_test.go @@ -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) +} diff --git a/year25/day10/v3.go b/year25/day10/v3.go new file mode 100644 index 0000000..813bab6 --- /dev/null +++ b/year25/day10/v3.go @@ -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 + } + } + } + } +}