Frederick Gotham <
cauldwel...@gmail.com> writes:
> I've been trying to find a way to take a subsection of a graph,
> i.e. to set a new root, and to discard the parents and siblings
> of the new root node. I ended up writing my own code in an hour
> or two but I haven't tested it extensively...
>
> [...code...]
Kind of a funny way of thinking of the problem. What I think you
want to is consider the set of nodes that are reachable from a
given starting point (what you call "the new root node").
The code you wrote is, hmmm, let me say tangled. I don't want to
say much more about it except that I think you're getting lost in
the details. A better approach is to write a general reachable
node finder, calling a function for each node in the graph subset
that is reachable from the given starting point.
In the code below I use your data structure for the graphs, except
I use different names for the types. I use the name 'Links' for
what you call 'ChildContainer', and for your 'LinkContainer' I
use the name 'Graph'.
Here is the code to enumerate and act on a reachable set, in
depth-first preorder (disclaimer: compiled, not tested):
#include <vector>
#include <map>
#include <set>
#include <string>
#include <functional>
using NodeName = ::std::string;
using Links = ::std::vector< NodeName >;
using Graph = ::std::map< NodeName, Links >;
using NodeDoer = ::std::function< void( Graph &, NodeName, unsigned, bool ) >;
using NodeSet = ::std::set< NodeName >;
void dfp_do( Graph &, NodeSet &, NodeName, unsigned, NodeDoer );
void
depth_first_preorder_do( Graph &g, NodeName root, NodeDoer what ){
NodeSet seen;
dfp_do( g, seen, root, 0, what );
}
void
dfp_do( Graph &g, NodeSet &seen, NodeName n, unsigned depth, NodeDoer what ){
bool seen_before = seen.count( n ) > 0;
what( g, n, depth, seen_before );
if( seen_before ) return;
seen.insert( n );
for( auto child :
g.at( n ) ){
dfp_do( g, seen, child, depth+1, what );
}
}
Note that if a given NodeName is encounted more than once, the
callback function 'what' is called for each encounter, with the
boolean 'seen_before' being true for all but the first encounter.
The 'depth' argument is the distance from the root along the
current path (which could be different for different paths through
the graph).
Here is an example use, which prints out nodes in the subgraph
in a nested "call tree" fashion. Nodes that have been traversed
previously are shown with a '*' to mean seen before (once more,
this code is compiled but not tested):
#include <cstdio>
void
show_reachable_nodes_as_nested_hierarchy( Graph &g, NodeName root ){
depth_first_preorder_do( g, root,
[]( Graph &, NodeName name, unsigned depth, bool seen_before ){
const char *flag = seen_before ? "*" : " ";
printf( "%*s %s %s\n", depth, "", flag, &name[0] );
}
);
}
My code doesn't have as much control as yours does with the depth
limiter. That's partly because I'm not sure exactly what you were
doing and partly because I'm not sure why you want it. The
callback function in my code includes a depth parameter, so if you
want just to exclude nodes greater than a certain distance from
the root that is easy to do. Unfortunately how my code does its
processing that exclusion interacts with how cases that have been
seen before are processed, so it may not do the right thing. One
way to fix that is to make the node enumeration function more
flexible, adding a parameter to test whether a particular node
should be processed now (a callback function with a depth argument
and maybe also some other arguments to give some additional
capability). That should be fairly easy to do, so I leave it as
an exercise for the reader.
I hope you found this interesting and helpful.