Skip to content

Commit

Permalink
2023 graph: rework how neighbors are iterated
Browse files Browse the repository at this point in the history
Replace `get_neighbors(Key) -> iterable<Key>` with
`process_neighbors(Key, auto &&visit)` where `visit` is called on each
of the neighbors. This avoids having to construct and destroy lots of
single-use vectors of neighbors, and should be able to inline down to a
single loop.
  • Loading branch information
yut23 committed Feb 22, 2024
1 parent ccd13a5 commit e6caa61
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 163 deletions.
18 changes: 10 additions & 8 deletions 2023/src/day16.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,13 @@ struct GraphHelper {
std::vector<std::vector<int>> component_successors;
std::map<Key, int> source_components;

std::vector<Key> get_neighbors(const Key &key) const {
std::vector<Key> neighbors;
void process_neighbors(const Key &key, auto &&handler) const {
for (AbsDirection dir : grid[key.pos].get_out_dir(key.dir)) {
Pos new_pos = key.pos + Delta(dir, true);
if (grid.in_bounds(new_pos)) {
neighbors.emplace_back(new_pos, dir);
handler({new_pos, dir});
}
}
return neighbors;
}

explicit GraphHelper(const LaserGrid &grid_) : grid(grid_) {
Expand All @@ -229,7 +227,9 @@ struct GraphHelper {

std::set<std::pair<int, int>> edges;
std::tie(components, edges) = aoc::graph::tarjan_scc<Key>(
sources, std::bind_front(&GraphHelper::get_neighbors, this));
sources, [this](const Key &key, auto &&handler) {
process_neighbors(key, handler);
});

component_successors.resize(components.size());
for (const auto &[from, to] : edges) {
Expand All @@ -244,8 +244,8 @@ struct GraphHelper {
}
}

const std::vector<int> &get_component_neighbors(int id) const {
return component_successors[id];
void process_component_neighbors(int id, auto &&handler) const {
std::ranges::for_each(component_successors[id], handler);
}

int count_energized(const Key &source) const;
Expand Down Expand Up @@ -276,7 +276,9 @@ int GraphHelper::count_energized(const GraphHelper::Key &source) const {
constexpr bool use_seen = false;
aoc::graph::dfs<use_seen>(
source_components.at(source),
std::bind_front(&GraphHelper::get_component_neighbors, this),
[this](int id, auto &&handler) {
process_component_neighbors(id, handler);
},
visit_with_parent);

return std::count(energized.data().begin(), energized.data().end(), true);
Expand Down
17 changes: 9 additions & 8 deletions 2023/src/day17.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class CityMap {

aoc::ds::Grid<unsigned char> block_costs;

std::vector<Key> get_neighbors(bool ultra, const Key &key) const;
void process_neighbors(bool ultra, const Key &key, auto &&visit) const;
int get_distance(const Key &from, const Key &to) const;

public:
Expand All @@ -60,15 +60,14 @@ std::ostream &operator<<(std::ostream &os, const CityMap::Key &key) {
return os;
}

std::vector<CityMap::Key> CityMap::get_neighbors(bool ultra,
const Key &key) const {
void CityMap::process_neighbors(bool ultra, const Key &key,
auto &&visit) const {
const int min_straight_moves = ultra ? 4 : 1;
const int max_straight_moves = ultra ? 10 : 3;
std::vector<Key> neighbors;
if (key.pos.x == 0 && key.pos.y == 0 &&
key.orient == Orientation::horizontal) {
// allow going down initially
neighbors.push_back({key.pos, Orientation::vertical});
visit({key.pos, Orientation::vertical});
}
for (const auto orient : {Orientation::horizontal, Orientation::vertical}) {
if (orient == key.orient) {
Expand All @@ -86,11 +85,10 @@ std::vector<CityMap::Key> CityMap::get_neighbors(bool ultra,
if (!block_costs.in_bounds(neighbor.pos)) {
continue;
}
neighbors.push_back(std::move(neighbor));
visit(neighbor);
}
}
}
return neighbors;
}

int CityMap::get_distance(const Key &from, const Key &to) const {
Expand Down Expand Up @@ -135,7 +133,10 @@ int CityMap::find_shortest_path(bool ultra) const {
std::bind_front(&CityMap::get_distance, this), is_target, visit);
#else
const auto &[distance, path] = aoc::graph::a_star(
source, std::bind_front(&CityMap::get_neighbors, this, ultra),
source,
[this, ultra](const Key &key, auto &&visit_neighbor) {
process_neighbors(ultra, key, visit_neighbor);
},
std::bind_front(&CityMap::get_distance, this), is_target,
[&target](const Key &key) -> int {
return (key.pos - target).manhattan_distance();
Expand Down
15 changes: 7 additions & 8 deletions 2023/src/day19.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ class Part2Solver {

std::multimap<Key, Key> prev{};

std::vector<Key> get_neighbors(const Key &key);
void process_neighbors(const Key &key, auto &&visit) const;

void visit_with_parent(const Key &key, const Key &parent, int);

Expand All @@ -257,18 +257,16 @@ std::ostream &operator<<(std::ostream &os, const Part2Solver::Key &key) {
return os;
}

std::vector<Part2Solver::Key> Part2Solver::get_neighbors(const Key &key) {
void Part2Solver::process_neighbors(const Key &key, auto &&visit) const {
const auto &[name, index] = key;
std::vector<Key> neighbors;
if (name == "A" || name == "R") {
return neighbors;
return;
}
const Rule &rule = cat.at(name, index);
if (rule.conditional()) {
neighbors.emplace_back(name, index + 1);
visit({name, index + 1});
}
neighbors.emplace_back(rule.dest, 0);
return neighbors;
visit({rule.dest, 0});
}

void Part2Solver::visit_with_parent(const Key &key, const Key &parent, int) {
Expand All @@ -293,7 +291,8 @@ std::optional<Condition> Part2Solver::get_condition(const Key &from,
long Part2Solver::solve() {
constexpr bool use_seen = false;
aoc::graph::dfs<use_seen>(
source, std::bind_front(&Part2Solver::get_neighbors, this),
source,
[this](const Key &key, auto &&visit) { process_neighbors(key, visit); },
std::bind_front(&Part2Solver::visit_with_parent, this));

if constexpr (aoc::DEBUG) {
Expand Down
7 changes: 4 additions & 3 deletions 2023/src/day20.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,11 @@ MessageBus MessageBus::read_modules(std::istream &is) {

void MessageBus::identify_components() {
const std::string root = "broadcaster";
const auto get_neighbors = [this](const std::string &name) {
return modules.at(name)->outputs;
const auto process_neighbors = [this](const std::string &name,
auto &&visit) {
std::ranges::for_each(modules.at(name)->outputs, visit);
};
components = aoc::graph::tarjan_scc(root, get_neighbors).first;
components = aoc::graph::tarjan_scc(root, process_neighbors).first;

// store component ids in each module
for (std::size_t i = 0; i < components.size(); ++i) {
Expand Down
10 changes: 4 additions & 6 deletions 2023/src/day21.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,23 @@ struct Garden {
Grid<int> distances(stones, std::numeric_limits<int>::max());
distances[source] = 0;

const auto get_neighbors = [this, &distances](const Key &key) {
std::vector<Key> neighbors{};
neighbors.reserve(4);
const auto process_neighbors = [this, &distances](const Key &key,
auto &&visit) {
for (const AbsDirection &dir : aoc::DIRECTIONS) {
Pos pos = key + Delta(dir, true);
if (stones.in_bounds(pos) && stones[pos] &&
distances[pos] == std::numeric_limits<int>::max()) {
neighbors.push_back(pos);
visit(pos);
}
}
return neighbors;
};
const auto visit = [&distances](const Key &key, int distance) {
distances[key] = distance;
};

// handled in get_neighbors
constexpr bool use_seen = false;
aoc::graph::bfs<use_seen>(source, get_neighbors, visit);
aoc::graph::bfs<use_seen>(source, process_neighbors, visit);

return distances;
}
Expand Down
7 changes: 4 additions & 3 deletions 2023/src/day22.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ int part_2(const aoc::day22::BrickStack &stack) {
}
}

const auto get_neighbors = [&stack](const Brick *brick) {
return stack.supporting.at(brick);
const auto process_neighbors = [&stack](const Brick *brick,
auto &&visit) {
std::ranges::for_each(stack.supporting.at(brick), visit);
};
const auto ordering = aoc::graph::topo_sort(root, get_neighbors);
const auto ordering = aoc::graph::topo_sort(root, process_neighbors);

std::unordered_set<const Brick *> falling{root};
for (const Brick *brick : ordering) {
Expand Down
9 changes: 5 additions & 4 deletions 2023/src/day23.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,16 @@ int TrailMap::part_1() const {
}
std::cerr << "}\n";
}
const auto get_neighbors = [&fwd_edges = fwd_edges](const Key key) {
return fwd_edges[key];
const auto process_neighbors = [&fwd_edges = fwd_edges](const Key key,
auto &&visit) {
std::ranges::for_each(fwd_edges[key], visit);
};
const auto is_target = [&target = target](const Key key) -> bool {
return key == target;
};
const auto &[distance, path] = aoc::graph::longest_path_dag(
start, get_neighbors, std::bind_front(&TrailMap::get_distance, this),
is_target);
start, process_neighbors,
std::bind_front(&TrailMap::get_distance, this), is_target);

if constexpr (aoc::DEBUG) {
std::cerr << "longest path:\n";
Expand Down
Loading

0 comments on commit e6caa61

Please sign in to comment.