From ab99f807e88473a92f0ea9c4a2ee42b9cce0cbed Mon Sep 17 00:00:00 2001 From: Gabriel Bizdoc Date: Sat, 6 Dec 2025 11:32:42 +0200 Subject: [PATCH] day 5 --- aocutils/utils.go | 111 ++++++++++++++++++++++++++++++++++++ year25/day1.go | 24 ++++---- year25/day2.go | 30 ++++------ year25/day3.go | 62 +++++++++++++------- year25/day4.go | 16 +++--- year25/day5.go | 140 ++++++++++++++++++++++++++++++++++++++++++++++ year25/day5.ts | 35 ++++++++++++ year25/utils.go | 6 -- 8 files changed, 362 insertions(+), 62 deletions(-) create mode 100644 year25/day5.go create mode 100644 year25/day5.ts delete mode 100644 year25/utils.go diff --git a/aocutils/utils.go b/aocutils/utils.go index 50d5b84..2636804 100644 --- a/aocutils/utils.go +++ b/aocutils/utils.go @@ -1,8 +1,11 @@ package aocutils import ( + "bufio" "bytes" + "context" "io" + "iter" ) 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 } +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) { buff, err := io.ReadAll(r) if err != nil { @@ -25,3 +44,95 @@ func ReadString(r io.Reader) (string, error) { } 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 +} diff --git a/year25/day1.go b/year25/day1.go index f824901..9153e2e 100644 --- a/year25/day1.go +++ b/year25/day1.go @@ -6,12 +6,14 @@ import ( "io" "iter" "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 solution := 0 - for pair := range day1GetPairs(r) { + for pair := range day1GetPairs(ctx.Body) { dial += pair.MustValue() dial %= 100 if dial < 0 { @@ -20,34 +22,34 @@ func Day1Part1(r io.Reader, l Logger) (any, error) { if dial == 0 { 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 } -func Day1Part2Slow(r io.Reader, l Logger) (any, error) { +func Day1Part2Slow(ctx aocutils.Context) (any, error) { dial := 50 solution := 0 - for pair := range day1GetPairs(r) { + for pair := range day1GetPairs(ctx.Body) { nextDial, count := day1AdvanceSimulation(dial, pair.MustValue()) dial = nextDial solution += count 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 { - 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 } -func Day1Part2Fast(r io.Reader, l Logger) (any, error) { +func Day1Part2Fast(ctx aocutils.Context) (any, error) { dial := 50 solution := 0 - for pair := range day1GetPairs(r) { + for pair := range day1GetPairs(ctx.Body) { value := pair.MustValue() nextDial, count := day1Advance(dial, value) @@ -56,9 +58,9 @@ func Day1Part2Fast(r io.Reader, l Logger) (any, error) { solution += count 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 { - 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) } } diff --git a/year25/day2.go b/year25/day2.go index ce3d4dc..b5c52ee 100644 --- a/year25/day2.go +++ b/year25/day2.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "fmt" - "io" "strconv" "strings" @@ -50,8 +49,8 @@ func (s *day2Scanner) Bounds() (int, int) { return s.a, s.b } -func Day2Part1Simple(r io.Reader, l Logger) (any, error) { - input, err := aocutils.ReadString(r) +func Day2Part1Simple(ctx aocutils.Context) (any, error) { + input, err := ctx.BodyString() if err != nil { 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 } -func Day2Part2Simple(r io.Reader, l Logger) (any, error) { - input, err := aocutils.ReadString(r) +func Day2Part2Simple(ctx aocutils.Context) (any, error) { + input, err := ctx.BodyString() if err != nil { 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 } -func Day2Part1(r io.Reader, logger Logger) (any, error) { - inputScanner := bufio.NewScanner(r) - inputScanner.Split(aocutils.SplitComma) - scanner := &day2Scanner{scanner: inputScanner, sep: '-'} +func Day2Part1(ctx aocutils.Context) (any, error) { + scanner := &day2Scanner{scanner: ctx.Scanner(aocutils.SplitComma), sep: '-'} var sum int for scanner.Scan() { start, end := scanner.Bounds() - logger.Println(start, "-", end) + ctx.Println(start, "-", end) for i := start; i <= end; i++ { str := []byte(strconv.Itoa(i)) @@ -171,7 +168,7 @@ func Day2Part1(r io.Reader, logger Logger) (any, error) { firstHalf := str[:len(str)/2] secondHalf := str[len(str)/2:] if bytes.Equal(firstHalf, secondHalf) { - logger.Println("found ", i) + ctx.Println("found ", i) sum += i } } @@ -181,16 +178,13 @@ func Day2Part1(r io.Reader, logger Logger) (any, error) { return sum, scanner.Err() } -func Day2Part2(r io.Reader, logger Logger) (any, error) { - inputScanner := bufio.NewScanner(r) - inputScanner.Split(aocutils.SplitComma) - - scanner := &day2Scanner{scanner: inputScanner, sep: '-'} +func Day2Part2(ctx aocutils.Context) (any, error) { + scanner := &day2Scanner{scanner: ctx.Scanner(aocutils.SplitComma), sep: '-'} var sum int for scanner.Scan() { start, end := scanner.Bounds() - logger.Println(start, "-", end) + ctx.Println(start, "-", end) for i := start; i <= end; i++ { str := []byte(strconv.Itoa(i)) @@ -210,7 +204,7 @@ func Day2Part2(r io.Reader, logger Logger) (any, error) { } if isInvalid { - logger.Println("found ", i) + ctx.Println("found ", i) sum += i break } diff --git a/year25/day3.go b/year25/day3.go index 8462e6e..5cc8f70 100644 --- a/year25/day3.go +++ b/year25/day3.go @@ -3,23 +3,24 @@ package year25 import ( "bufio" "fmt" - "io" + "strconv" "strings" + + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aocutils" ) -func Day3Part1(r io.Reader, l Logger) (any, error) { - return Day3(r, l, 2) +func Day3Part1(ctx aocutils.Context) (any, error) { + return Day3(ctx, 2) } -func Day3Part2(r io.Reader, l Logger) (any, error) { - return Day3(r, l, 12) +func Day3Part2(ctx aocutils.Context) (any, error) { + return Day3(ctx, 12) } -func Day3(r io.Reader, logger Logger, size int) (int64, error) { - scanner := bufio.NewScanner(r) - scanner.Split(bufio.ScanLines) +func Day3(ctx aocutils.Context, size int) (int64, error) { + scanner := ctx.Scanner(bufio.ScanLines) var sum int64 findLargetDigit := func(digits []rune) (rune, int) { - largestDigit := rune(0) + var largestDigit rune index := 0 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 } } - return rune(largestDigit) - '0', index + return largestDigit, index } for scanner.Scan() { @@ -42,23 +43,46 @@ func Day3(r io.Reader, logger Logger, size int) (int64, error) { for index := range parts { digit, start := findLargetDigit(line[:len(line)+index-len(parts)+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) } else { parts[index] = byte(digit) } } - logger.Println("line: ", text) - var partSum int64 - for _, part := range parts { - partSum *= 10 - partSum += int64(part) + partSum, err := strconv.Atoi(string(parts)) + if err != nil { + return 0, fmt.Errorf("invalid result %s for line %s", string(parts), text) } - logger.Println("got: ", partSum) - logger.Println() - sum += partSum + + ctx.Println("line: ", text) + ctx.Println("got: ", string(parts)) + ctx.Println() + sum += int64(partSum) } 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 +} diff --git a/year25/day4.go b/year25/day4.go index 8d84909..6727049 100644 --- a/year25/day4.go +++ b/year25/day4.go @@ -2,21 +2,21 @@ package year25 import ( "fmt" - "io" + "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aocutils" "git.bizdoc.ro/private/devkit.git/collections/geometry/v2" ) const Day4PaperRoll = '@' -func Day4Part1(r io.Reader, l Logger) (any, error) { +func Day4Part1(ctx aocutils.Context) (any, error) { // 4 rolls in 8 adjacent positions const maxAdjacentRolls = 4 var accessibleRolls int - g, err := geometry.ReaderToByteGrid(r) + g, err := geometry.ReaderToByteGrid(ctx.Body) 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() { @@ -39,14 +39,14 @@ func Day4Part1(r io.Reader, l Logger) (any, error) { 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 const maxAdjacentRolls = 4 var totalRemovedRolls int - g, err := geometry.ReaderToByteGrid(r) + g, err := geometry.ReaderToByteGrid(ctx.Body) 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 { @@ -65,7 +65,7 @@ func Day4Part2(r io.Reader, l Logger) (any, error) { if count <= maxAdjacentRolls { removedRolls += 1 - g.Set(c, '.') // remove grid + g.Set(c, '.') // remove roll from grid } } diff --git a/year25/day5.go b/year25/day5.go new file mode 100644 index 0000000..2624bd6 --- /dev/null +++ b/year25/day5.go @@ -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 +} diff --git a/year25/day5.ts b/year25/day5.ts new file mode 100644 index 0000000..89bee7a --- /dev/null +++ b/year25/day5.ts @@ -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)] +} diff --git a/year25/utils.go b/year25/utils.go deleted file mode 100644 index a4ad4ec..0000000 --- a/year25/utils.go +++ /dev/null @@ -1,6 +0,0 @@ -package year25 - -type Logger interface { - Printf(string, ...interface{}) - Println(...interface{}) -}