glwtta has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,
ok, here's my problem - I have a list of id's, we'll call it 'list1', each of which is associated with 1 to 3 id's from list2; I need to break up list2 into several sets, where each of the list2 ids in the set has at least one list1 id in common with at least one other list2 id.

I figured the simplest way to do this is to build a graph with the list2 id's as vertices and an edge between any two that share a list1 id.

I start out with an array (list1) or arrays (list2) of ids (for now I only care about the relationships, not the actual list1 ids) so, I use this to build the graph:

use Graph::Undirected; foreach my $ids (@list1){ $g->add_path(@$ids); )
This doesn't seem to work quite as expected, I would think that if I add a path or three vertices and then add another path of three vertices with one vertix in common, then I would end up with 5 connected vertices. From what I actually get, I seem to have two disconnected sets of vertices.

So, am I not getting how Graph::Undirected works, or am I going about this in a completely incorrect fashion (I supposed it's possible it's both)? Any advice?

Replies are listed 'Best First'.
Re: using Graph::Undirected
by Zaxo (Archbishop) on Feb 21, 2003 at 05:44 UTC

    You're calling an object method without constructing the object.

    From your description, it sounds like you want a complete graph for the vertices listed in each element of list1.

    use Graph::Undirected; my @colors = qw( black white red green blue cyan magenta yellow); my @combos = ( [qw(black cyan yellow)], [qw(red white blue)], [qw(white blue)], [qw(green red)], [qw(cyan magenta)], [qw(yellow magenta)], [qw(magenta cyan)], [qw(magenta black yellow)], ); my $g = Graph::Undirected->new(@colors); for my $combo (@combos) { # Special case of all @$combo <= 3, the following # can be replaced by: # $g->add_cycle( @$combo); for my $pick (0..$#$combo-1) { $g->add_edge( $combo->[$pick], $combo->[$_]) for $pick+1..$#$ +combo; } } my @components = $g->strongly_connected_components; print "@$_", $/ for @components;
    That adds all the elements of list2 as vertices in the constructor, then adds edges for all pairs in each element of list1. Is that close to what you want?

    Update: Repaired a couple of typos and a harmless fencepost error in the inner loop which added self-edges to most vertices. Added comment with simpler code for the case that the @combos elements don't have more than three colors. In response to glwtta's reply, modified data to exhibit disconnected components and added component extraction code with the &Graph::Base::strongly_connected_components method. The Graph module has lots of methods like this, documented in Graph::Base

    After Compline,
    Zaxo

      tall man is right, my example is incomplete and incoherent ( it was late), but luckily, you got it exactly, Zaxo.

      btw, I do construct the graph, I just missed that in the example, trying to keep it minimal.

      One clarification, my input data would look more like:

      my @combos = ( [qw(black white)], [qw(red white)], [qw(cyan red)], [qw(yellow blue)], [qw(magenta green blue)], [qw(cyan periwinkle raspberry)], );
      so not all the vertices are connected, and in fact what I need to get out of this are the sets that are. so what I do is:
      sub r { my $g = shift; my $v = shift; my @ns = $g->neighbors($v); print $v."\n"; $g->delete_vertex($v); foreach my $n (@ns){ r($g, $n); } } while (scalar $g->vertices > 0){ print "next set:\n"; r($g, ($g->vertices)[0]); }
      This however doesn't seem terribly elegant (or efficient), am I anywhere near what I should be doing in this case?

      and thanks for your great help!

Re: using Graph::Undirected
by tall_man (Parson) on Feb 21, 2003 at 05:33 UTC
    It would be nice if you gave us some sample input data. Here is how I imagine a more complete example would work:
    use strict; use Graph::Undirected; use Data::Dumper; # Suppose list1 consists of the # edges to add. Lowercase is first # list, uppercase is second list. # a to A, B, C # b to C, D my @list1 = ( [ qw(a A) ], [ qw(a B) ], [ qw(a C) ], [ qw(b C) ], [ qw(b D) ]); my $g = new Graph::Undirected; foreach my $ids (@list1){ $g->add_path(@$ids); } $Data::Dumper::Indent = 1; print Data::Dumper->Dump([\$g], [qw(*g)]);
    If I understand what you said you wanted to do, it would not make sense to have more than two items in each "add_path" call. That is, if you had [ qw(a A B C)] instead, there would be edges from a to A to B to C, which is wrong. You want a bipartite graph, correct?

    Or perhaps you want something different. If you have an edge between every list2 vertex that shares an id, you would need not only A to B to C, but also C to A, a cycle. Please clarify your question with a more complete example.