219 lines
4.4 KiB
Go
219 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
|
|
h "git.bullercodeworks.com/brian/adventofcode/helpers"
|
|
)
|
|
|
|
func main() {
|
|
inp := h.StdinToStringSlice()
|
|
part1(inp)
|
|
fmt.Println()
|
|
part2(inp)
|
|
}
|
|
|
|
func part1(input []string) {
|
|
a := BuildAlmanac(input)
|
|
fmt.Println("# Part 1")
|
|
fmt.Println(h.SliceMin(a.findAllSeedLocations()))
|
|
}
|
|
|
|
func part2(input []string) {
|
|
a := BuildAlmanac(input)
|
|
fmt.Println("# Part 2")
|
|
fmt.Println(a.findLowestFromPairs())
|
|
}
|
|
|
|
type Seed struct {
|
|
start, length int
|
|
}
|
|
type Almanac struct {
|
|
seeds []int
|
|
seedRanges []Seed
|
|
mappings map[string]*RangeMap
|
|
rangeMap *RangeMap
|
|
locRangeMap *RangeMap
|
|
mapOrder []string
|
|
}
|
|
|
|
func BuildAlmanac(input []string) *Almanac {
|
|
a := Almanac{
|
|
mappings: make(map[string]*RangeMap),
|
|
mapOrder: []string{
|
|
"seed-to-soil",
|
|
"soil-to-fertilizer",
|
|
"fertilizer-to-water",
|
|
"water-to-light",
|
|
"light-to-temperature",
|
|
"temperature-to-humidity",
|
|
"humidity-to-location",
|
|
},
|
|
}
|
|
for _, v := range strings.Fields(input[0])[1:] {
|
|
a.seeds = append(a.seeds, h.Atoi(v))
|
|
}
|
|
i := 2
|
|
for ; i < len(input); i++ {
|
|
wrkMap, wrkI := parseMapAt(input[i:])
|
|
a.mappings[wrkMap.name] = wrkMap
|
|
i = i + wrkI
|
|
}
|
|
for i, m := range a.mapOrder {
|
|
if i == 0 {
|
|
a.rangeMap = a.mappings[m]
|
|
}
|
|
if i < len(a.mapOrder)-1 {
|
|
a.mappings[m].next = a.mappings[a.mapOrder[i+1]]
|
|
a.mappings[a.mapOrder[i+1]].prev = a.mappings[m]
|
|
} else {
|
|
a.mappings[m].prev = a.mappings[a.mapOrder[i-1]]
|
|
a.locRangeMap = a.mappings[m]
|
|
}
|
|
}
|
|
a.getSeedRanges()
|
|
return &a
|
|
}
|
|
|
|
func (a *Almanac) getSeedRanges() {
|
|
for i := 0; i < len(a.seeds); i++ {
|
|
seed := a.seeds[i]
|
|
i++
|
|
num := a.seeds[i]
|
|
a.seedRanges = append(a.seedRanges, Seed{
|
|
start: seed,
|
|
length: num,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (a *Almanac) findAllSeedLocations() []int {
|
|
var res []int
|
|
for i := range a.seeds {
|
|
res = append(res, a.rangeMap.find(a.seeds[i]))
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (a *Almanac) findLowestFromPairs() int {
|
|
for loc := 0; loc < math.MaxInt; loc++ {
|
|
seed := a.locRangeMap.findDest(loc)
|
|
for _, s := range a.seedRanges {
|
|
if seed >= s.start && seed <= s.start+s.length-1 {
|
|
return loc
|
|
}
|
|
}
|
|
}
|
|
return h.MIN_INT
|
|
}
|
|
|
|
func (a Almanac) String() string {
|
|
res := fmt.Sprintln("Seeds:", a.seeds)
|
|
for i := range a.mappings {
|
|
res = fmt.Sprintf("%s\n%s\n", res, a.mappings[i])
|
|
}
|
|
return res
|
|
}
|
|
|
|
type RangeMap struct {
|
|
name string
|
|
next *RangeMap
|
|
prev *RangeMap
|
|
associations []Association
|
|
hereToLoc map[int]int
|
|
lastFind int
|
|
}
|
|
|
|
func parseMapAt(input []string) (*RangeMap, int) {
|
|
name := strings.Fields(input[0])[0]
|
|
res := &RangeMap{
|
|
name: name,
|
|
associations: []Association{},
|
|
hereToLoc: make(map[int]int),
|
|
}
|
|
i := 1
|
|
for ; i < len(input); i++ {
|
|
if input[i] == "" {
|
|
break
|
|
}
|
|
pts := strings.Fields(input[i])
|
|
assoc := Association{
|
|
startSource: h.Atoi(pts[1]),
|
|
startDest: h.Atoi(pts[0]),
|
|
assocRange: h.Atoi(pts[2]),
|
|
}
|
|
assoc.diff = assoc.startDest - assoc.startSource
|
|
res.associations = append(res.associations, assoc)
|
|
}
|
|
return res, i
|
|
}
|
|
|
|
func (r *RangeMap) find(source int) int {
|
|
if v, ok := r.hereToLoc[source]; ok {
|
|
return v
|
|
}
|
|
next := source
|
|
for _, a := range r.associations {
|
|
if a.has(source) {
|
|
next = a.find(source)
|
|
}
|
|
}
|
|
if r.next == nil {
|
|
return next
|
|
} else {
|
|
ret := r.next.find(next)
|
|
r.hereToLoc[source] = ret
|
|
return ret
|
|
}
|
|
}
|
|
func (r *RangeMap) findDest(dest int) int {
|
|
prev := dest
|
|
for i := len(r.associations) - 1; i >= 0; i-- {
|
|
a := r.associations[i]
|
|
if a.hasDest(dest) {
|
|
prev = a.findSource(dest)
|
|
}
|
|
}
|
|
if r.prev == nil {
|
|
return prev
|
|
} else {
|
|
return r.prev.findDest(prev)
|
|
}
|
|
}
|
|
func (r *RangeMap) setLastFindResult(to int) {
|
|
r.hereToLoc[r.lastFind] = to
|
|
}
|
|
|
|
func (r RangeMap) String() string {
|
|
res := fmt.Sprintf("%s map:", r.name)
|
|
for _, v := range r.associations {
|
|
res = fmt.Sprintf("%s\n%s", res, v)
|
|
}
|
|
return res
|
|
}
|
|
|
|
type Association struct {
|
|
startSource int
|
|
startDest int
|
|
assocRange int
|
|
diff int
|
|
}
|
|
|
|
func (a *Association) has(source int) bool {
|
|
return source >= a.startSource && source < a.startSource+a.assocRange
|
|
}
|
|
func (a *Association) find(source int) int {
|
|
return source + a.diff
|
|
}
|
|
func (a *Association) hasDest(dest int) bool {
|
|
return dest >= a.startDest && dest < a.startDest+a.assocRange
|
|
}
|
|
func (a *Association) findSource(dest int) int {
|
|
return dest - a.diff
|
|
}
|
|
func (a Association) String() string {
|
|
return fmt.Sprintf("%d %d %d", a.startSource, a.startDest, a.assocRange)
|
|
}
|