feat: completed day 10 for advent of code

This commit is contained in:
Ceferino Patino 2025-12-21 00:41:55 -06:00
commit b29cef3adc
Signed by: c4patino
SSH key fingerprint: SHA256:9fQ9TsujGrdNNi76mnsu63v7dS5JOmHRZEqBOl49OR8
4 changed files with 218 additions and 9 deletions

View file

@ -18,18 +18,20 @@ import (
"cpatino.com/advent-of-code/2025/day07"
"cpatino.com/advent-of-code/2025/day08"
"cpatino.com/advent-of-code/2025/day09"
"cpatino.com/advent-of-code/2025/day10"
)
var days = map[int]func(string) (any, any){
1: day01.Run,
2: day02.Run,
3: day03.Run,
4: day04.Run,
5: day05.Run,
6: day06.Run,
7: day07.Run,
8: day08.Run,
9: day09.Run,
1: day01.Run,
2: day02.Run,
3: day03.Run,
4: day04.Run,
5: day05.Run,
6: day06.Run,
7: day07.Run,
8: day08.Run,
9: day09.Run,
10: day10.Run,
}
var rootCmd = &cobra.Command{

170
2025/day10/day10.go Executable file
View file

@ -0,0 +1,170 @@
package day10
import (
"bufio"
"math"
"os"
"strconv"
"strings"
)
type Combination struct {
incr []int
presses int
}
func parse(line string) (goalBits []int, buttons [][]int, requirements []int) {
fields := strings.Fields(line)
pattern := fields[0][1 : len(fields[0])-1]
for i := 0; i < len(pattern); i++ {
if pattern[i] == '#' {
goalBits = append(goalBits, 1)
} else {
goalBits = append(goalBits, 0)
}
}
for i := 1; i < len(fields)-1; i++ {
inside := fields[i][1 : len(fields[i])-1]
if inside == "" {
buttons = append(buttons, []int{})
continue
}
parts := strings.Split(inside, ",")
var idxs []int
for _, p := range parts {
n, _ := strconv.Atoi(p)
idxs = append(idxs, n)
}
buttons = append(buttons, idxs)
}
last := fields[len(fields)-1][1 : len(fields[len(fields)-1])-1]
if last != "" {
for _, p := range strings.Split(last, ",") {
n, _ := strconv.Atoi(p)
requirements = append(requirements, n)
}
}
return
}
func buildCombinations(buttons [][]int, counters int) []Combination {
n := len(buttons)
res := make([]Combination, 0, 1<<uint(n))
for mask := 0; mask < 1<<uint(n); mask++ {
inc := make([]int, counters)
press := 0
for j := range n {
if (mask>>uint(j))&1 == 1 {
press++
for _, idx := range buttons[j] {
if 0 <= idx && idx < counters {
inc[idx]++
}
}
}
}
res = append(res, Combination{inc, press})
}
return res
}
func sameParity(a, b []int) bool {
for i := range a {
if (a[i]^b[i])&1 == 1 {
return false
}
}
return true
}
func notExceed(a, b []int) bool {
for i := range a {
if a[i] > b[i] {
return false
}
}
return true
}
func isZero(v []int) bool {
for _, x := range v {
if x != 0 {
return false
}
}
return true
}
func Part1(goalBits []int, buttons [][]int) int {
counters := len(goalBits)
best := math.MaxInt
for _, cb := range buildCombinations(buttons, counters) {
if sameParity(cb.incr, goalBits) && cb.presses < best {
best = cb.presses
}
}
if best == math.MaxInt {
return 0
}
return best
}
func minPressesCounters(state []int, combs []Combination) (int, bool) {
if isZero(state) {
return 0, true
}
best := math.MaxInt
for _, cb := range combs {
if !notExceed(cb.incr, state) || !sameParity(cb.incr, state) {
continue
}
next := make([]int, len(state))
for i := range state {
next[i] = (state[i] - cb.incr[i]) / 2
}
rec, ok := minPressesCounters(next, combs)
if ok {
total := 2*rec + cb.presses
if total < best {
best = total
}
}
}
if best < math.MaxInt {
return best, true
}
return 0, false
}
func Part2(requirements []int, buttons [][]int) int {
combs := buildCombinations(buttons, len(requirements))
ans, _ := minPressesCounters(requirements, combs)
return ans
}
func Run(filename string) (any, any) {
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
a, b := 0, 0
s := bufio.NewScanner(f)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
g, btns, req := parse(line)
a += Part1(g, btns)
b += Part2(req, btns)
}
if err := s.Err(); err != nil {
panic(err)
}
return a, b
}

34
2025/day10/day10_test.go Executable file
View file

@ -0,0 +1,34 @@
package day10
import (
"os"
"testing"
)
type TestCase struct {
FileName string
Part1 any
Part2 any
}
func TestDay10(t *testing.T) {
tests := []TestCase{
{"test.txt", 7, 33},
{"input.txt", 409, 15489},
}
for _, test := range tests {
if _, err := os.Stat(test.FileName); os.IsNotExist(err) {
t.Fatalf("test file does not exist: %v", test.FileName)
}
part1, part2 := Run(test.FileName)
if test.Part1 != nil && part1 != test.Part1 {
t.Fatalf("%s: unexpected part1:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part1, part1)
}
if test.Part2 != nil && part2 != test.Part2 {
t.Fatalf("%s: unexpected part2:\nwant:\t%v\ngot:\t%v", test.FileName, test.Part2, part2)
}
}
}

3
2025/day10/test.txt Executable file
View file

@ -0,0 +1,3 @@
[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}