235 lines
4.4 KiB
Go
235 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
aoc "go.sour.is/advent-of-code"
|
|
)
|
|
|
|
func main() { aoc.MustResult(aoc.Runner(run)) }
|
|
|
|
type result struct {
|
|
minLocation int
|
|
minRange int
|
|
}
|
|
|
|
func run(scan *bufio.Scanner) (result, error) {
|
|
log("begin...")
|
|
|
|
var seeds []int
|
|
var seedRanges [][2]int
|
|
lookup := map[string]*Lookup{}
|
|
|
|
for scan.Scan() {
|
|
text := scan.Text()
|
|
if strings.HasPrefix(text, "seeds:") && len(seeds) == 0 {
|
|
seeds, seedRanges = readSeeds(text)
|
|
log("seeds", len(seeds), "ranges", len(seedRanges))
|
|
}
|
|
|
|
lookup = readMaps(scan)
|
|
log("lookups", len(lookup))
|
|
}
|
|
|
|
find := NewFinder(
|
|
lookup["seed-to-soil"],
|
|
lookup["soil-to-fertilizer"],
|
|
lookup["fertilizer-to-water"],
|
|
lookup["water-to-light"],
|
|
lookup["light-to-temperature"],
|
|
lookup["temperature-to-humidity"],
|
|
lookup["humidity-to-location"],
|
|
)
|
|
|
|
return result{findMinLocation(seeds, find), FindMinRangeLocationMulti(seedRanges, find)}, nil
|
|
}
|
|
|
|
func readSeeds(text string) ([]int, [][2]int) {
|
|
var seeds []int
|
|
var seedRanges [][2]int
|
|
|
|
for i, n := range aoc.SliceMap(aoc.Atoi, strings.Fields(strings.TrimPrefix(text, "seeds: "))...) {
|
|
seeds = append(seeds, n)
|
|
|
|
if i%2 == 0 {
|
|
seedRanges = append(seedRanges, [2]int{n, 0})
|
|
} else {
|
|
seedRanges[len(seedRanges)-1][1] = n
|
|
}
|
|
}
|
|
return seeds, seedRanges
|
|
}
|
|
|
|
func readMaps(scan *bufio.Scanner) map[string]*Lookup {
|
|
var cur *Lookup
|
|
lookup := make(map[string]*Lookup)
|
|
for scan.Scan() {
|
|
text := scan.Text()
|
|
|
|
if strings.HasSuffix(text, "map:") {
|
|
if cur != nil {
|
|
cur.Sort()
|
|
}
|
|
cur = &Lookup{}
|
|
title := strings.TrimSuffix(text, " map:")
|
|
lookup[title] = cur
|
|
}
|
|
|
|
numbers := strings.Fields(text)
|
|
if len(numbers) == 3 {
|
|
rng := make([]int, 3)
|
|
for i, s := range numbers {
|
|
n, _ := strconv.Atoi(s)
|
|
rng[i] = n
|
|
}
|
|
cur.Add(rng[1], rng[0], rng[2])
|
|
}
|
|
}
|
|
return lookup
|
|
}
|
|
|
|
func findMinLocation(seeds []int, find *Finder) int {
|
|
seedLocations := make([]int, len(seeds))
|
|
for i, s := range seeds {
|
|
seedLocations[i] = find.Find(s)
|
|
}
|
|
return min(seedLocations...)
|
|
}
|
|
|
|
func FindMinRangeLocation(ranges [][2]int, find *Finder) int {
|
|
results := 0
|
|
for _, r := range ranges {
|
|
results += r[1]
|
|
}
|
|
|
|
seedLocations := make([]int, 0, results)
|
|
|
|
for _, s := range ranges {
|
|
for i := 0; i < s[1]; i++ {
|
|
seedLocations = append(seedLocations, find.Find(s[0]+i))
|
|
}
|
|
}
|
|
return min(seedLocations...)
|
|
}
|
|
|
|
func FindMinRangeLocationMulti(ranges [][2]int, find *Finder) int {
|
|
worker := func(id int, jobs <-chan [2]int, results chan<- []int) {
|
|
for s := range jobs {
|
|
res := make([]int, s[1])
|
|
for i := 0; i < s[1]; i++ {
|
|
res[i] = find.Find(s[0] + i)
|
|
}
|
|
results <- res
|
|
}
|
|
}
|
|
|
|
numWorkers := 16
|
|
jobsCh := make(chan [2]int, numWorkers)
|
|
resultsCh := make(chan []int, len(ranges))
|
|
|
|
for w := 0; w < numWorkers; w++ {
|
|
go worker(w, jobsCh, resultsCh)
|
|
}
|
|
log("started workers", numWorkers)
|
|
|
|
go func() {
|
|
for i, s := range ranges {
|
|
log("job", i, "send", s)
|
|
jobsCh <- s
|
|
}
|
|
close(jobsCh)
|
|
}()
|
|
|
|
results := 0
|
|
for _, r := range ranges {
|
|
results += r[1]
|
|
}
|
|
log("expecting results", results)
|
|
|
|
seedLocations := make([]int, 0, results)
|
|
expectResults := make([]struct{}, len(ranges))
|
|
for range expectResults {
|
|
r := <-resultsCh
|
|
seedLocations = append(seedLocations, r...)
|
|
}
|
|
|
|
return min(seedLocations...)
|
|
}
|
|
|
|
type Range struct {
|
|
src int
|
|
dest int
|
|
len int
|
|
}
|
|
type Ranges []Range
|
|
|
|
func (r Ranges) Len() int { return len(r) }
|
|
func (r Ranges) Less(i, j int) bool { return r[i].src < r[j].src }
|
|
func (r Ranges) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
|
|
type Lookup struct {
|
|
ranges Ranges
|
|
}
|
|
|
|
func (l *Lookup) Add(src, dest, len int) {
|
|
if l == nil {
|
|
return
|
|
}
|
|
l.ranges = append(l.ranges, Range{src, dest, len})
|
|
}
|
|
func (l *Lookup) Sort() {
|
|
sort.Sort(sort.Reverse(l.ranges))
|
|
}
|
|
func (l *Lookup) Find(n int) int {
|
|
for _, r := range l.ranges {
|
|
if n >= r.src && n <= r.src+r.len {
|
|
diff := n - r.src
|
|
if diff < r.len {
|
|
return r.dest + diff
|
|
}
|
|
}
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
type Finder struct {
|
|
stack []*Lookup
|
|
}
|
|
|
|
func NewFinder(stack ...*Lookup) *Finder {
|
|
return &Finder{stack: stack}
|
|
}
|
|
func (f *Finder) Find(n int) int {
|
|
// fmt.Print("Find: ")
|
|
for _, l := range f.stack {
|
|
// fmt.Print(n, "->")
|
|
n = l.Find(n)
|
|
// fmt.Print(n, " ")
|
|
}
|
|
// fmt.Println("")
|
|
return n
|
|
}
|
|
|
|
func min(arr ...int) int {
|
|
if len(arr) == 0 {
|
|
return 0
|
|
}
|
|
m := arr[0]
|
|
for _, a := range arr[1:] {
|
|
if m > a {
|
|
m = a
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func log(v ...any) {
|
|
fmt.Fprintln(os.Stderr, v...)
|
|
}
|