package year25 import ( "bufio" "cmp" "fmt" "iter" "slices" "strconv" "strings" "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc" ) 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 }