1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-12 13:36:02 +01:00

fix(libstore/find-cycles): add second pass to merge multiedges

The single-pass greedy algorithm could fail to connect all edges if
they arrived in certain orders. For example, edges A→B, C→D, D→A, B→C
would result in two paths [D→A→B→C] and [C→D] instead of one complete
cycle.

Added a second pass that repeatedly tries to merge existing multiedges
with each other until no more merges are possible. This ensures we find
complete cycle paths regardless of edge discovery order.
This commit is contained in:
Bernardo Meurer Costa 2025-10-11 19:50:59 +00:00
parent 8f19a6cb83
commit 7eaa4991a4
No known key found for this signature in database

View file

@ -155,20 +155,16 @@ void transformEdgesToMultiedges(StoreCycleEdgeVec & edges, StoreCycleEdgeVec & m
{
debug("transformEdgesToMultiedges: processing %lu edges", edges.size());
// First pass: join edges to multiedges
for (auto & edge2 : edges) {
bool edge2Joined = false;
for (auto & edge1 : multiedges) {
debug("comparing edge1 (size=%lu) with edge2 (size=%lu)", edge1.size(), edge2.size());
// Check if edge1.back() == edge2.front()
// This means we can extend edge1 by appending edge2
if (edge1.back() == edge2.front()) {
debug("appending: edge1.back()='%s' == edge2.front()='%s'", edge1.back(), edge2.front());
// Append all but the first element of edge2 (to avoid duplication)
for (size_t i = 1; i < edge2.size(); i++) {
debug(" appending edge2[%lu] = %s", i, edge2[i]);
edge1.push_back(edge2[i]);
}
edge2Joined = true;
@ -178,11 +174,8 @@ void transformEdgesToMultiedges(StoreCycleEdgeVec & edges, StoreCycleEdgeVec & m
// Check if edge2.back() == edge1.front()
// This means we can extend edge1 by prepending edge2
if (edge2.back() == edge1.front()) {
debug("prepending: edge2.back()='%s' == edge1.front()='%s'", edge2.back(), edge1.front());
// Prepend all but the last element of edge2 (to avoid duplication)
for (int i = edge2.size() - 2; i >= 0; i--) {
debug(" prepending edge2[%d] = %s", i, edge2[i]);
edge1.push_front(edge2[i]);
}
edge2Joined = true;
@ -191,11 +184,39 @@ void transformEdgesToMultiedges(StoreCycleEdgeVec & edges, StoreCycleEdgeVec & m
}
if (!edge2Joined) {
debug("edge2 is new, adding as separate multiedge");
multiedges.push_back(edge2);
}
}
// Second pass: merge multiedges that can now be connected
// After joining edges, some multiedges might now be connectable
bool merged = true;
while (merged) {
merged = false;
for (size_t i = 0; i < multiedges.size() && !merged; i++) {
for (size_t j = i + 1; j < multiedges.size() && !merged; j++) {
auto & path1 = multiedges[i];
auto & path2 = multiedges[j];
if (path1.back() == path2.front()) {
// Append path2 to path1
for (size_t k = 1; k < path2.size(); k++) {
path1.push_back(path2[k]);
}
multiedges.erase(multiedges.begin() + j);
merged = true;
} else if (path2.back() == path1.front()) {
// Prepend path2 to path1
for (int k = path2.size() - 2; k >= 0; k--) {
path1.push_front(path2[k]);
}
multiedges.erase(multiedges.begin() + j);
merged = true;
}
}
}
}
debug("transformEdgesToMultiedges: result has %lu multiedges", multiedges.size());
}