Solution for day 5 star 2

This commit is contained in:
Nic
2025-12-07 00:05:24 +01:00
parent 0c1ef467ca
commit ad588f8b9c
3 changed files with 167 additions and 18 deletions

View File

@@ -22,3 +22,24 @@ func (i *Interval) In(val int) bool {
## Star 2
- intervals need to be merged
- to do that efficiently, they need to be sorted
- then, a sequence of intervals can be merged:
```
100596811663215 - 100867276610455
100596811663215 - 101195447377813
100596811663215 - 101195447397536
100596811663215 - 101195447397536
```
- start of first and end of last
- iterate over list of intervals
- find furthest matching start
- copy new merged interval into separate list
### Attempts
- 323939472567605: too low
- 422408896659291: too high

View File

@@ -1,14 +1,24 @@
package main
import "fmt"
// Interval stores the start (From) and end (To) of a range
type Interval struct {
From int
To int
}
// Contains checks whether a given value is contained in the interval
func (i *Interval) Contains(val int) bool {
return i.From <= val && val <= i.To
}
// Count returns the number of values within the interval
func (i *Interval) Count() int {
return i.To - i.From + 1
}
// String returns the conventional representation of the interval
func (i *Interval) String() string {
return fmt.Sprintf("%d-%d", i.From, i.To)
}

View File

@@ -4,14 +4,90 @@ import (
"fmt"
"log"
"os"
"slices"
"strconv"
"strings"
)
func countJob(intervals <-chan Interval, results chan<- int) {
for i := range intervals {
results <- i.Count()
// mergeIntervals sequentially merges Intervals for a given sorted slice of
// Intervals. Intervals with the same start will be merged like this first:
//
// 12-15
// 12-18 => 12-18
//
// Then, Intervals that are directly succeeding and Intervals that are
// fully included by the leading one will be merged as well:
//
// 12-15
// 16-18 => 12-18
//
// 10-17
// 12-15 => 10-17
//
// After merging the Intervals, their range lengths can be summed up
// without ids being counted multiple times, since all Intervals
// are fully disjunct afterwards.
func mergeIntervals(intervals []Interval) []Interval {
merged := []Interval{}
// phase 1: merge all intervals with same start together,
// using the greatest end as new end
for i, interval := range intervals {
// iteratively try to come as far as possible
for j := i + 1; j <= len(intervals); j++ {
// last interval has no next one left
if j == len(intervals) {
merged = append(merged, interval)
continue
}
next := intervals[j]
// check if end has been reached
if next.From != interval.From {
merged = append(merged, interval)
}
break
}
}
// phase 2: merge succeeding intervals and sub-intervals together
for i := 0; ; i++ {
if i == len(merged) {
break
}
current := merged[i]
for j := i + 1; j <= len(merged); j++ {
if j == len(merged) {
continue
}
next := merged[j]
// next interval is fully included in current interval,
// so next can be dropped
if current.From < next.From && current.To > next.To {
merged = append(merged[:j], merged[j+1:]...)
i = -1
continue
}
// next interval is either overlapping or directly succeeding
// the current interval, so they can be merged together
// into current interval, and next can be dropped
if current.To >= next.From || current.To+1 == next.From {
merged[i].To = next.To
merged = append(merged[:j], merged[j+1:]...)
i = -1
continue
}
}
}
return merged
}
// readIds parses an id input string into a slice of ids (integers)
@@ -24,6 +100,8 @@ func readIds(input string) []int {
ids = append(ids, id)
}
sortIds(ids)
return ids
}
@@ -57,16 +135,55 @@ func readIntervals(input string) []Interval {
intervals = append(intervals, Interval{from, to})
}
sortIntervals(intervals)
return intervals
}
// sortIds sorts a slice of ids ascending.
func sortIds(ids []int) {
slices.SortFunc(ids, func(a, b int) int {
if a < b {
return -1
} else if a > b {
return 1
}
return 0
})
}
// sortIntervals sorts a slice of intervals ascending.
func sortIntervals(intervals []Interval) {
slices.SortFunc(intervals, func(a, b Interval) int {
if a.From < b.From {
return -1
} else if a.From > b.From {
return 1
}
// both ends are the same, so compare starts
if a.To < b.To {
return -1
} else if a.To > b.To {
return 1
}
// they are the same
return 0
})
}
// Level1 solves the riddle for the first star
func Level1(intervals *[]Interval, ids *[]int) int {
// count fresh ids
count := 0
// for each id
for _, id := range *ids {
// for each interval
for _, interval := range *intervals {
// if id lies in interval, count that interval and move on
// with the next id to avoid multiple counts
if interval.Contains(id) {
count++
break
@@ -79,27 +196,28 @@ func Level1(intervals *[]Interval, ids *[]int) int {
// Level2 solves the riddle for the second star
func Level2(intervals *[]Interval) int {
intervalChan := make(chan Interval)
resultsChan := make(chan int)
for _, i := range *intervals {
go countJob(intervalChan, resultsChan)
intervalChan <- i
}
close(intervalChan)
sum := 0
for range len(*intervals) {
sum += <-resultsChan
// merge intervals
merged := mergeIntervals(*intervals)
// sum up interval counts
for _, i := range merged {
sum += i.Count()
}
return sum
}
func main() {
// use argument as file path, if provided, or default to input.txt
filePath := "input.txt"
if len(os.Args) > 1 {
filePath = os.Args[1]
}
// read inputs
intervalInput, idInput := readInputs("input.txt")
intervalInput, idInput := readInputs(filePath)
intervals := readIntervals(intervalInput)
ids := readIds(idInput)
@@ -107,5 +225,5 @@ func main() {
fmt.Println("Level 1:", Level1(&intervals, &ids))
// Level 2
// fmt.Println("Level 2:", Level2(&intervals))
fmt.Println("Level 2:", Level2(&intervals))
}