Solution for day 5 star 2
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
154
day05/main.go
154
day05/main.go
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user