This commit is contained in:
Gabriel Bizdoc 2025-12-06 11:32:42 +02:00
parent ce0ccaaafa
commit ab99f807e8
No known key found for this signature in database
GPG Key ID: 3F0EDAECA5BE9ED9
8 changed files with 362 additions and 62 deletions

View File

@ -1,8 +1,11 @@
package aocutils package aocutils
import ( import (
"bufio"
"bytes" "bytes"
"context"
"io" "io"
"iter"
) )
func SplitComma(data []byte, atEOF bool) (advance int, token []byte, err error) { func SplitComma(data []byte, atEOF bool) (advance int, token []byte, err error) {
@ -18,6 +21,22 @@ func SplitComma(data []byte, atEOF bool) (advance int, token []byte, err error)
return 0, nil, nil return 0, nil, nil
} }
func SplitString(s string) bufio.SplitFunc {
sep := []byte(s)
return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.Index(data, sep); i >= 0 {
return i + len(sep), data[0:i], nil
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
}
func ReadString(r io.Reader) (string, error) { func ReadString(r io.Reader) (string, error) {
buff, err := io.ReadAll(r) buff, err := io.ReadAll(r)
if err != nil { if err != nil {
@ -25,3 +44,95 @@ func ReadString(r io.Reader) (string, error) {
} }
return string(buff), nil return string(buff), nil
} }
type Logger interface {
Printf(string, ...interface{})
Println(...interface{})
}
type Context struct {
Body io.Reader
Logger Logger
Context context.Context
}
func (c *Context) Scanner(split ...bufio.SplitFunc) *bufio.Scanner {
s := bufio.NewScanner(c.Body)
if len(split) == 1 {
s.Split(split[0])
}
if len(split) > 2 {
panic("too many split line handlers")
}
return s
}
func (c *Context) BodyString() (string, error) {
return ReadString(c.Body)
}
func (c *Context) BodyBytes() ([]byte, error) {
return io.ReadAll(c.Body)
}
func (c *Context) Printf(format string, args ...interface{}) {
if c.Logger != nil {
c.Logger.Printf(format, args...)
}
}
func (c *Context) Println(args ...interface{}) {
if c.Logger != nil {
c.Logger.Println(args...)
}
}
type CustomScanner[T scannable, K any] struct {
val K
err error
inner scannable
mapFunc func(T) (K, error)
}
func (s *CustomScanner[T, K]) From(t T) {
s.inner = t
}
func (s *CustomScanner[T, K]) MapFunc(f func(t T) (K, error)) {
s.mapFunc = f
}
func (s *CustomScanner[T, K]) Value() K {
return s.val
}
func (s *CustomScanner[T, K]) Values() iter.Seq[K] {
return func(yield func(K) bool) {
for s.Scan() {
if !yield(s.Value()) {
return
}
}
}
}
type scannable interface {
Scan() bool
}
func (s *CustomScanner[T, K]) Scan() bool {
if s.err != nil {
return false
}
if !s.inner.Scan() {
return false
}
s.val, s.err = s.mapFunc(s.inner.(T))
if s.err != nil {
return false
}
return true
}
func (s *CustomScanner[T, K]) Err() error {
return s.err
}

View File

@ -6,12 +6,14 @@ import (
"io" "io"
"iter" "iter"
"strconv" "strconv"
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/aocutils"
) )
func Day1Part1(r io.Reader, l Logger) (any, error) { func Day1Part1(ctx aocutils.Context) (any, error) {
dial := 50 dial := 50
solution := 0 solution := 0
for pair := range day1GetPairs(r) { for pair := range day1GetPairs(ctx.Body) {
dial += pair.MustValue() dial += pair.MustValue()
dial %= 100 dial %= 100
if dial < 0 { if dial < 0 {
@ -20,34 +22,34 @@ func Day1Part1(r io.Reader, l Logger) (any, error) {
if dial == 0 { if dial == 0 {
solution += 1 solution += 1
} }
l.Printf("The dial is rotated %s(%d) to point at 0 `once` \n", pair, pair.MustValue()) ctx.Printf("The dial is rotated %s(%d) to point at 0 `once` \n", pair, pair.MustValue())
} }
return solution, nil return solution, nil
} }
func Day1Part2Slow(r io.Reader, l Logger) (any, error) { func Day1Part2Slow(ctx aocutils.Context) (any, error) {
dial := 50 dial := 50
solution := 0 solution := 0
for pair := range day1GetPairs(r) { for pair := range day1GetPairs(ctx.Body) {
nextDial, count := day1AdvanceSimulation(dial, pair.MustValue()) nextDial, count := day1AdvanceSimulation(dial, pair.MustValue())
dial = nextDial dial = nextDial
solution += count solution += count
if count > 0 { if count > 0 {
l.Printf("The dial is rotated %s(%d) to point at %d, during this points to 0 (%d)\n", pair, pair.MustValue(), dial, count) ctx.Printf("The dial is rotated %s(%d) to point at %d, during this points to 0 (%d)\n", pair, pair.MustValue(), dial, count)
} else { } else {
l.Printf("The dial is rotated %s(%d) to point at %d\n", pair, pair.MustValue(), dial) ctx.Printf("The dial is rotated %s(%d) to point at %d\n", pair, pair.MustValue(), dial)
} }
} }
return solution, nil return solution, nil
} }
func Day1Part2Fast(r io.Reader, l Logger) (any, error) { func Day1Part2Fast(ctx aocutils.Context) (any, error) {
dial := 50 dial := 50
solution := 0 solution := 0
for pair := range day1GetPairs(r) { for pair := range day1GetPairs(ctx.Body) {
value := pair.MustValue() value := pair.MustValue()
nextDial, count := day1Advance(dial, value) nextDial, count := day1Advance(dial, value)
@ -56,9 +58,9 @@ func Day1Part2Fast(r io.Reader, l Logger) (any, error) {
solution += count solution += count
if count > 0 { if count > 0 {
l.Printf("The dial is rotated %s(%d) to point at %d, during this points to 0 (%d)\n", pair, pair.MustValue(), dial, count) ctx.Printf("The dial is rotated %s(%d) to point at %d, during this points to 0 (%d)\n", pair, pair.MustValue(), dial, count)
} else { } else {
l.Printf("The dial is rotated %s(%d) to point at %d\n", pair, pair.MustValue(), dial) ctx.Printf("The dial is rotated %s(%d) to point at %d\n", pair, pair.MustValue(), dial)
} }
} }

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"io"
"strconv" "strconv"
"strings" "strings"
@ -50,8 +49,8 @@ func (s *day2Scanner) Bounds() (int, int) {
return s.a, s.b return s.a, s.b
} }
func Day2Part1Simple(r io.Reader, l Logger) (any, error) { func Day2Part1Simple(ctx aocutils.Context) (any, error) {
input, err := aocutils.ReadString(r) input, err := ctx.BodyString()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read input: %s", err) return nil, fmt.Errorf("failed to read input: %s", err)
} }
@ -94,8 +93,8 @@ func Day2Part1Simple(r io.Reader, l Logger) (any, error) {
return sum, nil return sum, nil
} }
func Day2Part2Simple(r io.Reader, l Logger) (any, error) { func Day2Part2Simple(ctx aocutils.Context) (any, error) {
input, err := aocutils.ReadString(r) input, err := ctx.BodyString()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read input: %s", err) return nil, fmt.Errorf("failed to read input: %s", err)
} }
@ -154,16 +153,14 @@ func Day2Part2Simple(r io.Reader, l Logger) (any, error) {
return sum, nil return sum, nil
} }
func Day2Part1(r io.Reader, logger Logger) (any, error) { func Day2Part1(ctx aocutils.Context) (any, error) {
inputScanner := bufio.NewScanner(r) scanner := &day2Scanner{scanner: ctx.Scanner(aocutils.SplitComma), sep: '-'}
inputScanner.Split(aocutils.SplitComma)
scanner := &day2Scanner{scanner: inputScanner, sep: '-'}
var sum int var sum int
for scanner.Scan() { for scanner.Scan() {
start, end := scanner.Bounds() start, end := scanner.Bounds()
logger.Println(start, "-", end) ctx.Println(start, "-", end)
for i := start; i <= end; i++ { for i := start; i <= end; i++ {
str := []byte(strconv.Itoa(i)) str := []byte(strconv.Itoa(i))
@ -171,7 +168,7 @@ func Day2Part1(r io.Reader, logger Logger) (any, error) {
firstHalf := str[:len(str)/2] firstHalf := str[:len(str)/2]
secondHalf := str[len(str)/2:] secondHalf := str[len(str)/2:]
if bytes.Equal(firstHalf, secondHalf) { if bytes.Equal(firstHalf, secondHalf) {
logger.Println("found ", i) ctx.Println("found ", i)
sum += i sum += i
} }
} }
@ -181,16 +178,13 @@ func Day2Part1(r io.Reader, logger Logger) (any, error) {
return sum, scanner.Err() return sum, scanner.Err()
} }
func Day2Part2(r io.Reader, logger Logger) (any, error) { func Day2Part2(ctx aocutils.Context) (any, error) {
inputScanner := bufio.NewScanner(r) scanner := &day2Scanner{scanner: ctx.Scanner(aocutils.SplitComma), sep: '-'}
inputScanner.Split(aocutils.SplitComma)
scanner := &day2Scanner{scanner: inputScanner, sep: '-'}
var sum int var sum int
for scanner.Scan() { for scanner.Scan() {
start, end := scanner.Bounds() start, end := scanner.Bounds()
logger.Println(start, "-", end) ctx.Println(start, "-", end)
for i := start; i <= end; i++ { for i := start; i <= end; i++ {
str := []byte(strconv.Itoa(i)) str := []byte(strconv.Itoa(i))
@ -210,7 +204,7 @@ func Day2Part2(r io.Reader, logger Logger) (any, error) {
} }
if isInvalid { if isInvalid {
logger.Println("found ", i) ctx.Println("found ", i)
sum += i sum += i
break break
} }

View File

@ -3,23 +3,24 @@ package year25
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io" "strconv"
"strings" "strings"
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/aocutils"
) )
func Day3Part1(r io.Reader, l Logger) (any, error) { func Day3Part1(ctx aocutils.Context) (any, error) {
return Day3(r, l, 2) return Day3(ctx, 2)
} }
func Day3Part2(r io.Reader, l Logger) (any, error) { func Day3Part2(ctx aocutils.Context) (any, error) {
return Day3(r, l, 12) return Day3(ctx, 12)
} }
func Day3(r io.Reader, logger Logger, size int) (int64, error) { func Day3(ctx aocutils.Context, size int) (int64, error) {
scanner := bufio.NewScanner(r) scanner := ctx.Scanner(bufio.ScanLines)
scanner.Split(bufio.ScanLines)
var sum int64 var sum int64
findLargetDigit := func(digits []rune) (rune, int) { findLargetDigit := func(digits []rune) (rune, int) {
largestDigit := rune(0) var largestDigit rune
index := 0 index := 0
for i := len(digits) - 1; i >= 0; i-- { for i := len(digits) - 1; i >= 0; i-- {
@ -28,7 +29,7 @@ func Day3(r io.Reader, logger Logger, size int) (int64, error) {
index = i index = i
} }
} }
return rune(largestDigit) - '0', index return largestDigit, index
} }
for scanner.Scan() { for scanner.Scan() {
@ -42,23 +43,46 @@ func Day3(r io.Reader, logger Logger, size int) (int64, error) {
for index := range parts { for index := range parts {
digit, start := findLargetDigit(line[:len(line)+index-len(parts)+1]) digit, start := findLargetDigit(line[:len(line)+index-len(parts)+1])
line = line[start+1:] line = line[start+1:]
if digit < 0 || digit > 9 { if digit < '0' || digit > '9' {
return 0, fmt.Errorf("ivalid char '%c' in line %s", digit, text) return 0, fmt.Errorf("ivalid char '%c' in line %s", digit, text)
} else { } else {
parts[index] = byte(digit) parts[index] = byte(digit)
} }
} }
logger.Println("line: ", text)
var partSum int64 partSum, err := strconv.Atoi(string(parts))
for _, part := range parts { if err != nil {
partSum *= 10 return 0, fmt.Errorf("invalid result %s for line %s", string(parts), text)
partSum += int64(part)
} }
logger.Println("got: ", partSum)
logger.Println() ctx.Println("line: ", text)
sum += partSum ctx.Println("got: ", string(parts))
ctx.Println()
sum += int64(partSum)
} }
return sum, scanner.Err() return sum, scanner.Err()
} }
func Day3Compact(input string, size int) (total int64, _ error) {
findLargetDigit := func(digits string, skip int) (digit byte, idx int, next string) {
for i := len(digits) - 1 - skip; i >= 0; i-- {
if digits[i] >= digit {
digit, idx = digits[i], i
}
}
return digit, idx, digits[idx+1:]
}
for _, line := range strings.Split(input, "\n") {
var parts = make([]byte, size)
for index := range size {
parts[index], _, line = findLargetDigit(line, size-1-index)
}
if partSum, err := strconv.ParseInt(string(parts), 10, 64); err != nil {
return 0, fmt.Errorf("invalid result %s", string(parts))
} else {
total += partSum
}
}
return total, nil
}

View File

@ -2,21 +2,21 @@ package year25
import ( import (
"fmt" "fmt"
"io"
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/aocutils"
"git.bizdoc.ro/private/devkit.git/collections/geometry/v2" "git.bizdoc.ro/private/devkit.git/collections/geometry/v2"
) )
const Day4PaperRoll = '@' const Day4PaperRoll = '@'
func Day4Part1(r io.Reader, l Logger) (any, error) { func Day4Part1(ctx aocutils.Context) (any, error) {
// 4 rolls in 8 adjacent positions // 4 rolls in 8 adjacent positions
const maxAdjacentRolls = 4 const maxAdjacentRolls = 4
var accessibleRolls int var accessibleRolls int
g, err := geometry.ReaderToByteGrid(r) g, err := geometry.ReaderToByteGrid(ctx.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("day4: failed to read grid: %w", err) return nil, fmt.Errorf("day4 part1: failed to read grid: %w", err)
} }
for c := range g.Points() { for c := range g.Points() {
@ -39,14 +39,14 @@ func Day4Part1(r io.Reader, l Logger) (any, error) {
return accessibleRolls, nil return accessibleRolls, nil
} }
func Day4Part2(r io.Reader, l Logger) (any, error) { func Day4Part2(ctx aocutils.Context) (any, error) {
// 4 rolls in 8 adjacent positions // 4 rolls in 8 adjacent positions
const maxAdjacentRolls = 4 const maxAdjacentRolls = 4
var totalRemovedRolls int var totalRemovedRolls int
g, err := geometry.ReaderToByteGrid(r) g, err := geometry.ReaderToByteGrid(ctx.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("day4: failed to read grid: %w", err) return nil, fmt.Errorf("day4 part2: failed to read grid: %w", err)
} }
for { for {
@ -65,7 +65,7 @@ func Day4Part2(r io.Reader, l Logger) (any, error) {
if count <= maxAdjacentRolls { if count <= maxAdjacentRolls {
removedRolls += 1 removedRolls += 1
g.Set(c, '.') // remove grid g.Set(c, '.') // remove roll from grid
} }
} }

140
year25/day5.go Normal file
View File

@ -0,0 +1,140 @@
package year25
import (
"bufio"
"fmt"
"iter"
"slices"
"strconv"
"strings"
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/aocutils"
)
type Day5Interval struct {
Start, End int64
isRemoved bool
}
type MergedIntervals struct {
intervals []*Day5Interval
}
func (m *MergedIntervals) Put(newInterval Day5Interval) {
for item := range m.Items() {
if newInterval.Start <= item.End && newInterval.End >= item.Start {
newInterval.Start = min(newInterval.Start, item.Start)
newInterval.End = max(newInterval.End, item.End)
item.isRemoved = true
m.Put(newInterval)
return
}
}
// just in case there are too many intervals
m.intervals = slices.DeleteFunc(m.intervals, func(i *Day5Interval) bool {
return i.isRemoved
})
m.intervals = append(m.intervals, &newInterval)
}
func (m *MergedIntervals) Put2(newInterval Day5Interval) {
// fast search start, end
for item := range m.Items() {
if newInterval.Start <= item.End && newInterval.End >= item.Start {
newInterval.Start = min(newInterval.Start, item.Start)
newInterval.End = max(newInterval.End, item.End)
item.isRemoved = true
m.Put(newInterval)
return
}
}
// insert in order
m.intervals = append(m.intervals, &newInterval)
}
func (m *MergedIntervals) CheckItem(itemId int64) (*Day5Interval, bool) {
for interval := range m.Items() {
if interval.Start <= itemId && itemId <= interval.End {
return interval, true
}
}
return nil, false
}
func (m *MergedIntervals) Items() iter.Seq[*Day5Interval] {
return func(yield func(*Day5Interval) bool) {
for _, interval := range m.intervals {
if !interval.isRemoved {
if !yield(interval) {
return
}
}
}
}
}
func Day5ParseIntervals(s *bufio.Scanner) (intervals *MergedIntervals, err error) {
intervals = new(MergedIntervals)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" {
return
}
var newInterval Day5Interval
dashIndex := strings.Index(line, "-")
newInterval.Start, err = strconv.ParseInt(line[:dashIndex], 10, 64)
if err != nil {
return intervals, fmt.Errorf("day5: failed to parse line %s. invalid start index: %w", line, err)
}
newInterval.End, err = strconv.ParseInt(line[dashIndex+1:], 10, 64)
if err != nil {
return intervals, fmt.Errorf("day5: failed to parse line %s. invalid end index: %w", line, err)
}
intervals.Put(newInterval)
}
return intervals, s.Err()
}
func Day5Part1(ctx aocutils.Context) (int, error) {
scanner := ctx.Scanner()
intervals, err := Day5ParseIntervals(scanner)
if err != nil {
return 0, fmt.Errorf("day5: failed to parse intervals %w", err)
}
var freshIngredients int
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
itemId, err := strconv.ParseInt(line, 10, 64)
if err != nil {
return 0, fmt.Errorf("day5: failed to parse itemId in line %s. %w", line, err)
}
if interval, ok := intervals.CheckItem(itemId); ok {
freshIngredients += 1
ctx.Println(itemId, "is fresh because it was found in", interval)
} else {
ctx.Println(itemId, "is spoiled")
}
}
return freshIngredients, scanner.Err()
}
func Day5Part2(ctx aocutils.Context) (solution int64, _ error) {
intervals, err := Day5ParseIntervals(ctx.Scanner())
if err != nil {
return 0, fmt.Errorf("day5: failed to parse intervals %w", err)
}
for interval := range intervals.Items() {
solution += interval.End - interval.Start + 1
}
return solution, nil
}

35
year25/day5.ts Normal file
View File

@ -0,0 +1,35 @@
const input = ""
console.log(part1(input))
console.log(part2(input))
type Item = [number, number]
const [start, end] = [0, 1]
function part1(input: string): number {
const [db, ids] = parseInput(input)
return ids.filter(currentId => {
return db.find(id => id[start] <= currentId && id[end] >= currentId)
}).length
}
function part2(input: string): number {
const [db, _] = parseInput(input)
return db.reduce((sum, i) => sum + (i[end] - i[start] + 1), 0)
}
function parseInput(input: string): [Item[], number[]] {
const [dbLines, itemsLines] = input.trim().split('\n\n')
.map(n => n.trim().split('\n'))
const db = dbLines.map(line => line.split('-').map(n => parseInt(n, 10)) as Item)
.reduce((db: Item[], newItem: Item) => {
const mergedInterval = db
.filter(item => newItem[start] <= item[end] && newItem[end] >= item[start])
.reduce((acc, item) => [Math.min(acc[start], item[start]), Math.max(acc[end], item[end])], newItem)
const remaining = db.filter(item => (item[end] < newItem[start] || item[start] > newItem[end]))
return [...remaining, mergedInterval] as Item[]
}, [] as Item[])
return [db, itemsLines.map(Number)]
}

View File

@ -1,6 +0,0 @@
package year25
type Logger interface {
Printf(string, ...interface{})
Println(...interface{})
}