Compare commits
No commits in common. "d57d8e5a0a9d4c2f376cde7074eec35d54117cfc" and "4177e1f4e12eef7dc05fa5ba942d4f533136b096" have entirely different histories.
d57d8e5a0a
...
4177e1f4e1
@ -1,269 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@ -1,193 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if cost, err := solve(next); err == nil {
|
|
||||||
cost = cost*2 + candidate.ButtonsPresses
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
for pattern := range generateCombinations(buttons) {
|
|
||||||
joltages := make([]int, joltageLen)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -77,7 +77,6 @@ func parseInput(ctx aoc.Context) (input Input, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return input, fmt.Errorf("day12: failed to read input file: %w", err)
|
return input, fmt.Errorf("day12: failed to read input file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(text, "\n\n")
|
parts := strings.Split(text, "\n\n")
|
||||||
regionPart := parts[len(parts)-1]
|
regionPart := parts[len(parts)-1]
|
||||||
parts = parts[0 : len(parts)-1]
|
parts = parts[0 : len(parts)-1]
|
||||||
@ -106,8 +105,8 @@ func parseInput(ctx aoc.Context) (input Input, err error) {
|
|||||||
for _, regionStr := range strings.Split(regionPart, "\n") {
|
for _, regionStr := range strings.Split(regionPart, "\n") {
|
||||||
// 12x5: 1 0 1 0 3 2
|
// 12x5: 1 0 1 0 3 2
|
||||||
var region Region
|
var region Region
|
||||||
regionArea, presentsArea := gg.SplitUnpack(regionStr, ": ")
|
parts := strings.Split(regionStr, ": ")
|
||||||
for _, idstr := range strings.Split(presentsArea, " ") {
|
for _, idstr := range strings.Split(parts[1], " ") {
|
||||||
count, err := strconv.Atoi(idstr)
|
count, err := strconv.Atoi(idstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return input, fmt.Errorf("day12: failed to parse region count: %w", err)
|
return input, fmt.Errorf("day12: failed to parse region count: %w", err)
|
||||||
@ -115,13 +114,13 @@ func parseInput(ctx aoc.Context) (input Input, err error) {
|
|||||||
region.ShapeCounts = append(region.ShapeCounts, count)
|
region.ShapeCounts = append(region.ShapeCounts, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
rowsStr, colsStr := gg.SplitUnpack(regionArea, "x")
|
parts2 := strings.Split(parts[0], "x")
|
||||||
region.Rows, err = strconv.Atoi(rowsStr)
|
region.Rows, err = strconv.Atoi(parts2[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return input, fmt.Errorf("day12: failed to parse region rows: %w", err)
|
return input, fmt.Errorf("day12: failed to parse region rows: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
region.Cols, err = strconv.Atoi(colsStr)
|
region.Cols, err = strconv.Atoi(parts2[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return input, fmt.Errorf("day12: failed to parse region cols: %w", err)
|
return input, fmt.Errorf("day12: failed to parse region cols: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPart1Example(t *testing.T) {
|
func TestPart1Example(t *testing.T) {
|
||||||
t.Skip("solution does not work for example")
|
|
||||||
const want = 2
|
const want = 2
|
||||||
tests.Test(t, "example.txt", tests.Handler(Part1), want)
|
tests.Test(t, "example.txt", tests.Handler(Part1), want)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user