diff --git a/2023/src/day05.cpp b/2023/src/day05.cpp index c76b3c6..462ec6e 100644 --- a/2023/src/day05.cpp +++ b/2023/src/day05.cpp @@ -8,11 +8,62 @@ #include "day05.hpp" #include "lib.hpp" // for parse_args, DEBUG #include // for min_element +#include // for size_t #include // for cerr, cout, ws +#include // for numeric_limits + +long part_1(const std::vector &seeds, + const std::vector &maps) { + auto results{seeds}; + for (const auto &map : maps) { + map.apply_in_place(results); + } + return *std::ranges::min_element(results); +} + +long part_2(const std::vector &seeds, + const std::vector &maps) { + /* + * The insight here is that we only need to keep track of the ranges of + * values, since applying a map to a range will either shift it (if + * it's entirely within one MapEntry), or split it into multiple ranges + * (if it spans several MapEntries). + */ + using namespace aoc::day05; + std::vector seed_ranges; + for (std::size_t i = 0; i < seeds.size(); i += 2) { + seed_ranges.emplace_back(seeds[i], seeds[i + 1]); + } + + for (const auto &map : maps) { + if constexpr (aoc::DEBUG) { + std::cerr << "processing " << map.label << " map...\n"; + } + std::vector next_ranges; + for (auto rng : seed_ranges) { + if constexpr (aoc::DEBUG) { + std::cerr << " processing range " << rng << "...\n"; + } + for (const auto &entry : map.entries) { + Range new_range = entry.intersect(rng); + if (new_range.length > 0) { + if constexpr (aoc::DEBUG) { + std::cerr << " adding new range " << new_range + << " from " << entry << "\n"; + } + next_ranges.push_back(std::move(new_range)); + } + } + } + seed_ranges = std::move(next_ranges); + } + return std::ranges::min_element(seed_ranges)->start; +} int main(int argc, char **argv) { std::ifstream infile = aoc::parse_args(argc, argv); + // read input auto seeds = aoc::day05::read_seeds(infile); if constexpr (aoc::DEBUG) { std::cerr << "seeds:"; @@ -25,15 +76,34 @@ int main(int argc, char **argv) { // skip blank line infile >> std::ws; - auto results{seeds}; + std::vector maps; while (infile) { auto map = aoc::day05::ConversionMap::read(infile); + std::ranges::sort(map.entries); + // fill in no-op map entries (shift = 0) + std::size_t N = map.entries.size(); + long curr = 0; + for (std::size_t i = 0; i < N; ++i) { + const auto entry = map.entries[i]; + if (curr < entry.start) { + map.entries.emplace_back(curr, curr, entry.start - curr); + } + curr = entry.start + entry.length; + } + if (curr < std::numeric_limits::max()) { + map.entries.emplace_back( + curr, curr, std::numeric_limits::max() - curr); + } + std::ranges::sort(map.entries); if constexpr (aoc::DEBUG) { std::cerr << "read map: " << map << "\n"; } - map.apply_in_place(results); + maps.push_back(std::move(map)); } - auto part_1 = *std::ranges::min_element(results); - std::cout << part_1 << "\n"; + + std::cout << part_1(seeds, maps) << "\n"; + std::cout << part_2(seeds, maps) << "\n"; + + // part 2 return 0; } diff --git a/2023/src/day05.hpp b/2023/src/day05.hpp index 9d04e73..16a4ea7 100644 --- a/2023/src/day05.hpp +++ b/2023/src/day05.hpp @@ -6,7 +6,7 @@ *****************************************************************************/ #include "lib.hpp" // for skip -#include // for find_if, transform +#include // for find_if, transform, max, min #include // for setw #include // for istream, ostream #include // for istringstream @@ -16,36 +16,67 @@ namespace aoc::day05 { -struct MapEntry { - long src_start; - long dest_start; - long length; +struct Range { + long start{0}; + long length{0}; - auto operator<=>(const MapEntry &) const = default; + Range() = default; + Range(long start, long length) : start(start), length(length) {} + + bool contains(long value) const { + return value >= start && value < start + length; + } + + auto operator<=>(const Range &) const = default; +}; + +std::ostream &operator<<(std::ostream &os, const Range &r) { + os << "[" << r.start << ", " << r.start + r.length << ")"; + return os; +} + +struct MapEntry : public Range { + long shift{0}; + + MapEntry() = default; + MapEntry(long dest_start, long src_start, long length) + : Range(src_start, length), shift(dest_start - src_start) {} + + Range intersect(const Range &r) const { + long max_start = std::max(start, r.start); + long min_end = std::min(start + length, r.start + r.length); + long new_length = min_end - max_start; + if (new_length > 0) { + return Range(max_start + shift, new_length); + } + return {}; + } + + auto operator<=>(const MapEntry &other) const = default; }; std::istream &operator>>(std::istream &is, MapEntry &entry) { - is >> entry.dest_start >> entry.src_start >> entry.length; + long dest_start, src_start, length; + is >> dest_start >> src_start >> length; + entry = MapEntry(dest_start, src_start, length); return is; } std::ostream &operator<<(std::ostream &os, const MapEntry &entry) { - os << "MapEntry<[" << std::setw(10) << entry.src_start << ", " - << std::setw(10) << entry.src_start + entry.length << ") → [" - << std::setw(10) << entry.dest_start << ", " << std::setw(10) - << entry.dest_start + entry.length << ")>"; + os << "MapEntry<[" << entry.start << ", " << entry.start + entry.length + << "), shift=" << entry.shift << ">"; return os; } -class ConversionMap { +struct ConversionMap { std::string label{}; std::vector entries; - public: ConversionMap() = default; - explicit ConversionMap(const std::string &label) : label(label) {} + template ConversionMap(const std::string &label, T &&entries) : label(label), entries(std::forward>(entries)) {} + std::string get_label() const { return label; } void add_entry(MapEntry &&entry) { entries.push_back(std::move(entry)); } @@ -69,11 +100,10 @@ class ConversionMap { long apply(long source) const { auto entry_it = std::ranges::find_if(entries, [source](const MapEntry &entry) { - return source >= entry.src_start && - source < entry.src_start + entry.length; + return entry.contains(source); }); if (entry_it != entries.end()) { - return source - entry_it->src_start + entry_it->dest_start; + return source + entry_it->shift; } return source; } diff --git a/2023/src/test05.cpp b/2023/src/test05.cpp index 226b514..94b9c0a 100644 --- a/2023/src/test05.cpp +++ b/2023/src/test05.cpp @@ -24,14 +24,14 @@ namespace aoc::day05::test { std::size_t test_mapping() { ConversionMap map; - map.add_entry(MapEntry{.src_start = 98, .dest_start = 50, .length = 2}); - map.add_entry(MapEntry{.src_start = 50, .dest_start = 52, .length = 48}); - unit_test::MethodTest test( - std::string{"test05::test_mapping"}, map, &ConversionMap::apply); + map.add_entry(MapEntry(50, 98, 2)); + map.add_entry(MapEntry(52, 50, 48)); + unit_test::MethodTest test( + "test05::test_mapping", map, &ConversionMap::apply); - std::vector input_vec(110); + std::vector input_vec(110); std::iota(input_vec.begin(), input_vec.end(), 0); - std::vector expected_vec(110); + std::vector expected_vec(110); auto start = expected_vec.begin(); std::iota(start, start + 50, 0); start += 50; @@ -57,23 +57,18 @@ ConversionMap parse_map(const std::string &text) { std::size_t test_map_input() { std::istringstream ss{}; unit_test::PureTest test("test05::test_map_input", &parse_map); - test.run_on_args( - "seed-to-soil map:\n50 98 2\n52 50 48\n", - ConversionMap( - "seed-to-soil", - std::vector{ - MapEntry{.src_start = 50, .dest_start = 52, .length = 48}, - MapEntry{.src_start = 98, .dest_start = 50, .length = 2}, - })); + test.run_on_args("seed-to-soil map:\n50 98 2\n52 50 48\n", + ConversionMap("seed-to-soil", std::vector{ + MapEntry(52, 50, 48), + MapEntry(50, 98, 2), + })); test.run_on_args( "soil-to-fertilizer map:\n0 15 37\n37 52 2\n39 0 15\n", - ConversionMap( - "soil-to-fertilizer", - std::vector{ - MapEntry{.src_start = 0, .dest_start = 39, .length = 15}, - MapEntry{.src_start = 15, .dest_start = 0, .length = 37}, - MapEntry{.src_start = 52, .dest_start = 37, .length = 2}, - })); + ConversionMap("soil-to-fertilizer", std::vector{ + MapEntry(39, 0, 15), + MapEntry(0, 15, 37), + MapEntry(37, 52, 2), + })); test.done(); return test.num_failed(); }