advent-of-code/day05/main.go
2023-12-05 12:51:57 -07:00

190 lines
3.6 KiB
Go

package main
import (
"bufio"
"fmt"
"log/slog"
"os"
"sort"
"strconv"
"strings"
)
func main() {
var level slog.Level
if err := level.UnmarshalText([]byte(os.Getenv("DEBUG_LEVEL"))); err == nil && level != 0 {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})))
}
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "Usage: day05 FILE")
}
input, err := os.Open(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
scan := bufio.NewScanner(input)
minLocation, minRangeLocation := run(scan)
fmt.Println("min location:", minLocation)
fmt.Println("min range location:", minRangeLocation)
}
func run(scan *bufio.Scanner) (int, int) {
var seeds []int
var seedRanges []int
lookup := map[string]*Lookup{}
for scan.Scan() {
text := scan.Text()
if strings.HasPrefix(text, "seeds:") && len(seeds) == 0 {
seeds, seedRanges = readSeeds(text)
}
lookup = readMaps(scan)
}
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"],
)
seedLocations := make([]int, len(seeds))
for i, s := range seeds {
seedLocations[i] = find.Find(s)
}
minLocation := min(seedLocations...)
seedRangeLocations := make([]int, len(seedRanges))
for i, s := range seedRanges {
seedRangeLocations[i] = find.Find(s)
}
minRangeLocation := min(seedRangeLocations...)
return minLocation, minRangeLocation
}
func readSeeds(text string) ([]int, []int) {
var seeds, seedRanges []int
sp := strings.Fields(strings.TrimPrefix(text, "seeds: "))
for i, s := range sp {
n, _ := strconv.Atoi(s)
seeds = append(seeds, n)
if i%2 == 0 {
seedRanges = append(seedRanges, n)
} else {
lastN := seedRanges[len(seedRanges)-1]
r := make([]int, n-1)
for i := range r {
r[i] = lastN + i + 1
}
seedRanges = append(seedRanges, r...)
}
}
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
}
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 {
m := arr[0]
for _, a := range arr[1:] {
if m > a {
m = a
}
}
return m
}