package day5 import ( "fmt" "log" "math" "os" "sort" "strconv" "strings" "sync" ) type Almanach struct { SeedToSoil, SoilToFert, FertToWater, WaterToLight, LightToTemp, TempToHum, HumToLocation EfficientMap } type Triplet struct { target, source, rangeLen int } type EfficientMap struct { mappings []Triplet } func (m EfficientMap) applyMap(source int) int { for _, triplet := range m.mappings { sourceStart, sourceEnd := triplet.source, triplet.source + triplet.rangeLen if source >= sourceStart && source <= sourceEnd { diff := source - sourceStart return triplet.target + diff } } return source } func applyMap(source int, mapping map[int]int) int { var result int result, found := mapping[source] if !found { result = source } return result } func (a Almanach)locationForSeed(seed int) int { // log.Print("starting calc of location for seed %n", seed) soil := a.SeedToSoil.applyMap(seed) fert := a.SoilToFert.applyMap(soil) water := a.FertToWater.applyMap(fert) light := a.WaterToLight.applyMap(water) temp := a.LightToTemp.applyMap(light) hum := a.TempToHum.applyMap(temp) location := a.HumToLocation.applyMap(hum) return location } func Run() int { fmt.Println("the day 5") inputDir := "day5/input" seedToSoil := fmt.Sprint(inputDir, "/seed-to-soil") log.Print("finished seed map") soilToFert := fmt.Sprint(inputDir, "/soil-to-fertilizer") log.Print("finished soil map") fertToWater := fmt.Sprint(inputDir, "/fertilizer-to-water") log.Print("finished fert map") waterToLight := fmt.Sprint(inputDir, "/water-to-light") log.Print("finished water map") lightToTemp := fmt.Sprint(inputDir, "/light-to-temperature") log.Print("finished light map") tempToHum := fmt.Sprint(inputDir, "/temperature-to-humidity") log.Print("finished temp map") humToLocation := fmt.Sprint(inputDir, "/humidity-to-location") seedsFileName := fmt.Sprint(inputDir, "/seeds") seedsBytes, err := os.ReadFile(seedsFileName) if err != nil { panic(fmt.Sprint("error reading seeds file ", seedsFileName)) } seedsLine := string(seedsBytes) var seeds []int for _, seedStr := range strings.Fields(seedsLine) { seed, err := strconv.Atoi(seedStr) if err != nil { panic(fmt.Sprint("can't read seeds ", seedStr)) } seeds = append(seeds, seed) } log.Print("finished for seeds file", seeds) almanach := Almanach{ SeedToSoil: ReadMap(seedToSoil), SoilToFert: ReadMap(soilToFert), FertToWater: ReadMap(fertToWater), WaterToLight: ReadMap(waterToLight), LightToTemp: ReadMap(lightToTemp), TempToHum: ReadMap(tempToHum), HumToLocation: ReadMap(humToLocation), } log.Print("created almanach") result := math.MaxInt log.Print("before seed range") seedRanges := make([][]int, 0) for i := 0; i < len(seeds)-1; i += 2 { start := seeds[i] seedRangeLen := seeds[i+1] seedRanges = append(seedRanges, []int{start, start+seedRangeLen}) } rangeMins := make(chan int, len(seedRanges)) var wg sync.WaitGroup mergedSeedRanges := merge(seedRanges) for _, seedRange := range mergedSeedRanges { wg.Add(1) go func(seedRange []int) { log.Printf("starting new range %+v. current min is %d", seedRange, result) rangeMin := math.MaxInt for i := seedRange[0]; i <= seedRange[1]; i++ { location := almanach.locationForSeed(i) if location < rangeMin { rangeMins<- location rangeMin = location } } wg.Done() return }(seedRange) } go func() { wg.Wait() close(rangeMins) }() for rangeMin := range rangeMins { log.Printf("processing range min : %d. cur result is %d", rangeMin, result) if rangeMin <= result { result = rangeMin } } return result } // copy from https://codereview.stackexchange.com/questions/259048/merge-intervalsgolang func merge(intervals [][]int) [][]int { const start, end = 0, 1 var merged [][]int if len(intervals) > 1 { sort.Slice(intervals, func(i, j int) bool { return intervals[i][start] < intervals[j][start] }) } for _, interval := range intervals { last := len(merged) - 1 if last < 0 || interval[start] > merged[last][end] { merged = append(merged, []int{start: interval[start], end: interval[end]}, ) } else if interval[end] > merged[last][end] { merged[last][end] = interval[end] } } return merged[:len(merged):len(merged)] } func ReadMap(filename string) EfficientMap { log.Printf("reading in map from %s", filename) result := make([]Triplet, 0) bytes, err := os.ReadFile(filename) if err != nil { panic(fmt.Sprintf("error reading file %s", filename)) } text := string(bytes) text = strings.TrimSpace(text) for _, line := range strings.Split(text, "\n") { nums := strings.Fields(line) if len(nums) != 3 { panic(fmt.Sprint("error, map line doesn't have 3 nums: ", line, nums)) } destinaitonStart, err1 := strconv.Atoi(nums[0]) sourceStart, err2 := strconv.Atoi(nums[1]) rangeLength, err3 := strconv.Atoi(nums[2]) if err1 != nil || err2 != nil || err3 != nil { panic(fmt.Sprint("error converting one of the numbers: ", nums)) } result = append(result, Triplet{destinaitonStart, sourceStart, rangeLength}) } return EfficientMap{result} }