diff --git a/2023/day05_almanac.go b/2023/day05_almanac.go index fbf7c87..bddf0d7 100644 --- a/2023/day05_almanac.go +++ b/2023/day05_almanac.go @@ -46,12 +46,63 @@ func Day05Part1(input io.Reader) (int, error) { func Day05Part2(input io.Reader) (int, error) { scanner := bufio.NewScanner(input) + scanner.Scan() + seedRanges := parseSeedRanges(lineToInts(strings.TrimPrefix(scanner.Text(), "seeds: "))) + + scanner.Scan() + + inMap := false + var currentMap day05Map for scanner.Scan() { line := scanner.Text() - _ = line + if line == "" { + var newRanges []day05SeedRange + for _, sRange := range seedRanges { + newRanges = append(newRanges, currentMap.convertRange(sRange)...) + } + seedRanges = newRanges + inMap = false + currentMap = nil + } else if strings.HasSuffix(line, "map:") { + inMap = true + } else if inMap { + currentMap = append(currentMap, newDay05Range(line)) + } } - return 0, nil + if inMap { + // Convert one last time if necessary + var newRanges []day05SeedRange + for _, sRange := range seedRanges { + newRanges = append(newRanges, currentMap.convertRange(sRange)...) + } + seedRanges = newRanges + inMap = false + currentMap = nil + } + + mini := int(^uint(0) >> 1) // highest int + for _, r := range seedRanges { + if r.start < mini { + mini = r.start + } + } + + return mini, nil +} + +func parseSeedRanges(ints []int) []day05SeedRange { + ranges := make([]day05SeedRange, len(ints)/2) + for i := 0; i < len(ints)-1; i += 2 { + ranges[i/2] = day05SeedRange{start: ints[i], end: ints[i] + ints[i+1]} + } + return ranges +} + +// range is [start; end) +// start is included, end is not. +type day05SeedRange struct { + start, end int } func lineToInts(line string) []int { @@ -68,25 +119,61 @@ func lineToInts(line string) []int { } type day05Range struct { - destination, source, length int + destStart, sourceStart, length int } func newDay05Range(line string) day05Range { ints := lineToInts(line) return day05Range{ - destination: ints[0], - source: ints[1], + destStart: ints[0], + sourceStart: ints[1], length: ints[2], } } +func (r day05Range) sourceEnd() int { + return r.sourceStart + r.length +} + +func (r day05Range) destEnd() int { + return r.destStart + r.length +} + func (r day05Range) convert(n int) (int, bool) { - if n >= r.source && n < r.source+r.length { - return n - r.source + r.destination, true + if n >= r.sourceStart && n < r.sourceEnd() { + return r.mustConvert(n), true } return n, false } +func (r day05Range) mustConvert(n int) int { + return n - r.sourceStart + r.destStart +} + +func (r day05Range) convertRange(s day05SeedRange) (converted []day05SeedRange, notConverted []day05SeedRange) { + if r.sourceStart <= s.start && s.end <= r.sourceEnd() { + // s-----s + // r--------r + return []day05SeedRange{{start: r.mustConvert(s.start), end: r.mustConvert(s.end)}}, nil + } else if s.start < r.sourceStart && r.sourceStart <= s.end && s.end <= r.sourceEnd() { + // s-------s + // r--------r + return []day05SeedRange{{start: r.destStart, end: r.mustConvert(s.end)}}, []day05SeedRange{{start: s.start, end: r.sourceStart}} + } else if r.sourceStart <= s.start && s.start <= r.sourceEnd() && r.sourceEnd() < s.end { + // s-------s + // r--------r + return []day05SeedRange{{start: r.mustConvert(s.start), end: r.destEnd()}}, []day05SeedRange{{start: r.sourceEnd(), end: s.end}} + } else if s.start <= r.sourceStart && r.sourceEnd() < s.end { + // s------------s + // r--------r + return []day05SeedRange{{start: r.destStart, end: r.destEnd()}}, []day05SeedRange{{start: s.start, end: r.sourceStart}, {start: r.sourceEnd(), end: s.end}} + } else { + // s---s + // r-----r + return nil, []day05SeedRange{s} + } +} + type day05Map []day05Range func (m day05Map) convert(n int) int { @@ -97,3 +184,19 @@ func (m day05Map) convert(n int) int { } return n } + +func (m day05Map) convertRange(s day05SeedRange) []day05SeedRange { + var converted []day05SeedRange + toConvert := []day05SeedRange{s} + for _, dRange := range m { + var toConvertNext []day05SeedRange + for _, c := range toConvert { + var conv []day05SeedRange + conv, toConvertNext = dRange.convertRange(c) + converted = append(converted, conv...) + } + toConvert = toConvertNext + } + converted = append(converted, toConvert...) + return converted +} diff --git a/2023/day05_almanac_test.go b/2023/day05_almanac_test.go index 529c7ff..eb9919c 100644 --- a/2023/day05_almanac_test.go +++ b/2023/day05_almanac_test.go @@ -26,10 +26,38 @@ func TestDay05Part1(t *testing.T) { func TestDay05Part2(t *testing.T) { tests := []testCase{ - {"inputs/day05_test2", 0}, - {"inputs/day05", 0}, + {"inputs/day05_test2", 46}, + {"inputs/day05", 78775051}, } for _, test := range tests { t.Run(test.filename, check(test, Day05Part2)) } } + +func TestDay05Ranges(t *testing.T) { + tests := []struct { + name string + dRange day05Range + seedRange day05SeedRange + expected []day05SeedRange + }{ + { + "seed included in range", + day05Range{0, 5, 5}, + day05SeedRange{6, 8}, + []day05SeedRange{{1, 3}}, + }, + { + "range included in seed", + day05Range{0, 10, 2}, + day05SeedRange{9, 13}, + []day05SeedRange{{0, 2}, {9, 10}, {12, 13}}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := day05Map{test.dRange}.convertRange(test.seedRange) + assert.Equal(t, test.expected, got) + }) + } +} diff --git a/2023/inputs/day05_test2 b/2023/inputs/day05_test2 index e69de29..f756727 100644 --- a/2023/inputs/day05_test2 +++ b/2023/inputs/day05_test2 @@ -0,0 +1,33 @@ +seeds: 79 14 55 13 + +seed-to-soil map: +50 98 2 +52 50 48 + +soil-to-fertilizer map: +0 15 37 +37 52 2 +39 0 15 + +fertilizer-to-water map: +49 53 8 +0 11 42 +42 0 7 +57 7 4 + +water-to-light map: +88 18 7 +18 25 70 + +light-to-temperature map: +45 77 23 +81 45 19 +68 64 13 + +temperature-to-humidity map: +0 69 1 +1 0 69 + +humidity-to-location map: +60 56 37 +56 93 4