Skip to content

Commit

Permalink
2023: day 5 part 2 complete
Browse files Browse the repository at this point in the history
  • Loading branch information
yut23 committed Dec 6, 2023
1 parent a4c6530 commit 0ed25a0
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 42 deletions.
78 changes: 74 additions & 4 deletions 2023/src/day05.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,62 @@
#include "day05.hpp"
#include "lib.hpp" // for parse_args, DEBUG
#include <algorithm> // for min_element
#include <cstddef> // for size_t
#include <iostream> // for cerr, cout, ws
#include <limits> // for numeric_limits

long part_1(const std::vector<long> &seeds,
const std::vector<aoc::day05::ConversionMap> &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<long> &seeds,
const std::vector<aoc::day05::ConversionMap> &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<Range> 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<Range> 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:";
Expand All @@ -25,15 +76,34 @@ int main(int argc, char **argv) {
// skip blank line
infile >> std::ws;

auto results{seeds};
std::vector<aoc::day05::ConversionMap> 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<decltype(curr)>::max()) {
map.entries.emplace_back(
curr, curr, std::numeric_limits<decltype(curr)>::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;
}
64 changes: 47 additions & 17 deletions 2023/src/day05.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*****************************************************************************/

#include "lib.hpp" // for skip
#include <algorithm> // for find_if, transform
#include <algorithm> // for find_if, transform, max, min
#include <iomanip> // for setw
#include <iostream> // for istream, ostream
#include <sstream> // for istringstream
Expand All @@ -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<MapEntry> entries;

public:
ConversionMap() = default;
explicit ConversionMap(const std::string &label) : label(label) {}

template <class T>
ConversionMap(const std::string &label, T &&entries)
: label(label), entries(std::forward<std::vector<MapEntry>>(entries)) {}

std::string get_label() const { return label; }
void add_entry(MapEntry &&entry) { entries.push_back(std::move(entry)); }

Expand All @@ -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;
}
Expand Down
37 changes: 16 additions & 21 deletions 2023/src/test05.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConversionMap, id_t, id_t> 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<ConversionMap, long, long> test(
"test05::test_mapping", map, &ConversionMap::apply);

std::vector<id_t> input_vec(110);
std::vector<long> input_vec(110);
std::iota(input_vec.begin(), input_vec.end(), 0);
std::vector<id_t> expected_vec(110);
std::vector<long> expected_vec(110);
auto start = expected_vec.begin();
std::iota(start, start + 50, 0);
start += 50;
Expand All @@ -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>{
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>{
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>{
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>{
MapEntry(39, 0, 15),
MapEntry(0, 15, 37),
MapEntry(37, 52, 2),
}));
test.done();
return test.num_failed();
}
Expand Down

0 comments on commit 0ed25a0

Please sign in to comment.