Advent-of-Code/year25/day5.go

148 lines
3.6 KiB
Go

package year25
import (
"bufio"
"cmp"
"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) Put1(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) Put(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
}
}
m.intervals = slices.DeleteFunc(m.intervals, func(i *Day5Interval) bool {
return i.isRemoved
})
i, _ := slices.BinarySearchFunc(m.intervals, &newInterval, func(interval *Day5Interval, d *Day5Interval) int {
return cmp.Compare(interval.Start, newInterval.Start)
})
m.intervals = slices.Insert(m.intervals, i, &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 aoc.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 aoc.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
}