Graph-0.9735/0000755000175000017500000000000014771263365012626 5ustar osboxesosboxesGraph-0.9735/RELEASE0000644000175000017500000000565613743337306013640 0ustar osboxesosboxes0.50 * THIS IS NOT COMPATIBLE with the old 0.2xxx series of the Graph module. Your scripts are likely to break. I did try to fashion a nice compatibility mode but there was no way to do that cleanly and to cover all the old oddities. You can try the compatibility mode but I suggest changing your code instead because the compat mode is not going to be carried over to the next releases of the module. * The main reason for introducing the incompatibilities was that the new Graph supports graphs of 'higher dimensions', and the assumptions made by the old module (most importantly that edges could only span two vertices) in effect made it impossible to extend the interfaces. * The most serious incompatibility is edges(): with the old way in list context it returned the vertices of the edges as a flat list. Now it returns a list of anonymous arrays that contain the vertices for each edge. * vertices() now returns the vertices in an undefined order. * This release does not worry much about speed (some inlining of the "hot paths" has been done, however), but instead about correctness and documentation. Everything is probably slower than 0.2xx by a factor of two to five, or worse. * The average size of an empty graph is about 1160 bytes. * The average size per vertex is about 110 bytes. * The average size per edge is about 390 bytes. * These figures are for a 32-bit Perl. * If you want speed (or especially if you want small memory footprint), you shouldn't be using pure Perl. Consider using things like PDL (pdl.perl.org), XS interfaces to LEDA or Boost Graph libraries (no, I don't know of such interfaces), or build your own algorithms on top of Bit::Vector, or resort non-Perl solutions like MATLAB or Mathematica, or again LEDA or Boost. * The current implementation of Graph objects is decidedly non-trivial (see DESIGN), which means that you cannot extend it in trivial ways (e.g. access vertices of a graph as keys in a hash). But you shouldn't be doing things like that anyway, peeking and poking at objects' innards, right? * The next version of Graph (most likely 0.90) is going to aim for speed. The backward compatibility for the 0.2xxx series will be dropped (because that, too, slows down this release). * No DAG SSSP has been implemented for this release. Dijkstra and Bellman-Ford SSSPs are available, though. * No flow network algorithms (like Ford-Fulkerson) have been implemented for this release. This omission will no doubt bring out from the woodwork all the myriad users of flow networks. * This release depends on the List::Util module, part of Perl releases since Perl 5.8.0, or available from the CPAN. (Also the Heap module is required, as it was already with Graph 0.2xxx.) * This release requires at least Perl 5.005, a step up from 5.004_04 as required by 0.2xx. (I just don't have 5.004 installed any more, so I simply wasn't able to test this release with 5.004.) -- Graph-0.9735/META.json0000644000175000017500000000330514771263365014250 0ustar osboxesosboxes{ "abstract" : "unknown", "author" : [ "Jarkko Hietaniemi " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.72, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Graph", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Test::Pod" : "1.00", "Test::Pod::Coverage" : "1.00" } }, "runtime" : { "requires" : { "Heap" : "0.80", "List::Util" : "1.45", "Safe" : "0", "Scalar::Util" : "0", "Set::Object" : "1.40", "Storable" : "2.05", "perl" : "5.006" } }, "test" : { "recommends" : { "App::Prove" : "3.00" }, "requires" : { "Math::Complex" : "0", "Test::More" : "0.82" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/graphviz-perl/Graph/issues" }, "repository" : { "type" : "git", "url" : "git://github.com/graphviz-perl/Graph.git", "web" : "https://github.com/graphviz-perl/Graph" } }, "version" : "0.9735", "x_serialization_backend" : "JSON::PP version 4.04" } Graph-0.9735/t/0000755000175000017500000000000014771263365013071 5ustar osboxesosboxesGraph-0.9735/t/04_dgraph.t0000644000175000017500000000023613774262024015020 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 1; use lib 't'; use MyDGraph; # http://rt.cpan.org/NoAuth/Bug.html?id=6429 ok(ref(new DGraph), "DGraph"); Graph-0.9735/t/48_get_vertex_count.t0000644000175000017500000000127413774262024017152 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 12; use Graph; my $g = Graph->new; is( $g->get_vertex_count("a"), 0 ); is( $g->get_vertex_count("b"), 0 ); $g->add_vertex("a"); is( $g->get_vertex_count("a"), 1 ); is( $g->get_vertex_count("b"), 0 ); $g->add_vertex("a"); is( $g->get_vertex_count("a"), 1 ); is( $g->get_vertex_count("b"), 0 ); my $h = $g->new(countvertexed => 1); $h->add_vertex("a"); $h->add_vertex("a"); is( $h->get_vertex_count("a"), 2 ); is( $h->get_vertex_count("b"), 0 ); $h->delete_vertex("a"); is( $h->get_vertex_count("a"), 1 ); is( $h->get_vertex_count("b"), 0 ); $h->delete_vertex("a"); is( $h->get_vertex_count("a"), 0 ); is( $h->get_vertex_count("b"), 0 ); Graph-0.9735/t/50_vertex_attributes.t0000644000175000017500000001003414102052753017324 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 187; use Graph; use Graph::Directed; use Graph::Undirected; my @ARGS; { no warnings qw(qw); # the commas @ARGS = ( [qw(graph graphs 0)], [qw(vertex vertices 1 a a,b a a,b,c)], [qw(edge edges 2 a-a a-a,b-b a-a,b a-a,b-b,c-c a-a,b-b,b-c,c-c)], ); } for my $t (@ARGS) { my ($what, $whats, $arity, $g1, $g2, $g3, $g4, $g5) = @$t; my @args = ('a') x $arity; my @args2 = ('b') x $arity; my @args3 = ('c') x $arity; my @anti_args = ("x") x $arity; my ( $add_e, $del_e, $has_e, $add_w, $has_w, $get_w, $set_w, $del_w, $names, $values, $has, $get, $set, $del, $hass, $gets, $sets, $dels, ) = map sprintf($_, $what), qw( add_%s delete_%s has_%s add_weighted_%s has_%s_weight get_%s_weight set_%s_weight delete_%s_weight get_%s_attribute_names get_%s_attribute_values has_%s_attribute get_%s_attribute set_%s_attribute delete_%s_attribute has_%s_attributes get_%s_attributes set_%s_attributes delete_%s_attributes ); my ( $list_e, $add_ws, ) = map sprintf($_, $whats), qw( %s add_weighted_%s ); my $g_d = Graph::Directed->new; my $g_u = Graph::Undirected->new; $_->add_edge(1, 1) for $g_d, $g_u; $g_d->$set((1) x $arity, 'color', 'electric blue'); $g_u->$set((1) x $arity, 'color', 'firetruck red'); is $g_d, '1-1'; is $g_u, '1=1'; my $g = Graph->new; $g->$add_e(@args) if $arity; ok( !$g->$hass(@args) ) for 1..2; ok( $g->$set(@args, "color", "red") ); ok( $g->$has(@args, "color") ) for 1..2; ok( $g->$hass(@args) ) for 1..2; is( $g->$get(@args, "color"), "red" ) for 1..2; is( $g->$get(@args, "colour"), undef ) for 1..2; ok( $g->$set(@args, "color", "green") ); ok( $g->$hass(@args) ) for 1..2; is( $g->$get(@args, "color"), "green" ) for 1..2; is_deeply $g->$gets(@args), { color => "green" }; is_deeply [ $g->$gets(@anti_args) ], [ undef ] if $arity; is_deeply [ $g->$names(@args) ], [ 'color' ]; is_deeply [ $g->$values(@args) ], [ 'green' ]; ok( $g->$set(@args, "taste", "rhubarb") ); ok( $g->$hass(@args) ) for 1..2; is( $g->$get(@args, "taste"), "rhubarb" ) for 1..2; is( $g->$get(@args, "color"), "green" ); is( $g->$get(@args, "taste"), "rhubarb" ); is_deeply $g->$gets(@args), { color => "green", taste => "rhubarb" }; is_deeply [ sort $g->$names(@args) ], [ qw(color taste) ]; is_deeply [ sort $g->$values(@args) ], [ qw(green rhubarb) ]; ok( $g->$del(@args, "color" ) ); ok( !$g->$has(@args, "color" ) ); ok( $g->$hass(@args) ); is( $g->$get(@args, "taste"), "rhubarb" ); ok( $g->$dels(@args) ); ok( !$g->$hass(@args) ); is( $g->$get(@args, "taste"), undef ); ok( !$g->$del(@args, "taste" ) ); ok( !$g->$dels(@args) ); is_deeply $g->$gets(@args), undef; is_deeply [ $g->$names(@args) ], []; is_deeply [ $g->$values(@args) ], []; ok($g->$sets(@args, { 'color' => 'pearl', 'weight' => 'heavy' })); is_deeply $g->$gets(@args), { 'color' => 'pearl', 'weight' => 'heavy' }; next if !$arity; ok( $g->$del_e(@args2) ); is $g, $g1; ok(!$g->$has_e(@args2)); $g->$add_w(@args2, 42); is $g, $g2; ok( $g->$has_e(@args2)); is( $g->$get_w(@args2), 42 ); is( $g->$get(@args2, 'weight'), 42 ); is( $g->$list_e, 2 ); ok( $g->$del_e(@args2) ); ok( $g->$del_e(@args3) ); is $g, $g3; $g->$add_ws(@args2, 43, @args3, 44); is $g, $g4; is( $g->$get_w(@args2), 43 ); is( $g->$get_w(@args3), 44 ); is( $g->$list_e, 3 ); if ($arity > 1) { ok( $g->$del_e(@args2) ); ok( $g->$del_e($args2[1], $args3[0]) ); $g->add_weighted_path($args2[0], 45, $args2[1], 46, $args3[0]); is $g, $g5; is( $g->get_edge_weight(@args2), 45 ); is( $g->get_edge_weight($args2[1], $args3[0]), 46 ); is( $g->$list_e, 4 ); } ok( $g->$set_w(@args, 42)); is( $g->$get_w(@args), 42); ok( $g->$has_w(@args)); ok(!$g->$has_w(@anti_args)); ok( $g->$del_w(@args)); ok(!$g->$has_w(@args)); is( $g->$get_w(@args), undef); } Graph-0.9735/t/u_re_sd.t0000644000175000017500000000245713774262024014677 0ustar osboxesosboxesuse Graph::Directed ; use strict; use warnings; use Test::More tests => 2; my $g0 = Graph::Directed->new() ; $g0->add_weighted_edge('A', 'A1', 1) ; $g0->add_weighted_edge('A', 'A2', 1) ; $g0->add_weighted_edge('A1', 'A2', 1) ; $g0->add_weighted_edge('A2', 'A1', 1) ; $g0->add_weighted_edge('A1', 'L1', 100) ; $g0->add_weighted_edge('A2', 'L2', 100) ; $g0->add_weighted_edge('L1', 'B1', 100) ; $g0->add_weighted_edge('L2', 'B2', 100) ; $g0->add_weighted_edge('B1', 'B', 1) ; $g0->add_weighted_edge('B2', 'B', 2) ; $g0->add_weighted_edge('B1', 'B2', 1) ; $g0->add_weighted_edge('B2', 'B1', 1) ; my $SSSP0 = $g0->SPT_Dijkstra(first_root=>'A') ; is($SSSP0, "A-A1,A-A2,A1-L1,A2-L2,B1-B,L1-B1,L2-B2"); my $g1 = Graph::Directed->new() ; $g1->add_weighted_edge('A', 'A1', 1) ; $g1->add_weighted_edge('A', 'A2', 1) ; $g1->add_weighted_edge('A1', 'A2', 1) ; $g1->add_weighted_edge('A2', 'A1', 1) ; $g1->add_weighted_edge('A1', 'L1', 100) ; $g1->add_weighted_edge('A2', 'L2', 100) ; $g1->add_weighted_edge('L1', 'B3', 100) ; $g1->add_weighted_edge('L2', 'B2', 100) ; $g1->add_weighted_edge('B3', 'B', 1) ; $g1->add_weighted_edge('B2', 'B', 2) ; $g1->add_weighted_edge('B3', 'B2', 1) ; $g1->add_weighted_edge('B2', 'B3', 1) ; my $SSSP1 = $g1->SPT_Dijkstra(first_root=>'A') ; is($SSSP1, "A-A1,A-A2,A1-L1,A2-L2,B3-B,L1-B3,L2-B2"); Graph-0.9735/t/21_multivertexed.t0000644000175000017500000000440514741033474016455 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph; my $g = Graph->new(multivertexed => 1); ok $g->multivertexed; is( $g->get_vertex_count('a'), 0 ); ok( $g->add_vertex_by_id('a', 'red') ); is( $g->get_vertex_count('a'), 1 ); for (1,2) { ok( $g->has_vertex('a') ); ok(!$g->has_vertex('b') ); ok( $g->has_vertex_by_id('a', 'red') ); ok(!$g->has_vertex_by_id('a', 'blue') ); } $g->add_vertex_by_id('a', 'blue'); is( $g->get_vertex_count('a'), 2 ); ok( $g->has_vertex_by_id('a', $_) ) for qw(blue red); $g->add_vertex('a'); ok( $g->has_vertex('a') ); ok(!$g->has_vertex('b') ); is( $g->get_vertex_count('a'), 3 ); is( $g->add_vertex_get_id('a'), $_) for 1..3; ok( $g->has_vertex_by_id('a', $_) ) for 0..3; is( $g->get_vertex_count('a'), 6 ); ok( $g->delete_vertex_by_id('a', 'blue') ); ok(!$g->has_vertex_by_id('a', 'blue') ); ok( $g->has_vertex_by_id('a', 'red') ); ok( $g->delete_vertex_by_id('a', 'green') ); ok(!$g->has_vertex_by_id('a', $_)) for qw(blue green); ok( $g->has_vertex_by_id('a', 'red') ); ok( $g->delete_vertex_by_id('a', 'red') ); my $got = [ sort $g->get_multivertex_ids('a') ]; is_deeply $got, [ qw(0 1 2 3) ] or diag explain $got; is( $g->get_vertex_count('a'), 4 ); ok $g->add_edge('a', 'b'); is $g, "a-b"; $got = [ $g->successors('a') ]; is_deeply $got, [ 'b' ] or diag explain $got; $got = [ $g->predecessors('b') ]; is_deeply $got, [ 'a' ] or diag explain $got; is( $g->delete_vertex('a'), 'b' ); ok(!$g->has_vertex_by_id('a', $_) ) for 0..3; is( $g->get_multivertex_ids('a'), undef ); ok $g->add_edge('a', 'b'); is $g, "a-b"; ok( $g->add_vertex_by_id('b', 'bob') ); is $g, "a-b"; ok( $g->delete_vertex_by_id('b', '0') ); is $g, "a-b"; ok( $g->delete_vertex_by_id('b', 'bob') ); is $g, "a"; my $h = Graph->new; eval { $h->add_vertex_by_id("b", "black") }; like($@, qr/add_vertex_by_id: expected multivertexed/); eval { $h->has_vertex_by_id("b", "black") }; like($@, qr/has_vertex_by_id: expected multivertexed/); eval { $h->get_multivertex_ids() }; like($@, qr/get_multivertex_ids: expected multivertexed/); eval { $h->delete_vertex_by_id("b", "black") }; like($@, qr/delete_vertex_by_id: expected multivertexed/); eval { Graph->new( multivertexed => 1, countvertexed => 1 ) }; like ( $@, qr/both countvertexed and multivertexed/ ); done_testing; Graph-0.9735/t/74_random.t0000644000175000017500000000473513774441636015062 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 152; use Graph; my $g = Graph->new; is($g->random_vertex, undef); is($g->random_edge, undef); is($g->random_successor('a'), undef); is($g->random_predecessor('a'), undef); for my $v (0..9) { $g->add_edge($v, 2 * $v); } # print "g = $g\n"; my $N = 30; for (1..$N) { my $v = $g->random_vertex(); ok($v >= 0 && $v <= 18); } for (1..$N) { my $e = $g->random_edge(); my ($u, $v) = @$e; is($v, 2 * $u); } for (1..$N) { my ($u, $v); do { $u = $g->random_vertex(); $v = $g->random_successor($u); } until (defined $v); is($v, 2 * $u); } for (1..$N) { my ($u, $v); do { $v = $g->random_vertex(); $u = $g->random_predecessor($v); } until (defined $u); is($v, 2 * $u); } my $g0 = Graph->random_graph(vertices => [1..30], directed => 0); my $g1 = Graph->random_graph(vertices => 30, directed => 1); my $g2 = Graph->random_graph(vertices => 30, edges => 100); my $g3 = Graph->random_graph(vertices => 30, edges_fill => 0.1); is($g0->vertices, 30); is($g0->edges, 218); ok($g0->undirected); is($g1->vertices, 30); is($g1->edges, 435); ok($g1->directed); is($g2->vertices, 30); is($g2->edges, 100); is($g3->vertices, 30); is($g3->edges, 44); # int(30*29/2*0.1+0.5) my $g4a = Graph->random_graph(vertices => 10, random_seed => 1234); my $g4b = Graph->random_graph(vertices => 10, random_seed => 1234); my $g4c = Graph->random_graph(vertices => 10, random_seed => 1235); my $g4d = Graph->random_graph(vertices => 10, random_seed => 1235); my $g4e = Graph->random_graph(vertices => 10); SKIP: { # http://undeadly.org/cgi?action=article&sid=20141118170028 # http://www.openbsd.org/plus58.html skip("openbsd rand() was undeterministic before Perl 5.20", 2) if $^O eq 'openbsd' && $] < 5.020; is ($g4a, $g4b); is ($g4c, $g4d); } isnt($g4a, $g4c); isnt($g4a, $g4d); isnt($g4a, $g4e); isnt($g4c, $g4e); my $g5 = Graph->random_graph(vertices => 10, edges => 10, random_edge => sub { my ($g, $u, $v, $p) = @_; # Create two "boxes" so that vertices 0..4 # only have edges between each other, ditto # for vertices 5..9. my $a = $u < 5; my $b = $v < 5; return $a == $b ? $p : 0; }); for my $e ($g5->edges) { my ($u, $v) = @$e; my $a = $u < 5; my $b = $v < 5; is($a, $b, "u = $u, v = $v"); } my $g6 = Graph::random_graph(vertices => 10); isa_ok($g6, 'Graph'); is($g6->vertices, 10); Graph-0.9735/t/79_unionfind.t0000644000175000017500000000202114741033474015552 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::UnionFind; my $uf = Graph::UnionFind->new; is_deeply [$uf->find('a')], [undef]; $uf->add('a'); is_deeply [$uf->find('a')], ['a']; $uf->add('b'); is_deeply [$uf->find('a')], ['a']; is_deeply [$uf->find('b')], ['b']; $uf->union(['a', 'b']); # http://rt.cpan.org/NoAuth/Bug.html?id=2627 is_deeply [$uf->find('a')], ['b']; is_deeply [$uf->find('b')], ['b']; $uf->union(['c', 'd']); is_deeply [$uf->find('c')], ['d']; is_deeply [$uf->find('d')], ['d']; is_deeply [$uf->find('e')], [undef]; ok( $uf->same('a', 'b')); ok( $uf->same('b', 'a')); ok( $uf->same('c', 'd')); ok(!$uf->same('a', 'c')); $uf->union(['a', 'd']); ok( $uf->same('a', 'c')); ok(!$uf->same('c', 'e')); # rt.cpan.org #39805: UnionFind: Repeated adds clobbers graph component information my $graph = Graph::UnionFind->new; $graph->add('a'); $graph->union(['a','b']); ok($graph->same('a', 'b')); ok($graph->same('b', 'a')); $graph->add('a'); ok($graph->same('a', 'b')); ok($graph->same('b', 'a')); done_testing; Graph-0.9735/t/45_add_cycle.t0000644000175000017500000000130714102052753015457 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 13; use Graph; my $g = Graph->new; $g->add_cycle("a", "b", "c", "d", "e"); is $g, "a-b,b-c,c-d,d-e,e-a"; ok( $g->has_cycle(qw(a b c d e)) ); ok( ! $g->has_cycle(qw(a c b d e)) ); ok( ! $g->has_cycle(qw(b a c d e)) ); ok( $g->has_cycle(qw(e a b c d)) ); ok( ! $g->has_cycle(qw(e d a b c)) ); $g->delete_cycle("a", "b", "c"); is $g, "c-d,d-e,e-a,b"; my $h = Graph->new(undirected => 1); $h->add_cycle("a", "b", "c", "d", "e"); is $h, "a=b,a=e,b=c,c=d,d=e"; ok( $h->has_cycle(qw(a b c d e)) ); ok( $h->has_cycle(qw(e a b c d)) ); ok( ! $h->has_cycle(qw(a b d c e)) ); $h->delete_cycle("a", "b", "c"); is $h, "a=e,c=d,d=e,b"; ok(! $g->has_cycle()); Graph-0.9735/t/61_connected.t0000644000175000017500000001377314741033474015532 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 264; my %undirected_map = map +($_ => $_), qw( is_connected connected_components connected_component_by_vertex connected_component_by_index same_connected_components connected_graph ); my %directed_map = map { (my $v=$_)=~s/connected/weakly_$&/;($_=>$v) } keys %undirected_map; my %mapping = ('Graph::Undirected' => \%undirected_map, 'Graph::Directed', \%directed_map); use Graph::Undirected; use Graph::Directed; test_graph(@$_) for ( ['Graph::Undirected', {}], ['Graph::Undirected', {unionfind => 1}], ['Graph::Undirected', {unionfind => 1, multiedged => 1}], ['Graph::Directed', {}], ); sub test_graph { my ($class, $args) = @_; my $label = "$class {".join(',', map "$_=>$args->{$_}", sort keys %$args)."}"; my $g0 = $class->new(%$args); my $methmap = $mapping{$class}; ok(!$g0->${ \$methmap->{is_connected} }); is( $g0->${ \$methmap->{connected_components} }, 0); is( $g0->${ \$methmap->{connected_component_by_vertex} }('a'), undef); is( $g0->${ \$methmap->{connected_component_by_index} }(0), undef ); ok(!$g0->${ \$methmap->{same_connected_components} }('a', 'b')); is($g0->${ \$methmap->{connected_graph} }, ''); $g0->add_vertex('a'); ok( $g0->${ \$methmap->{is_connected} }); is( $g0->${ \$methmap->{connected_components} }(), 1); isnt($g0->${ \$methmap->{connected_component_by_vertex} }('a'), undef); is( "@{[ $g0->${ \$methmap->{connected_component_by_index} }(0) ]}", 'a' ); ok(!$g0->${ \$methmap->{same_connected_components} }('a', 'b')); is($g0->${ \$methmap->{connected_graph} }, 'a'); $g0->add_vertex('b'); ok(!$g0->${ \$methmap->{is_connected} }, $label); is( $g0->${ \$methmap->{connected_components} }(), 2, $label); isnt($g0->${ \$methmap->{connected_component_by_vertex} }($_), undef, $label) for qw(a b); isnt $g0->${ \$methmap->{connected_component_by_vertex} }('a'), $g0->${ \$methmap->{connected_component_by_vertex} }('b'), $label; my @c0 = map [ $g0->${ \$methmap->{connected_component_by_index} }(0) ], (1..3); is( @$_, 1, $label ) for @c0; is( "@{$c0[0]}", "@{$c0[$_]}", $label ) for 1, 2; my @c1 = map [ $g0->${ \$methmap->{connected_component_by_index} }(1) ], (1..3); is( @$_, 1, $label ) for @c1; is( "@{$c1[0]}", "@{$c1[$_]}", $label ) for 1, 2; isnt( "@{$c0[0]}", "@{$c1[0]}", $label ); ok( ("@{$c0[0]}" eq "a" && "@{$c1[0]}" eq "b") || ("@{$c0[0]}" eq "b" && "@{$c1[0]}" eq "a"), $label ); ok(!$g0->${ \$methmap->{same_connected_components} }('a', 'b'), $label); is($g0->${ \$methmap->{connected_graph} }, 'a,b', $label); $g0->add_edge(qw(a b)); ok( $g0->${ \$methmap->{is_connected} }); is( $g0->${ \$methmap->{connected_components} }(), 1); isnt($g0->${ \$methmap->{connected_component_by_vertex} }($_), undef) for qw(a b); is($g0->${ \$methmap->{connected_component_by_vertex} }('a'), $g0->${ \$methmap->{connected_component_by_vertex} }('b')); @c0 = map [ $g0->${ \$methmap->{connected_component_by_index} }(0) ], (1..3); is( @$_, 2 ) for @c0; is( "@{$c0[0]}", "@{$c0[$_]}", $label ) for 1, 2; @c1 = map [ $g0->${ \$methmap->{connected_component_by_index} }(1) ], (1..3); is( @$_, 0, $label ) for @c1; is( "@{[ sort @{$c0[0]} ]}", "a b", $label ); ok( $g0->${ \$methmap->{same_connected_components} }('a', 'b')); is($g0->${ \$methmap->{connected_graph} }, 'a+b'); $g0->add_edge(qw(c d)); ok(!$g0->${ \$methmap->{is_connected} }); is( $g0->${ \$methmap->{connected_components} }(), 2); isnt($g0->${ \$methmap->{connected_component_by_vertex} }($_), undef) for qw(a b c d); is($g0->${ \$methmap->{connected_component_by_vertex} }($_->[0]), $g0->${ \$methmap->{connected_component_by_vertex} }($_->[1]), $label) for [qw(a b)], [qw(c d)]; isnt($g0->${ \$methmap->{connected_component_by_vertex} }('a'), $g0->${ \$methmap->{connected_component_by_vertex} }('d'), $label); ok( $g0->${ \$methmap->{same_connected_components} }(@$_), $label) for [qw(a b)], [qw(c d)]; ok(!$g0->${ \$methmap->{same_connected_components} }('a', 'c'), $label); my $g0c = $g0->${ \$methmap->{connected_graph} }; is($g0c, 'a+b,c+d'); is("@{[sort @{ $g0c->get_vertex_attribute('a+b', 'subvertices') }]}", "a b"); is("@{[sort @{ $g0c->get_vertex_attribute('c+d', 'subvertices') }]}", "c d"); is($g0c->get_vertex_attribute('b+a', 'subvertices'), undef); } my $g4 = Graph::Directed->new; eval { $g4->is_connected }; like($@, qr/Graph::is_connected: expected undirected graph, got directed/); eval { $g4->connected_components }; like($@, qr/Graph::connected_components: expected undirected graph, got directed/); eval { $g4->connected_component_by_vertex }; like($@, qr/Graph::connected_component_by_vertex: expected undirected graph, got directed/); eval { $g4->connected_component_by_index }; like($@, qr/Graph::connected_component_by_index: expected undirected graph, got directed/); eval { $g4->same_connected_components }; like($@, qr/Graph::same_connected_components: expected undirected graph, got directed/); eval { $g4->connected_graph }; like($@, qr/Graph::connected_graph: expected undirected graph, got directed/); my $g5 = Graph::Undirected->new; eval { $g5->is_weakly_connected }; like($@, qr/Graph::is_weakly_connected: expected directed graph, got undirected/); eval { $g5->weakly_connected_components }; like($@, qr/Graph::weakly_connected_components: expected directed graph, got undirected/); eval { $g5->weakly_connected_component_by_vertex }; like($@, qr/Graph::weakly_connected_component_by_vertex: expected directed graph, got undirected/); eval { $g5->weakly_connected_component_by_index }; like($@, qr/Graph::weakly_connected_component_by_index: expected directed graph, got undirected/); eval { $g5->same_weakly_connected_components }; like($@, qr/Graph::same_weakly_connected_components: expected directed graph, got undirected/); eval { $g5->weakly_connected_graph }; like($@, qr/Graph::weakly_connected_graph: expected directed graph, got undirected/); Graph-0.9735/t/71_spt.t0000644000175000017500000001273014741033474014367 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph; use Graph::Directed; use Graph::Undirected; my $g = Graph::Directed->new; $g->add_weighted_path("b", 1, "f", 2, "c", 3, "d", 3, "f", 2, "g", 2, "e"); $g->add_weighted_edges("d", "e", 3, "g", "a", 3, "g", "f", 2, "b", "a", 3, "h", "b", 1, "h", "c", 1); my $sgb_d = $g->SPT_Dijkstra(first_root => "b"); is $sgb_d->get_graph_attribute('SPT_Dijkstra_root'), "b"; is( $sgb_d, "b-a,b-f,c-d,f-c,f-g,g-e" ); my $sgb_bf = $g->SPT_Bellman_Ford(first_root => "b"); is $sgb_bf->get_graph_attribute('SPT_Bellman_Ford_root'), "b"; is( $sgb_bf, "b-a,b-f,c-d,f-c,f-g,g-e" ); my $sgh_d = $g->SPT_Dijkstra(first_root => sub { "h" }); is $sgh_d->get_graph_attribute('SPT_Dijkstra_root'), "h"; is( $sgh_d, "b-a,b-f,c-d,f-g,g-e,h-b,h-c" ); my $sga_d = $g->SPT_Dijkstra(start => "a"); is $sga_d->get_graph_attribute('SPT_Dijkstra_root'), "a"; is( $sga_d, '' ); my $u = Graph::Undirected->new; $u->add_weighted_path("b", 1, "f", 2, "c", 3, "d", 3, "f", 2, "g", 2, "e"); $u->add_weighted_edges("d", "e", 3, "g", "a", 3, "g", "f", 2, "b", "a", 3, "h", "b", 1, "h", "c", 1); my $sub = $u->SPT_Dijkstra(first_root => "b"); is $sub->get_graph_attribute('SPT_Dijkstra_root'), "b"; is( $sub, "a=b,b=f,b=h,c=h,d=f,e=g,f=g" ); my $suh = $u->SPT_Dijkstra(first_root => "h"); is $suh->get_graph_attribute('SPT_Dijkstra_root'), "h"; is( $suh, "a=b,b=f,b=h,c=d,c=h,e=g,f=g" ); my $sua = $u->SPT_Dijkstra(first_root => "a"); is $sua->get_graph_attribute('SPT_Dijkstra_root'), "a"; ok( $sua eq "a=b,a=g,b=f,b=h,c=h,d=e,e=g" || $sua eq "a=b,a=g,b=f,b=h,c=h,d=f,e=g" || $sua eq "a=b,a=g,c=f,c=h,d=e,e=g,f=g" || $sua eq "a=b,a=g,c=f,c=h,d=f,e=g,f=g" ); # Sedgewick, Algorithms in C, Third Edition # Chapter 21, "Shortest Paths", Figure 21.10 (p 282) for my $is_multi (0,1) { my $g2 = Graph::Directed->new(multiedged=>$is_multi); my $awe = 'add_weighted_edge'.($is_multi ? '_by_id' : ''); my @id = $is_multi ? 'ID' : (); $g2->$awe(@$_[0,1], @id, $_->[2]) for [0,1,0.41], [1,2,0.51], [2,3,0.50], [4,3,0.36], [3,5,0.38], [3,0,0.45], [0,5,0.29], [5,4,0.21], [1,4,0.32], [4,2,0.32], [5,1,0.29]; my @s2_tests = ( [0,0,"0"], [0,1,"0 1"], [0,2,"0 5 4 2"], [0,3,"0 5 4 3"], [0,4,"0 5 4"], [0,5,"0 5"], [1,0,"1 4 3 0"], [1,1,"1"], [1,2,"1 2"], [1,3,"1 4 3"], [1,4,"1 4"], [1,5,"1 4 3 5"] ); is("@{[$g2->SP_Dijkstra(@$_[0,1])]}", $_->[2], "path @$_[0,1]") for @s2_tests; my $s2_di = $g2->SPT_Dijkstra(first_root => "0"); is( $s2_di, "0-1,0-5,4-2,4-3,5-4" ); is($s2_di->get_edge_attribute(@$_[0,1], 'weight'), $_->[2], "edge @$_[0,1]") for [0,1,0.41], [0,5,0.29], [5,4,0.50], [4,3,0.86], [4,2,0.82], [0,3,undef], [3,5,undef], [5,1,undef], [1,2,undef], [2,3,undef], [1,0,undef], [5,0,undef], [4,5,undef], [3,4,undef], [2,4,undef], [3,0,undef], [5,3,undef], [1,5,undef], [2,1,undef], [3,2,undef]; is($s2_di->get_vertex_attribute(@$_[0,1]), $_->[2], "vertex @$_[0,1]") for [0,'weight',undef], [1,'weight',0.41], [2,'weight',0.82], [3,'weight',0.86], [4,'weight',0.50], [5,'weight',0.29], [0,'p',undef], [1,'p',0], [2,'p',4], [3,'p',4], [4,'p',5], [5,'p',0]; is +($s2_di->get_edge_attribute_all(0, 1, 'weight'))[0], 0.41, "get_edge_attribute_all"; my $s2_bf = $g2->SPT_Bellman_Ford(first_root => "0"); is( $s2_bf, "0-1,0-5,4-2,4-3,5-4" ); is($s2_bf->get_edge_attribute(@$_[0,1], 'weight'), $_->[2], "edge @$_[0,1]") for [0,1,0.41], [0,5,0.29], [5,4,0.21], [4,3,0.36], [4,2,0.32], [0,3,undef], [3,5,undef], [5,1,undef], [1,2,undef], [2,3,undef], [1,0,undef], [5,0,undef], [4,5,undef], [3,4,undef], [2,4,undef], [3,0,undef], [5,3,undef], [1,5,undef], [2,1,undef], [3,2,undef]; is($s2_bf->get_vertex_attribute(@$_[0,1]), $_->[2], "vertex @$_[0,1]") for [0,'weight',undef], [1,'weight',0.41], [2,'weight',0.82], [3,'weight',0.86], [4,'weight',0.50], [5,'weight',0.29], [0,'p',undef], [1,'p',0], [2,'p',4], [3,'p',4], [4,'p',5], [5,'p',0]; is("@{[$g2->SP_Bellman_Ford(@$_[0,1])]}", $_->[2], "path @$_[0,1]") for @s2_tests; } my $g3 = Graph::Directed->new; $g3->add_weighted_path(qw(a 1 b 2 c 3 d -1 e 4 f)); my $s3_da = eval { $g3->SPT_Dijkstra(first_root => "a") }; like($@, qr/Graph::SPT_Dijkstra: edge d-e is negative \(-1\)/); is( $s3_da, undef ); my $s3_bf = eval { $g3->SPT_Bellman_Ford(first_root => "a") }; is($@, ''); is( $s3_bf, "a-b,b-c,c-d,d-e,e-f"); $g3->add_weighted_path(qw(b -2 a)); $s3_bf = eval { $g3->SPT_Bellman_Ford(first_root => "a") }; like($@, qr/Graph::SPT_Bellman_Ford: negative cycle exists/); is( $s3_bf, undef ); # http://rt.cpan.org/NoAuth/Bug.html?id=516 my $g4 = new Graph::Undirected; $g4->add_weighted_edge("r1", "l1", 1); my $d4 = $g4->SSSP_Dijkstra("r1"); is($g4, "l1=r1"); is($d4, "l1=r1"); # Nathan Goodman my $g5 = Graph::Directed->new; $g5->add_path(qw(0 1 2)); my $sg5 = $g5->SPT_Dijkstra(first_root => "0"); is($sg5, "0-1,1-2"); { my $g = Graph::Directed->new(refvertexed => 1); $g->add_edge(qw(a b)); $g->add_edge(qw(a c)); $g->add_edge(qw(c d)); $g->add_edge(qw(c e)); $g->add_edge(qw(e f)); my $r = [1, 2, 3]; $g->add_edge('f', $r); my $s0 = $g->SPT_Dijkstra(first_root => 'a'); ok($s0->has_vertex('f')); my @e0 = $s0->successors('f'); is(@e0, 1); is_deeply($e0[0], $r); my $s1 = $g->SPT_Bellman_Ford(first_root => 'a'); ok($s1->has_vertex('f')); my @e1 = $s1->successors('f'); is(@e1, 1); is($e1[0], $r); } done_testing; Graph-0.9735/t/66_simple.t0000644000175000017500000001131013774262024015047 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 96; use Graph; my ($g0, $g1, $g2, $g3, $g4, $g5, $g6, $g7, $g8, $g9, $ga, $gb, $gc, $gd, $ge, $gf); $g0 = Graph->new; $g1 = Graph->new(countedged => 1); $g2 = Graph->new->add_edge(qw(a a)); $g3 = Graph->new(countedged => 1)->add_edge(qw(a a)); $g4 = Graph->new->add_edge(qw(a b)); $g5 = Graph->new(countedged => 1)->add_edge(qw(a b)); $g6 = Graph->new->add_edge(qw(a a))->add_edge(qw(a b)); $g7 = Graph->new(countedged => 1)->add_edge(qw(a a))->add_edge(qw(a b)); $g8 = Graph->new->add_edge(qw(a b)); $g9 = Graph->new(countedged => 1)->add_edge(qw(a b)); $ga = Graph->new->add_edge(qw(a a))->add_edge(qw(a b)); $gb = Graph->new(countedged => 1)->add_edge(qw(a a))->add_edge(qw(a b)); $gc = Graph->new->add_edge(qw(a b))->add_edge(qw(a b)); $gd = Graph->new(countedged => 1)->add_edge(qw(a b))->add_edge(qw(a b)); $ge = Graph->new->add_edge(qw(a a))->add_edge(qw(a b))->add_edge(qw(a b)); $gf = Graph->new(countedged => 1)->add_edge(qw(a a))->add_edge(qw(a b))->add_edge(qw(a b)); ok( $g0->is_simple_graph); ok(!$g0->is_pseudo_graph); ok(!$g0->is_multi_graph); ok( $g1->is_simple_graph); ok(!$g1->is_pseudo_graph); ok(!$g1->is_multi_graph); ok( $g2->is_simple_graph); ok( $g2->is_pseudo_graph); # a a ok(!$g2->is_multi_graph); ok( $g3->is_simple_graph); ok( $g3->is_pseudo_graph); # a a ok(!$g3->is_multi_graph); ok( $g4->is_simple_graph); ok(!$g4->is_pseudo_graph); ok(!$g4->is_multi_graph); ok( $g5->is_simple_graph); ok(!$g5->is_pseudo_graph); ok(!$g5->is_multi_graph); ok( $g6->is_simple_graph); ok( $g6->is_pseudo_graph); # a a once ok(!$g6->is_multi_graph); ok( $g7->is_simple_graph); ok( $g7->is_pseudo_graph); # a a once ok(!$g7->is_multi_graph); ok( $g8->is_simple_graph); ok(!$g8->is_pseudo_graph); ok(!$g8->is_multi_graph); ok( $g9->is_simple_graph); ok(!$g9->is_pseudo_graph); ok(!$g9->is_multi_graph); ok( $ga->is_simple_graph); ok( $ga->is_pseudo_graph); # a a once ok(!$ga->is_multi_graph); ok( $gb->is_simple_graph); ok( $gb->is_pseudo_graph); # a a once ok(!$gb->is_multi_graph); ok( $gc->is_simple_graph); ok(!$gc->is_pseudo_graph); ok(!$gc->is_multi_graph); ok(!$gd->is_simple_graph); # a b twice ok( $gd->is_pseudo_graph); # a b twice ok( $gd->is_multi_graph); # a b twice ok( $ge->is_simple_graph); ok( $ge->is_pseudo_graph); # a a once ok(!$ge->is_multi_graph); ok(!$gf->is_simple_graph); ok( $gf->is_pseudo_graph); # a a once, a b twice ok(!$gf->is_multi_graph); # a a once, a b twice $g0 = Graph->new; $g1 = Graph->new(multiedged => 1); $g2 = Graph->new->add_edge(qw(a a)); $g3 = Graph->new(multiedged => 1)->add_edge(qw(a a)); $g4 = Graph->new->add_edge(qw(a b)); $g5 = Graph->new(multiedged => 1)->add_edge(qw(a b)); $g6 = Graph->new->add_edge(qw(a a))->add_edge(qw(a b)); $g7 = Graph->new(multiedged => 1)->add_edge(qw(a a))->add_edge(qw(a b)); $g8 = Graph->new->add_edge(qw(a b)); $g9 = Graph->new(multiedged => 1)->add_edge(qw(a b)); $ga = Graph->new->add_edge(qw(a a))->add_edge(qw(a b)); $gb = Graph->new(multiedged => 1)->add_edge(qw(a a))->add_edge(qw(a b)); $gc = Graph->new->add_edge(qw(a b))->add_edge(qw(a b)); $gd = Graph->new(multiedged => 1)->add_edge(qw(a b))->add_edge(qw(a b)); $ge = Graph->new->add_edge(qw(a a))->add_edge(qw(a b))->add_edge(qw(a b)); $gf = Graph->new(multiedged => 1)->add_edge(qw(a a))->add_edge(qw(a b))->add_edge(qw(a b)); ok( $g0->is_simple_graph); ok(!$g0->is_pseudo_graph); ok(!$g0->is_multi_graph); ok( $g1->is_simple_graph); ok(!$g1->is_pseudo_graph); ok(!$g1->is_multi_graph); ok( $g2->is_simple_graph); ok( $g2->is_pseudo_graph); # a a ok(!$g2->is_multi_graph); ok( $g3->is_simple_graph); ok( $g3->is_pseudo_graph); # a a ok(!$g3->is_multi_graph); ok( $g4->is_simple_graph); ok(!$g4->is_pseudo_graph); ok(!$g4->is_multi_graph); ok( $g5->is_simple_graph); ok(!$g5->is_pseudo_graph); ok(!$g5->is_multi_graph); ok( $g6->is_simple_graph); ok( $g6->is_pseudo_graph); # a a once ok(!$g6->is_multi_graph); ok( $g7->is_simple_graph); ok( $g7->is_pseudo_graph); # a a once ok(!$g7->is_multi_graph); ok( $g8->is_simple_graph); ok(!$g8->is_pseudo_graph); ok(!$g8->is_multi_graph); ok( $g9->is_simple_graph); ok(!$g9->is_pseudo_graph); ok(!$g9->is_multi_graph); ok( $ga->is_simple_graph); ok( $ga->is_pseudo_graph); # a a once ok(!$ga->is_multi_graph); ok( $gb->is_simple_graph); ok( $gb->is_pseudo_graph); # a a once ok(!$gb->is_multi_graph); ok( $gc->is_simple_graph); ok(!$gc->is_pseudo_graph); ok(!$gc->is_multi_graph); ok(!$gd->is_simple_graph); # a b twice ok( $gd->is_pseudo_graph); # a b twice ok( $gd->is_multi_graph); # a b twice ok( $ge->is_simple_graph); ok( $ge->is_pseudo_graph); # a a once ok(!$ge->is_multi_graph); ok(!$gf->is_simple_graph); ok( $gf->is_pseudo_graph); # a a once, a b twice ok(!$gf->is_multi_graph); # a a once, a b twice Graph-0.9735/t/60_bfs.t0000644000175000017500000001222613774262024014331 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 93; use Graph::Directed; use Graph::Undirected; use Graph::Traversal::BFS; my $g0 = Graph::Undirected->new; my $g1 = Graph::Directed->new; my $g2 = Graph::Undirected->new; # cyclic my $g3 = Graph::Undirected->new; # unconnetced my $g4 = Graph::Directed->new; # cyclic my $g5 = Graph::Directed->new; # cyclic $g0->add_path(qw(a b c)); $g0->add_path(qw(a b d)); $g0->add_path(qw(a e f)); $g1->add_path(qw(a b c)); $g1->add_path(qw(a b d)); $g1->add_path(qw(a e f)); $g2->add_cycle(qw(a b c)); $g3->add_path(qw(a b c)); $g3->add_path(qw(d e f)); $g4->add_cycle(qw(a)); $g5->add_cycle(qw(a b c)); sub simple { my $g = shift; my @v = $g->vertices; is(@_, @v, "vertices"); my %v; $v{$_} ++ for @_; is(scalar(grep { ($v{$_} || 0) != 1 } @v), 0, "... once"); } { my $t = Graph::Traversal::BFS->new($g0); is($t->unseen, $g0->vertices, "fresh traversal"); is($t->seen, 0); is($t->seeing, 0); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->bfs; simple($g0, @t0); simple($g0, @t1); simple($g0, @t2); is($t->graph, $g0, "graph"); } { my @pre; my @post; my $t = Graph::Traversal::BFS->new($g0, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->bfs; simple($g1, @t0); simple($g1, @t1); simple($g1, @t2); is("@pre", "a b e c d f", "pre"); is("@post", "a b e c d f", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "a", "roots"); } { my @pre; my @post; my $t = Graph::Traversal::BFS->new($g1, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1, first_root => 'b'); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->bfs; simple($g1, @t0); simple($g1, @t1); simple($g1, @t2); is("@pre", "b c d a e f", "pre"); is("@post", "b c d a e f", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "b a", "roots"); } { my $t0 = Graph::Traversal::BFS->new($g0, next_alphabetic => 1); is($t0->next, "a", "scalar next"); $t0->terminate; is($t0->next, undef, "terminate"); $t0->reset; is($t0->next, "a", "after reset scalar next"); } { my @pre; my @post; my $t = Graph::Traversal::BFS->new($g2, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->postorder; simple($g2, @t0); simple($g2, @t1); simple($g2, @t2); is("@pre", "a b c", "pre"); is("@post", "a b c", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 3, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c", "seen all"); is("@{[$t->roots]}", "a", "roots"); } { my @pre; my @post; my $t = Graph::Traversal::BFS->new($g3, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->postorder; simple($g3, @t0); simple($g3, @t1); simple($g3, @t2); is("@pre", "a b c d e f", "pre"); is("@post", "a b c d e f", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "a d", "roots"); } { my @pre; my @post; my $t = Graph::Traversal::BFS->new($g0, first_root => 'a', pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_successor => sub { shift; (reverse sort keys %{ $_[0] })[0] }); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->bfs; simple($g1, @t0); simple($g1, @t1); simple($g1, @t2); is("@pre", "a e b f d c", "pre"); is("@post", "a e b f d c", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "a", "roots"); } Graph-0.9735/t/82_cycle.t0000644000175000017500000000111613774262024014656 0ustar osboxesosboxesuse strict; use warnings; use Graph; use Test::More tests => 1; eval { require Devel::Cycle }; SKIP: { skip("no Devel::Cycle", 1) if $@; import Devel::Cycle; my $g = Graph->new; $g->add_edge(qw(a b)); $g->add_edge(qw(b c)); $g->add_edge(qw(c d)); $g->add_edge(qw(c e)); $g->add_cycle(qw(e f g)); # This is not a true cycle if weakrefs work. my $out = tie *STDOUT, 'FakeOut'; find_cycle($g); is($$out, undef); } package FakeOut; sub TIEHANDLE { bless(\(my $text), $_[0]); } sub PRINT { my $self = shift; $$self .= join('', @_); } Graph-0.9735/t/u_at2.t0000644000175000017500000000242413774262024014263 0ustar osboxesosboxesuse Test::More tests => 4; use strict; use warnings; use Graph::Undirected; my $g = Graph::Undirected->new; while () { if (/(\S+)\s+(\S+)/) { $g->add_edge($1, $2); } } my $src = "NRTPZ5WOkg"; my $dst = "ObpULOKHH0"; my @u = qw(NRTPZ5WOkg vJqD6skXdS TNgfs0KcUd qI7Po3TrBA ZiPHVw509v bnDd3VuBpJ ObpULOKHH0); for (1, 2) { print "# finding SP_Dijkstra path between $src and $dst\n"; my @v = $g->SP_Dijkstra($src, $dst); is_deeply(\@v, \@u); foreach (@v) { print "# $_\n"; } { print "# finding APSP_Floyd_Warshall path between $src and $dst\n"; my $apsp = $g->APSP_Floyd_Warshall(); my @v = $apsp->path_vertices($src, $dst); is_deeply(\@v, \@u); foreach (@v) { print "# $_\n"; } } } __END__ Cwx0nn09zg pDRu7q707v ENQH4XaK3o bnuPl9BV2A J6UG5junOo UNQcGQ7Yxs J6UG5junOo vZJeF6iWP5 JU5fopQvgK Cqw1sHOUJ1 JU5fopQvgK Cwx0nn09zg NRTPZ5WOkg Cqw1sHOUJ1 NRTPZ5WOkg vJqD6skXdS ObpULOKHH0 bnDd3VuBpJ Ody8vNNKOn bnDd3VuBpJ Ody8vNNKOn nONYKw3o4X RlBKE0bWDY p5gUeVx6pZ UNQcGQ7Yxs els2v8URGW ZiPHVw509v qI7Po3TrBA bnDd3VuBpJ ZiPHVw509v bnuPl9BV2A eiTqtOz3aL eiTqtOz3aL pDRu7q707v els2v8URGW IDU5MGPovY p5gUeVx6pZ IDU5MGPovY pWZsc88Hfm RlBKE0bWDY pWZsc88Hfm nONYKw3o4X qI7Po3TrBA TNgfs0KcUd vJqD6skXdS TNgfs0KcUd vZJeF6iWP5 ENQH4XaK3o Graph-0.9735/t/u_bo_apx.t0000644000175000017500000000122513774262024015043 0ustar osboxesosboxesuse Test::More tests => 5; use Graph::Undirected; use strict; my $g = Graph::Undirected->new; $g->add_edge(qw(a b)); $g->add_edge(qw(a c)); $g->add_edge(qw(b c)); $g->add_edge(qw(b d)); $g->add_edge(qw(d e)); $g->add_edge(qw(d f)); $g->add_edge(qw(e f)); my @a1 = sort $g->articulation_points(); is("@a1", "b d"); $g->add_edge(qw(b b)); my @a2 = sort $g->articulation_points(); is("@a2", "b d"); $g->add_edge(qw(d d)); my @a3 = sort $g->articulation_points(); is("@a3", "b d"); $g->add_edge(qw(a a)); my @a4 = sort $g->articulation_points(); is("@a4", "b d"); $g->add_edge(qw(f f)); my @a5 = sort $g->articulation_points(); is("@a5", "b d"); Graph-0.9735/t/MyDGraph.pm0000644000175000017500000000015013774262024015067 0ustar osboxesosboxespackage DGraph; use strict; use warnings; require Graph::Directed; @DGraph::ISA=qw(Graph::Directed); 1; Graph-0.9735/t/09_eq.t0000644000175000017500000000106713774262024014170 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 14; use Graph; my $g = Graph->new; $g->add_vertices(qw(a b c d)); $g->add_path(qw(b c e f)); my $h = Graph->new; $h->add_vertices(qw(a b c d)); $h->add_path(qw(b c e f)); my $i = $h->new; $i->add_vertex(qw(g)); is($g, "b-c,c-e,e-f,a,d"); is("b-c,c-e,e-f,a,d", $g); ok($g->eq("b-c,c-e,e-f,a,d")); is($g, $h); is($h, $g); ok($g->eq($h)); ok($h->eq($g)); isnt($i, "b-c,c-e,e-f,a,d"); isnt("b-c,c-e,e-f,a,d", $i); ok($i->ne("b-c,c-e,e-f,a,d")); isnt($g, $i); isnt($i, $g); ok($g->ne($i)); ok($i->ne($g)); Graph-0.9735/t/39_edges_at.t0000644000175000017500000000214414102052753015326 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 14; use Graph; my $g = Graph->new(hyperedged => 1, directed => 0); $g->add_edge("a", "b"); $g->add_edge("d" ,"e"); $g->add_edge("a", "b", "c"); sub deref { my $r = shift; ref $r ? "[" . join(" ", sort map { deref($_) } @$r) . "]" : $_; } sub at { join(" ", sort map { deref($_) } $g->edges_at(@_)); } is( at("a"), "[a b c] [a b]"); is( at("b"), "[a b c] [a b]"); is( at("c"), "[a b c]"); is( at("d"), "[d e]"); is( at("e"), "[d e]"); is( at("x"), ""); { # [cpan #11543] self-edges reported twice in edges_at use Graph::Directed; my $g1 = new Graph::Directed(); $g1->add_edge(0,0); is(scalar $g1->edges_at(0), 1); is("@{ ($g1->edges_at(0))[0] }", "0 0"); } { my $g2 = new Graph::Directed(); $g2->add_edge(1,1); $g2->add_edge(1,2); my @e1 = $g2->edges_at(1); is(@e1, 2); @e1[1, 0] = @e1[0, 1] if $e1[0]->[1] > $e1[1]->[1]; is("@{ $e1[0] }", "1 1"); is("@{ $e1[1] }", "1 2"); my @e2 = $g2->edges_at(2); is(@e2, 1); is("@{ $e2[0] }", "1 2"); my @e3 = $g2->edges_at(3); is(@e3, 0); } Graph-0.9735/t/51_multivertex_attributes.t0000644000175000017500000001074114102052753020405 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 62; use Graph; my $g = Graph->new(multivertexed => 1); $g->add_vertex_by_id("a", "hot"); ok( !$g->has_vertex_attributes_by_id("a", "hot") ); ok( !$g->has_vertex_attributes_by_id("a", "hot") ); ok( $g->set_vertex_attribute_by_id("a", "hot", "color", "red") ); ok( $g->has_vertex_attribute_by_id("a", "hot", "color") ); ok( $g->has_vertex_attribute_by_id("a", "hot", "color") ); ok( $g->has_vertex_attributes_by_id("a", "hot") ); ok( $g->has_vertex_attributes_by_id("a", "hot") ); is( $g->get_vertex_attribute_by_id("a", "hot", "color"), "red" ); is( $g->get_vertex_attribute_by_id("a", "hot", "color"), "red" ); is( $g->get_vertex_attribute_by_id("a", "hot", "colour"), undef ); is( $g->get_vertex_attribute_by_id("a", "hot", "colour"), undef ); ok( $g->set_vertex_attribute_by_id("a", "hot", "color", "green") ); ok( $g->has_vertex_attributes_by_id("a", "hot") ); ok( $g->has_vertex_attributes_by_id("a", "hot") ); is( $g->get_vertex_attribute_by_id("a", "hot", "color"), "green" ); is( $g->get_vertex_attribute_by_id("a", "hot", "color"), "green" ); my $attr = $g->get_vertex_attributes_by_id("a", "hot"); my @name = $g->get_vertex_attribute_names_by_id("a", "hot"); my @val = $g->get_vertex_attribute_values_by_id("a", "hot"); is_deeply $attr, { color => "green" }; is_deeply \@name, [ "color" ]; is_deeply \@val, [ "green" ]; ok( $g->set_vertex_attribute_by_id("a", "hot", "taste", "rhubarb") ); ok( $g->has_vertex_attributes_by_id("a", "hot") ); ok( $g->has_vertex_attributes_by_id("a", "hot") ); is( $g->get_vertex_attribute_by_id("a", "hot", "taste"), "rhubarb" ); is( $g->get_vertex_attribute_by_id("a", "hot", "taste"), "rhubarb" ); is( $g->get_vertex_attribute_by_id("a", "hot", "color"), "green" ); is( $g->get_vertex_attribute_by_id("a", "hot", "taste"), "rhubarb" ); $attr = $g->get_vertex_attributes_by_id("a", "hot"); @name = sort $g->get_vertex_attribute_names_by_id("a", "hot"); @val = sort $g->get_vertex_attribute_values_by_id("a", "hot"); is_deeply $attr, { color => "green", taste => "rhubarb" }; is_deeply \@name, [ "color", "taste" ]; is_deeply \@val, [ "green", "rhubarb" ]; is_deeply(($g->as_hashes)[0], { a => { hot => { color => "green", taste => "rhubarb" } } }); ok( $g->delete_vertex_attribute_by_id("a", "hot", "color" ) ); ok( !$g->has_vertex_attribute_by_id("a", "hot", "color" ) ); ok( $g->has_vertex_attributes_by_id("a", "hot") ); is( $g->get_vertex_attribute_by_id("a", "hot", "taste"), "rhubarb" ); ok( $g->delete_vertex_attributes_by_id("a", "hot") ); ok( !$g->has_vertex_attributes_by_id("a", "hot") ); is( $g->get_vertex_attribute_by_id("a", "hot", "taste"), undef ); ok( !$g->delete_vertex_attribute_by_id("a", "hot", "taste" ) ); ok( $g->delete_vertex_attributes_by_id("a", "hot") ); $attr = $g->get_vertex_attributes_by_id("a", "hot"); @name = $g->get_vertex_attribute_names_by_id("a", "hot"); @val = $g->get_vertex_attribute_values_by_id("a", "hot"); is_deeply $attr, undef; is_deeply \@name, []; is_deeply \@val, []; is( $g->vertices, 1 ); # Deleting attributes does not delete vertex $g->add_weighted_vertex_by_id("b", "cool", 42); ok( $g->has_vertex_by_id("b", "cool") ); is( $g->get_vertex_weight_by_id("b", "cool"), 42 ); is( $g->vertices, 2 ); $g->add_weighted_vertices_by_id("b", 43, "c", 44, "cool"); is( $g->get_vertex_weight_by_id("b", "cool"), 43 ); is( $g->get_vertex_weight_by_id("c", "cool" ), 44 ); is( $g->vertices, 3 ); ok($g->set_vertex_attributes_by_id('a', 'hot', { 'color' => 'pearl', 'weight' => 'heavy' })); $attr = $g->get_vertex_attributes_by_id('a', 'hot'); is_deeply $attr, { color => "pearl", weight => 'heavy' }; ok( $g->set_vertex_weight_by_id("a", "hot", 42)); is( $g->get_vertex_weight_by_id("a", "hot"), 42); ok( $g->has_vertex_weight_by_id("a", "hot")); ok(!$g->has_vertex_weight_by_id("x", "hot")); ok( $g->delete_vertex_weight_by_id("a", "hot")); ok(!$g->has_vertex_weight_by_id("a", "hot")); is( $g->get_vertex_weight_by_id("a", "hot"), undef); ok( $g->set_vertex_attribute_by_id("a", 0, "zero", "absolute") ); my $got = [ sort $g->vertices ]; is_deeply($got, [qw(a a b c)]) or diag explain $got; my $h = Graph->new(multivertexed => 1); eval { $h->set_vertex_attribute("foo", "color", "gold") }; like($@, qr/expected non-multivertexed/); $h->ingest($g); $got = ($h->as_hashes)[0]; is_deeply($got, { a => { hot => { color => 'pearl' }, 0 => { "zero" => "absolute" } }, b => { cool => { weight => 43 } }, c => { cool => { weight => 44 } } }) or diag explain $got; Graph-0.9735/t/65_ref.t0000644000175000017500000003054114741033474014340 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph; use Graph::AdjacencyMap qw(:flags :fields); use Graph::AdjacencyMap::Light; use Math::Complex; use List::Util qw(uniq); my $t = [1, 2]; my $u = bless { 3, 4 }, "Ubu"; my $v = cplx(3, 4); my $z = cplx(4, 5); my @MAP_TESTS = ( [ 'Graph::AdjacencyMap::Light', [0, 1], ['a'] ], [ 'Graph::AdjacencyMap::Light', [0, 2], [[qw(2 7)]] ], [ 'Graph::AdjacencyMap::Light', [_UNORD, 2], [[qw(2 7)]] ], [ 'Graph::AdjacencyMap', [_REF, 1], [$t] ], [ 'Graph::AdjacencyMap', [_REF, 1], [$v] ], [ 'Graph::AdjacencyMap', [0, 0], [[[qw(2 7 9)], [qw(12 13)]]] ], [ 'Graph::AdjacencyMap', [_UNORD, 0], [[qw()]] ], [ 'Graph::AdjacencyMap', [_UNORD, 0], [[qw(2 7 9 12)]] ], [ 'Graph::AdjacencyMap', [0, 2], [[qw(2 7)]] ], [ 'Graph::AdjacencyMap', [_MULTI, 1], ['a', 'b'] ], [ 'Graph::AdjacencyMap', [_MULTI, 2], [[qw(2 7)], 'c'] ], [ 'Graph::AdjacencyMap', [_COUNT, 1], [qw(a)] ], [ 'Graph::AdjacencyMap', [_COUNT, 2], [[qw(2 7)]] ], [ 'Graph::AdjacencyMap', [_UNORD, 2], [[qw(2 7)]] ], ); my @METHOD_MAP = ( { has => 'has_path', del => 'del_path', set => 'set_paths' }, { has => 'has_path_by_multi_id', del => 'del_path_by_multi_id', set => 'set_path_by_multi_id' }, ); sub test_adjmap { my ($class, $args, $path_maybe_id) = @_; my ($path, $maybe_id) = @$path_maybe_id; my ($m, $flags, $arity) = ($class->_new(@$args), @$args); my ($is_multi, $is_unord) = ($m->_is_MULTI ? 1 : 0, $m->_is_UNORD); my $maybe_count = $m->_is_COUNT ? 2 : 1; my $map = $METHOD_MAP[ $is_multi ]; my @paths_to_create = map $arity == 1 ? $_ : [ ($_) x ($arity || 1) ], qw(22 23); my @paths_to_create_maybe_id = map [ $_, $is_multi ? 0 : () ], @paths_to_create; my $label = "$class(@{[Graph::AdjacencyMap::_stringify_fields($flags)]}, $arity) @{[$m->_dumper($path)]}"; my $got = [ $m->get_ids_by_paths([ $path ], 0) ]; is_deeply $got, [], $label or diag explain $got; $got = [ $m->get_ids_by_paths([ [$path] ], 0, 1) ]; is_deeply $got, [], $label or diag explain $got; ok( !$m->has_any_paths, $label ); is( $m->${ \$map->{has} }(@$path_maybe_id), undef, $label ); $got = [ $m->${ \$map->{set} }(@$path_maybe_id) ]; is_deeply( $got, [ 0, $is_multi ? $maybe_id : () ], $label ) or diag explain $got; if ($args->[0] == _REF) { my $m2 = Graph::_deep_copy_best($m); $m2->reindex; isnt( $m2->${ \$map->{has} }($m2->[ _i ][0]), undef, $label ); } is $m->_set_path_attr(@$path_maybe_id, 'say', 'hi'), 'hi', $label; ok $m->_has_path_attrs(@$path_maybe_id), $label; ok $m->_del_path_attrs(@$path_maybe_id), $label; ok( $m->has_any_paths, $label ); isnt( $m->${ \$map->{has} }(@$path_maybe_id), undef, $label ); $m->${ \$map->{set} }(@$path_maybe_id); # second time is( $m->_get_path_count($path), $maybe_count, $label ); my @ids = $m->ids; isnt 0+@ids, 0, $label; ok( $m->${ \$map->{del} }(@$path_maybe_id), $label ) for 1..$maybe_count; ok( !$m->has_any_paths, $label ) or diag explain $m; is( $m->${ \$map->{has} }(@$path_maybe_id), undef, $label ); is( $m->_get_path_count($path), 0, $label ); $got = [ $m->${ \$map->{set} }(@$path_maybe_id) ]; is_deeply( $got, [ 1, $is_multi ? $maybe_id : () ], $label ) or diag explain $got; is( $m->_get_path_count($path), 1, $label ); $got = [ $m->paths ]; is_deeply $got, [ $path ], $label or diag explain $got; isnt( $m->${ \$map->{has} }(@$path_maybe_id), undef, $label ); if ($arity == 1) { $got = [ $m->get_ids_by_paths([ $path ], 0) ]; is_deeply $got, [ 1 ], $label or diag explain $got; $got = [ $m->get_paths_by_ids([ [ 1 ] ]) ]; is_deeply( $got, [ [$path] ], $label ) or diag explain $got; $got = [ $m->get_ids_by_paths([ [$path, $path] ], 0, 1) ]; is_deeply $got, [ [1, 1] ], $label or diag explain $got; $got = [ $m->get_paths_by_ids([ [[1,1]] ], 1) ]; is_deeply $got, [ [[$path, $path]] ], $label or diag explain $got; } eval { $m->stringify }; is $@, '', $label; if ($arity == 1) { ok $m->rename_path($path, 'newname'), $label; is( $m->${ \$map->{has} }(@$path_maybe_id), undef, $label ); isnt( $m->${ \$map->{has} }('newname', $is_multi ? $maybe_id : ()), undef, $label ); ok $m->rename_path('newname', $path), $label; isnt( $m->${ \$map->{has} }(@$path_maybe_id), undef, $label ); is( $m->${ \$map->{has} }('newname', $is_multi ? $maybe_id : ()), undef, $label ); } elsif ($arity == 2) { $got = [ $m->successors($path->[0]) ]; is_deeply $got, [ $path->[1] ], $label or diag explain $got; ok $m->has_successor(@$path), $label; ok !$m->has_successor($path->[0], 99), $label; $got = [ $m->paths_from($path->[0]) ]; is_deeply $got, [ $path ], $label or diag explain $got; if ($is_unord) { $got = [ $m->successors($path->[1]) ]; is_deeply $got, [ $path->[0] ], $label or diag explain $got; $got = [ $m->paths_from($path->[1]) ]; is_deeply $got, [ $path ], $label or diag explain $got; } else { $got = [ $m->predecessors($path->[1]) ]; is_deeply $got, [ $path->[0] ], $label or diag explain $got; $got = [ $m->paths_to($path->[1]) ]; is_deeply $got, [ $path ], $label or diag explain $got; } } elsif ($arity == 0 and @$path) { my ($froms, $tos) = $is_unord ? ($path, $path) : @$path; for my $f (@$froms) { for my $t ($is_unord ? grep $_ ne $f, @$tos : @$tos) { ok $m->has_successor($f, $t), $label; ok !$m->has_successor($f, 99), $label; ok !$m->has_successor(99, $t), $label; next if $is_unord; $got = [ $m->paths_to($t) ]; is_deeply $got, [ $path ], $label or diag explain $got; } $got = [ sort $m->successors($f) ]; is_deeply $got, [ sort $is_unord ? grep $_ ne $f, @$tos : @$tos ], $label or diag explain $got; $got = [ $m->paths_from($f) ]; is_deeply $got, [ $path ], $label or diag explain $got; } $got = [ sort $m->successors(@$froms) ]; is_deeply $got, [ sort @$tos ], $label or diag explain $got; $got = [ $m->paths_from(@$froms) ]; is_deeply $got, [ $path ], $label or diag explain $got; if (!$is_unord) { $got = [ sort $m->predecessors(@$tos) ]; is_deeply $got, $froms, $label or diag explain $got; $got = [ sort $m->predecessors($tos->[0]) ]; is_deeply $got, $froms, $label or diag explain $got; $got = [ $m->paths_to(@$tos) ]; is_deeply $got, [ $path ], $label or diag explain $got; } } ok( !$m->_has_path_attrs(@$path_maybe_id), $label ); is( $m->_set_path_attr(@$path_maybe_id, 'say', 'hi'), 'hi', $label ); ok( $m->_has_path_attr(@$path_maybe_id, 'say'), $label ); ok( $m->_has_path_attrs(@$path_maybe_id), $label ); is_deeply [ $m->_get_path_attr_names(@$path_maybe_id) ], [ 'say' ], $label; is_deeply [ $m->_get_path_attr_values(@$path_maybe_id) ], [ 'hi' ], $label; if ($arity == 1) { my @new_path_full = @$path_maybe_id; $new_path_full[0] = 'newname'; ok $m->rename_path($path, 'newname'), $label; is_deeply [ $m->_get_path_attr_names(@new_path_full) ], [ 'say' ], $label; ok $m->rename_path('newname', $path), $label; is_deeply [ $m->_get_path_attr_names(@$path_maybe_id) ], [ 'say' ], $label; } $got = $m->_get_path_attrs(@$path_maybe_id); is_deeply $got, { say => 'hi' }, $label or diag explain $got; $got = { %{ $got || {} }, extra => 'hello' }; $got = $m->_set_path_attrs(@$path_maybe_id, $got); is_deeply [ sort $m->_get_path_attr_names(@$path_maybe_id) ], [ qw(extra say) ], $label; is_deeply [ $m->_get_path_attr(@$path_maybe_id, 'extra') ], [ qw(hello) ], $label; $m->_del_path_attr(@$path_maybe_id, 'extra'); is_deeply [ $m->_get_path_attr_names(@$path_maybe_id) ], [ qw(say) ], $label; $got = [ $m->_get_path_attrs(@$path_maybe_id) ]; is_deeply $got, [ { say => 'hi' } ], $label or diag explain $got; $m->_del_path_attr(@$path_maybe_id, 'say'); is_deeply [ $m->_get_path_attr_names(@$path_maybe_id) ], [ ], $label; is( $m->_get_path_count($path), 1, $label ); $m->_set_path_attr(@$path_maybe_id, 'say', 'hi'); ok $m->_del_path_attrs(@$path_maybe_id), $label; ok( !$m->_has_path_attr(@$path_maybe_id, 'say'), $label ); is( $m->_get_path_count($path), 1, $label ); if ($is_multi) { is $m->${ \$map->{set} }($path, _GEN_ID), 0, $label; ok( $m->set_path_by_multi_id($path, 'hello'), $label ); $got = [ sort $m->get_multi_ids($path) ]; is_deeply $got, [ sort $path_maybe_id->[-1], qw(0 hello) ], $label or diag explain $got; } if ($arity == 1) { $got = [ $m->get_ids_by_paths([ $path, @paths_to_create ], 1) ]; is_deeply $got, [ 1..3 ], $label or diag explain $got; ok $m->${ \$map->{has} }(@$_), $label for @paths_to_create_maybe_id; my @paths_to_create_deep = map [ $_, "1$_" ], @paths_to_create; my @paths_deep_maybe_id = map [ "1$_", $is_multi ? 0 : () ], @paths_to_create; $got = [ $m->get_ids_by_paths(\@paths_to_create_deep, 1, 1) ]; is_deeply $got, [ [2, 4], [3, 5] ], $label or diag explain $got; $got = [ $m->get_ids_by_paths([ \@paths_to_create_deep ], 1, 2) ]; is_deeply $got, [ [ [2, 4], [3, 5] ] ], $label or diag explain $got; ok $m->${ \$map->{has} }(@$_), $label for @paths_deep_maybe_id; ok( $m->has_any_paths, $label ) or diag explain $m; $m->${ \$map->{del} }(@$_) for @paths_deep_maybe_id, @paths_to_create_maybe_id; } $m->_set_path_attr(@$path_maybe_id, 'say', 'hi'); $m->${ \$map->{del} }(@$path_maybe_id); ok( !$m->_has_path_attr(@$path_maybe_id, 'say'), $label ); if ($is_multi) { $m->${ \$map->{del} }($path, $_) for $m->get_multi_ids($path); is( $m->${ \$map->{has} }(@$path_maybe_id), undef, $label ); } ok( !$m->has_any_paths, $label ) or diag explain $m; if ($arity == 2) { $got = [ $m->successors($path->[0]) ]; is_deeply $got, [ ], $label or diag explain $got; $got = [ $m->paths_from($path->[0]) ]; is_deeply $got, [ ], $label or diag explain $got; if (!($is_unord)) { $got = [ $m->predecessors($path->[1]) ]; is_deeply $got, [ ], $label or diag explain $got; $got = [ $m->paths_to($path->[1]) ]; is_deeply $got, [ ], $label or diag explain $got; } } } test_adjmap(@$_) for @MAP_TESTS; my $g = Graph->new(refvertexed => 1); $g->add_vertex($v); $g->add_edge($v, $z); my @V = sort { $a->sqrt <=> $b->sqrt } $g->vertices; is($V[0]->Re, 3); is($V[0]->Im, 4); is($V[1]->Re, 4); is($V[1]->Im, 5); ok($g->has_vertex($v)); ok($g->has_vertex($z)); ok($g->has_edge($v, $z)); $v->Re(7); $z->Im(8); ok($g->has_vertex($v)); ok($g->has_vertex($z)); @V = sort { $a->sqrt <=> $b->sqrt } $g->vertices; is($V[0]->Re, 4); is($V[0]->Im, 8); is($V[1]->Re, 7); is($V[1]->Im, 4); my $x = cplx(1,2); my $y = cplx(3,4); $g = Graph->new(refvertexed => 1); $g->add_edge($x,$y); my @e = $g->edges; is("@{$e[0] || []}", "1+2i 3+4i"); $x->Im(5); is("@{$e[0] || []}", "1+5i 3+4i"); SKIP: { skip 'no object to invoke', 1 unless @e and ref $e[0]->[1]; $e[0]->[1]->Im(6); is("$y", "3+6i"); } use vars qw($foo $bar); my $o0; my $o1; my $o1a = bless \$o0, 'S'; my $o1b = bless \$o1, 'S'; { package S; use overload '""' => sub { "s" } } my $o2a = bless [], 'A'; my $o2b = bless [], 'A'; { package A; use overload '""' => sub { "a" } } my $o3a = bless {}, 'H'; my $o3b = bless {}, 'H'; { package H; use overload '""' => sub { "h" } } my $o4a = bless sub {}, 'C'; my $o4b = bless sub {}, 'C'; { package C; use overload '""' => sub { "c" } } my $o5a = bless \*STDIN{IO}, 'I'; my $o5b = bless \*STDOUT{IO}, 'I'; { package I; use overload '""' => sub { "i" } } my $o6a = bless \*foo, 'G'; my $o6b = bless \*bar, 'G'; { package G; use overload '""' => sub { "g" } } for my $d (1, 0) { for my $i ($o1a, $o2a, $o3a, $o4a, $o5a, $o6a) { for my $j ($o1b, $o2b, $o3b, $o4b, $o5b, $o6b) { note "d = $d, i = $i, j = $j"; my $g = Graph->new(refvertexed => 1, directed => $d); ok( $g->add_edge($i, $j)); note "g = $g"; ok( $g->has_vertex($i)); ok( $g->has_vertex($j)); ok( $g->has_edge($i, $j)); if (!$d) { # bridges only for undirected eval {ok $g->has_vertex($_) for map @$_, $g->bridges}; is $@, ''; } ok( $g->delete_vertex($i)); note "g = $g"; ok(!$g->has_vertex($i)); ok( $g->has_vertex($j)); ok(!$g->has_edge($i, $j)); ok($g->delete_vertex($j)); ok(!$g->has_vertex($i)); ok(!$g->has_vertex($j)); ok(!$g->has_edge($i, $j)); } } } my $g_ref = Graph->new(refvertexed => 1); $g_ref->add_edge( my $A = {}, {} ); bless $A; is $g_ref->neighbours( $_ ), 1, 'each known vertex has 1 neighbour' for $g_ref->vertices; done_testing; Graph-0.9735/t/14_delete_vertex.t0000644000175000017500000000317613774262024016421 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 36; use Graph; my $g = Graph->new; $g->add_vertex("a"); $g->add_vertex("b"); ok( $g->delete_vertex("b") ); ok( $g->has_vertex("a") ); ok( ! $g->has_vertex("b") ); ok( ! $g->has_vertex("c") ); ok( $g->delete_vertex("c") ); ok( $g->has_vertex("a") ); ok( ! $g->has_vertex("b") ); ok( ! $g->has_vertex("c") ); is( $g->delete_vertex("a"), "" ); is( $g->delete_vertex("a"), "" ); $g->add_vertices(qw(a b c d)); ok( $g->has_vertex("a") ); ok( $g->has_vertex("b") ); ok( $g->has_vertex("c") ); ok( $g->has_vertex("d") ); $g->delete_vertices(qw(a c)); ok( ! $g->has_vertex("a") ); ok( $g->has_vertex("b") ); ok( ! $g->has_vertex("c") ); ok( $g->has_vertex("d") ); $g->delete_vertices(qw(a c)); ok( ! $g->has_vertex("a") ); ok( $g->has_vertex("b") ); ok( ! $g->has_vertex("c") ); ok( $g->has_vertex("d") ); $g->delete_vertices(qw(b d)); ok( ! $g->has_vertex("a") ); ok( ! $g->has_vertex("b") ); ok( ! $g->has_vertex("c") ); ok( ! $g->has_vertex("d") ); is( $g->delete_vertex(), $g ); is( $g->delete_vertices(), $g ); my $h = Graph->new(countvertexed => 1); $h->add_vertices(qw(a a b b)); ok( $h->has_vertex("a") ); ok( $h->has_vertex("b") ); $h->delete_vertex('a'); ok( $h->has_vertex("a") ); $h->delete_vertex('a'); ok( ! $h->has_vertex("a") ); $h->delete_vertices('b'); ok( $h->has_vertices("b") ); $h->delete_vertices('b'); ok( ! $h->has_vertices("b") ); { # From Andras Salamon use Graph::Directed; my $f = new Graph::Directed; $f->add_edges( qw( a a a b ) ); # notice self-loop is($f, "a-a,a-b"); $f->delete_vertex('a'); is($f, "b"); } Graph-0.9735/t/u_sc_me.t0000644000175000017500000000137613743337306014672 0ustar osboxesosboxes# rt.cpan.org #41190: add_edge_by_id on multigraph malfunctioning use strict; use Test::More tests => 4; use Graph; my $G0 = Graph->new(undirected => 1, vertices => ["v0", "v1", "v2"], multiedged => 1); $G0->add_edge_by_id("v0", "v1", "0"); is($G0, "v0=v1,v2"); my $G1 = Graph->new(undirected => 1, vertices => ["v0", "v1", "v2"], multiedged => 1); $G1->add_edge_by_id("v0", "v1", "1"); is($G1, "v0=v1,v2"); my $G2 = Graph->new(undirected => 1, vertices => ["v0", "v1", "v2"], multiedged => 1); $G2->add_edge_by_id("v0", "v0", "0"); is($G2, "v0=v0,v1,v2"); my $G3 = Graph->new(undirected => 1, vertices => ["v0", "v1", "v2"], multiedged => 1); $G3->add_edge_by_id("v0", "v0", "1"); is($G3, "v0=v0,v1,v2"); Graph-0.9735/t/56_neighbourhood.t0000644000175000017500000000404314741033474016416 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph; my $g0 = Graph->new; my $g1 = Graph->new(undirected => 1); my @E = ([1=>1], [1=>2], [1=>3], [2=>4], [5=>4]); $g0->add_edge(@$_) for @E; $g1->add_edge(@$_) for @E; $g0->add_vertex(6); $g1->add_vertex(6); is $g0, "1-1,1-2,1-3,2-4,5-4,6"; is $g1, "1=1,1=2,1=3,2=4,4=5,6"; my %methods = ( neighbours => [ [ [1], "1 2 3", "1 2 3" ], [ [2], "1 4", "1 4" ], [ [3], "1", "1" ], [ [4], "2 5", "2 5" ], [ [5], "4", "4" ], [ [6], "", "" ], ], neighbours_by_radius => [ [ [1, 1], "1 2 3", "1 2 3" ], [ [2, 1], "1 4", "1 4" ], [ [3, 1], "1", "1" ], [ [4, 1], "2 5", "2 5" ], [ [5, 2], "2 4", "2 4" ], [ [6, 1], "", "" ], ], is_successorless_vertex => [ [ [1], "", "" ], [ [2], "", "" ], [ [3], 1, "" ], [ [4], 1, "" ], [ [5], "", "" ], [ [6], 1, 1 ], ], is_successorful_vertex => [ [ [1], 1, 1 ], [ [2], 1, 1 ], [ [3], "", 1 ], [ [4], "", 1 ], [ [5], 1, 1 ], [ [6], "", "" ], ], is_predecessorless_vertex => [ [ [1], "", "" ], [ [2], "", "" ], [ [3], "", "" ], [ [4], "", "" ], [ [5], 1, "" ], [ [6], 1, 1 ], ], is_predecessorful_vertex => [ [ [1], 1, 1 ], [ [2], 1, 1 ], [ [3], 1, 1 ], [ [4], 1, 1 ], [ [5], "", 1 ], [ [6], "", "" ], ], successorless_vertices => [ [ [], "3 4 6", "6" ], ], successorful_vertices => [ [ [], "1 2 5", "1 2 3 4 5" ], ], predecessorless_vertices => [ [ [], "5 6", "6" ], ], predecessorful_vertices => [ [ [], "1 2 3 4", "1 2 3 4 5" ], ], ); for my $m (sort keys %methods) { for my $t ( @{ $methods{$m} } ) { my ($args, $expected0, $expected1) = @$t; my $got0_count = scalar $g0->$m(@$args); my $expected0_count = @{[split ' ', $expected0]}; is $got0_count+0, $expected0_count, "right number for scalar context $m"; is( "@{[sort $g0->$m(@$args)]}", $expected0, "directed $m (@$args)" ); is( "@{[sort $g1->$m(@$args)]}", $expected1, "undirected $m (@$args)" ); } } done_testing; Graph-0.9735/t/16_edges.t0000644000175000017500000000375514102052753014646 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 48; use Graph; my $g = Graph->new; is $g, ""; ok( !$g->has_edges() ); is( $g->edges, 0 ); is( "@{[$g->edges]}", "" ); ok( !$g->any_edge("a", "b") ); ok( !$g->any_edge("b", "a") ); ok( !$g->any_edge("b", "c") ); ok( !$g->any_edge("c", "b") ); $g->add_edge("a", "b"); is $g, "a-b"; ok( $g->has_edges() ); is( $g->edges, 1 ); is( "@{[map { qq{[@$_]} } $g->edges]}", "[a b]" ); ok( $g->any_edge("a", "b") ); ok( !$g->any_edge("b", "a") ); ok( !$g->any_edge("b", "c") ); ok( !$g->any_edge("c", "b") ); $g->add_edge("b", "c"); is $g, "a-b,b-c"; ok( $g->has_edges() ); is( $g->edges, 2 ); is( "@{[sort map { qq{[@$_]} } $g->edges]}", "[a b] [b c]" ); ok( $g->any_edge("a", "b") ); ok( !$g->any_edge("b", "a") ); ok( $g->any_edge("b", "c") ); ok( !$g->any_edge("c", "b") ); eval { $g->add_edges("x") }; like($@, qr/Graph::add_edges: missing end vertex/); is($g->add_edges("x", "y"), $g); is($g, "a-b,b-c,x-y"); eval { $g->add_edge("c", "d", "e", "f") }; like($@, qr/Graph::add_edge: expected hyperedged graph/); eval { $g->add_edge("c") }; like($@, qr/Graph::add_edge: expected hyperedged graph/); ok( $g->delete_edge("b", "c") ); is $g, "a-b,x-y,c"; ok( $g->delete_edge("b", "d") ); is $g, "a-b,x-y,c"; is( $g->delete_edge("a", "b"), 'x-y,a,b,c' ); is( $g->delete_edge("a", "b"), 'x-y,a,b,c' ); $g->add_edges(qw(a b b x c d c y)); is $g, "a-b,b-x,c-d,c-y,x-y"; $g->delete_edges(qw(a b c d)); is $g, "b-x,c-y,x-y,a,d"; $g->delete_edges(qw(a b c d)); is $g, "b-x,c-y,x-y,a,d"; $g->delete_edges(qw(b x c y)); is $g, "x-y,a,b,c,d"; is( $g->delete_edge(), $g ); is( $g->delete_edges(), $g ); my $h = Graph->new(countedged => 1); $h->add_edges(qw(a x a x b y b y)); is $h, "a-x,b-y"; $h->delete_edge('a', 'x'); is $h, "a-x,b-y"; $h->delete_edge('a', 'x'); is $h, "b-y,a,x"; $h->delete_edges('b', 'y'); ok( $h->has_edges ); # takes no args ok( $h->has_edges("b", "y") ); $h->delete_edges('b', 'y'); ok( ! $h->has_edges ); ok( ! $h->has_edges("b", "y") ); Graph-0.9735/t/20_countvertexed.t0000644000175000017500000000164713774262024016457 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 28; use Graph; my $g1 = Graph->new; ok ( !$g1->countvertexed ); my $g2 = Graph->new( countvertexed => 1 ); ok ( $g2->countvertexed ); is( $g2->vertices, 0 ); is( $g2->unique_vertices, 0 ); ok( $g2->add_vertex('a') ); is( $g2->vertices, 1 ); is( $g2->unique_vertices, 1 ); ok( $g2->add_vertex('a') ); is( $g2->vertices, 2 ); is( $g2->unique_vertices, 1 ); ok( $g2->add_vertex('b') ); is( $g2->vertices, 3 ); is( $g2->unique_vertices, 2 ); ok( $g2->add_vertex('a') ); is( $g2->vertices, 4 ); is( $g2->unique_vertices, 2 ); ok( $g2->delete_vertex('b') ); is( $g2->vertices, 3 ); is( $g2->unique_vertices, 1 ); ok( $g2->delete_vertex('a') ); is( $g2->vertices, 2 ); is( $g2->unique_vertices, 1 ); ok( $g2->delete_vertex('a') ); is( $g2->vertices, 1 ); is( $g2->unique_vertices, 1 ); is( $g2->delete_vertex('a'), "" ); is( $g2->vertices, 0 ); is( $g2->unique_vertices, 0 ); Graph-0.9735/t/99_misc.t0000644000175000017500000000436714741033474014535 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::Directed; use Graph::Undirected; my @E = ([qw(a b)], [qw(a c)], [qw(b d)], [qw(b e)], [qw(c f)], [qw(c g)]); { my $gi0 = Graph->new; $gi0->set_edge_attribute(qw(a b), weight => 1); my $gi1 = Graph->new; $gi1->set_vertex_attribute('x', shape => 1); $gi1->set_edge_attribute(qw(x y), weight => 2); is_deeply [ $gi0->ingest($gi1)->as_hashes ], [ { x => { shape => 1 }, map +($_ => {}), qw(a b y) }, { a => { b => { weight => 1 } }, x => { y => { weight => 2 } } }, ]; } for ({}, {countvertexed => 1}, {multivertexed => 1}) { my $gr = Graph::Directed->new(%$_); $gr->add_edge(@$_) for @E; $gr->rename_vertex('b', 'b1'); my $label = ref($gr->[ 2 ]) . ' {' . join('=>', %$_) . '}'; is $gr, "a-b1,a-c,b1-d,b1-e,c-f,c-g", $label; $gr->rename_vertices(sub { uc $_[0] }); is $gr, "A-B1,A-C,B1-D,B1-E,C-F,C-G", $label; } for ({}, {multivertexed => 1}, {multiedged => 1}) { my $g = Graph::Directed->new(%$_); $g->add_edge(@$_) for @E; my $label = ref($g->[ 2 ]) . ' {' . join('=>', %$_) . '}'; is $g, "a-b,a-c,b-d,b-e,c-f,c-g", $label; $g->filter_edges(sub {$_[2] ne 'g'}); is $g, "a-b,a-c,b-d,b-e,c-f,g", $label; $g->filter_vertices(sub {$_[1] !~ /[fg]/}); is $g, "a-b,a-c,b-d,b-e", $label; } my $g2 = Graph->new; is_deeply [ $g2->clustering_coefficient ], [], 'clustering_coefficient with no vertices = empty list'; for my $p (qw(zero one two three four five six seven eight nine ten)) { $g2->add_path(split(//, $p)); } my ($gamma, %clustering) = $g2->clustering_coefficient; my $eps = 1e-6; ok(abs($gamma - 0.402222222222222) <= $eps); ok(abs($clustering{e} - 0.7) <= $eps); ok(abs($clustering{t} - 1/3) <= $eps); is($clustering{z}, 0.0); is($clustering{r}, 1.0); my %betweenness = $g2->betweenness; ok(abs($betweenness{e} - 60.3333333333333) <= $eps); ok(abs($betweenness{t} - 17.1666666666667) <= $eps); is($betweenness{x}, 0.0); is($betweenness{u}, 3.0); { my $w = ''; local $SIG{__WARN__} = sub { $w = shift }; my $g3 = Graph->new; $g3->add_edge(0,1); my @dummy = $g3->SP_Dijkstra(1,0); is $w, ''; } is_deeply [ sort(Graph::__fisher_yates_shuffle(1..3)) ], [ 1..3 ]; done_testing; Graph-0.9735/t/67_copy.t0000644000175000017500000001403614741033474014541 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::Directed; use Graph::Undirected; my ($g0, $g2, $g4) = map Graph::Directed->new, 1..3; my ($g1, $g3, $g5) = map Graph::Undirected->new, 1..3; $_->add_path(qw(a b c)) for $g0, $g1; $_->add_path(qw(d b e)) for $g0, $g1; $_->add_path(qw(c a b c d)) for $g2, $g3; $_->add_path(qw(b a b c)) for $g4, $g5; is $g0->copy, "a-b,b-c,b-e,d-b"; is $g1->copy, "a=b,b=c,b=d,b=e"; is $g2->copy, "a-b,b-c,c-a,c-d"; is $g3->copy, "a=b,a=c,b=c,c=d"; is $g4->copy, "a-b,b-a,b-c"; is $g5->copy, "a=b,b=c"; is $g0->undirected_copy, $g1; $g0->undirected_copy->delete_vertex('a'); is $g0->undirected_copy, $g1; is $g2->undirected_copy, $g3; is $g4->undirected_copy, $g5; is $g1->directed_copy, "a-b,b-a,b-c,b-d,b-e,c-b,d-b,e-b"; $g1->directed_copy->delete_vertex('a'); is $g1->directed_copy, "a-b,b-a,b-c,b-d,b-e,c-b,d-b,e-b"; is $g3->directed_copy, "a-b,a-c,b-a,b-c,c-a,c-b,c-d,d-c"; is $g5->directed_copy, "a-b,b-a,b-c,c-b"; is $g0->transpose, "b-a,b-d,c-b,e-b"; is $g1->transpose, "a=b,b=c,b=d,b=e"; is $g2->transpose, "a-c,b-a,c-b,d-c"; is $g3->transpose, "a=b,a=c,b=c,c=d"; is $g4->transpose, "a-b,b-a,c-b"; is $g5->transpose, "a=b,b=c"; $g0 = Graph::Directed->new(multivertexed=>1, multiedged=>1); $g1 = Graph::Undirected->new(multivertexed=>1, multiedged=>1); $_->add_path_by_id(qw(a b c), 'id') for $g0, $g1; $_->add_path_by_id(qw(d b e), 'id') for $g0, $g1; $_->set_vertex_attribute_by_id(qw(d 0 height 7)) for $g0, $g1; $_->set_edge_attribute_by_id(qw(d b id weight 5)) for $g0, $g1; is $g0, "a-b,b-c,b-e,d-b"; is $g1, "a=b,b=c,b=d,b=e"; my $expected = Graph::_deep_copy_best([$g1->as_hashes]); delete $expected->[0]{$_->[0]}{$_->[1]}{$_->[2]} for [qw(d 0 height)]; delete $expected->[1]{$_->[0]}{$_->[1]}{$_->[2]}{$_->[3]} for [qw(d b id weight)], [qw(b d id weight)]; is_deeply [$g0->undirected_copy->as_hashes], $expected, 'undirected_copy preserve multi' or diag 'got: ', explain([$g0->undirected_copy->as_hashes]), 'expected: ', explain($expected); is_deeply [$g0->undirected_copy_attributes->as_hashes], [$g1->as_hashes], 'undirected_copy_attributes' or diag 'got: ', explain([$g0->undirected_copy_attributes->as_hashes]), 'expected: ', explain([$g1->as_hashes]); $expected = Graph::_deep_copy_best([$g0->as_hashes]); $expected->[1]{$_->[0]}{$_->[1]}{id} = {} for [qw(b a)],[qw(b d)],[qw(c b)],[qw(e b)]; my $expected2 = Graph::_deep_copy_best($expected); $expected2->[1]{b}{d} = $expected2->[1]{d}{b}; is_deeply [$g1->directed_copy_attributes->as_hashes], $expected2, 'directed_copy_attributes' or diag 'got: ', explain([$g1->directed_copy_attributes->as_hashes]), 'expected: ', explain($expected2); delete $expected->[0]{$_->[0]}{$_->[1]}{$_->[2]} for [qw(d 0 height)]; delete $expected->[1]{$_->[0]}{$_->[1]}{$_->[2]}{$_->[3]} for [qw(d b id weight)]; is_deeply [$g1->directed_copy->as_hashes], $expected, 'directed_copy preserve multi' or diag 'got: ', explain([$g1->directed_copy->as_hashes]), 'expected: ', explain($expected); $expected = Graph::_deep_copy_best([$g0->as_hashes]); delete $expected->[0]{$_->[0]}{$_->[1]}{$_->[2]} for [qw(d 0 height)]; delete $expected->[1]{$_->[0]}{$_->[1]}{$_->[2]}{$_->[3]} for [qw(d b id weight)]; is_deeply [$g0->copy->as_hashes], $expected, 'copy of directed preserve multi' or diag 'got: ', explain([$g0->copy->as_hashes]), 'expected: ', explain($expected); $expected = Graph::_deep_copy_best([$g1->as_hashes]); delete $expected->[0]{$_->[0]}{$_->[1]}{$_->[2]} for [qw(d 0 height)]; delete $expected->[1]{$_->[0]}{$_->[1]}{$_->[2]}{$_->[3]} for [qw(d b id weight)], [qw(b d id weight)]; is_deeply [$g1->copy->as_hashes], $expected, 'copy of undirected preserve multi' or diag 'got: ', explain([$g1->copy->as_hashes]), 'expected: ', explain($expected); my $g6 = Graph->new; is($g6->complete->edges, 0); is($g6->complement->edges, 0); my $g7 = Graph::Directed->new(); $g7->add_edge(qw(a b)); $g7->add_edge(qw(a c)); is($g7, "a-b,a-c"); is($g7->complete, "a-b,a-c,b-a,b-c,c-a,c-b"); is($g7->complement, "b-a,b-c,c-a,c-b"); my $g8 = Graph::Undirected->new(); $g8->add_edge(qw(a b)); $g8->add_edge(qw(a c)); is($g8, "a=b,a=c"); is($g8->complete, "a=b,a=c,b=c"); is($g8->complement, "b=c,a"); my $g9 = Graph::Directed->new(countedged => 1); $g9->add_edge(qw(a b)); $g9->add_edge(qw(a c)); my $c9 = $g9->complete_graph; is $c9, "a-b,a-c,b-a,b-c,c-a,c-b"; for my $u (qw(a b c)) { for my $v (qw(a b c)) { next if $u eq $v; is($c9->get_edge_count($u, $v), 1); } } is $g9->complement_graph, "b-a,b-c,c-a,c-b"; my $g10 = Graph::Undirected->new(); $g10->add_edge(qw(a b)); is scalar($g10->vertices), 2; my $c10 = $g10->complement_graph; is scalar($c10->vertices), 2; is scalar($c10->edges), 0; { my $g = Graph->new; $g->set_graph_attribute('color' => 'deep_purple'); $g->set_graph_attribute('hunky' => sub { "hunky $_[0]" }); SKIP: { skip("no coderef Deparse", 2) unless $] >= 5.008; my $c = $g->deep_copy; is($c->get_graph_attribute('color'), 'deep_purple'); is($c->get_graph_attribute('hunky')->('dory'), 'hunky dory'); } } SKIP: { skip("no coderef Deparse", 1) unless $] >= 5.008; my $g = Graph->new; $g->set_graph_attribute('color' => sub { $_[0] ** 2 }); my $c = $g->deep_copy; is($c->get_graph_attribute('color')->(7), 49); } SKIP: { skip("no coderef Deparse", 1) unless $] >= 5.008; skip("no coderef Deparse with Storable", 1) unless Graph::_can_deep_copy_Storable(); require Storable; my $g = Graph->new; $g->set_graph_attribute('color' => sub { $_[0] ** 3 }); my $c = $g->_deep_copy_Storable; is($c->get_graph_attribute('color')->(2), 8); } SKIP: { skip("no coderef Deparse", 1) unless $] >= 5.008; my $g = Graph->new; $g->set_graph_attribute('color' => sub { $_[0] ** 4 }); my $c = $g->_deep_copy_DataDumper; is($c->get_graph_attribute('color')->(3), 81); } my $edges = [[{ name => 'A' }, { name => 'B' }]]; SKIP: { my $orig = Graph::Undirected->new(refvertexed=>1, edges=>$edges); for my $g ($orig, $orig->deep_copy) { is scalar $g->neighbours( $_ ), 1, 'still linked up' for $g->vertices; } } done_testing; Graph-0.9735/t/10_has_vertices.t0000644000175000017500000000032513774262024016226 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 3; use Graph; my $g = Graph->new; ok( !$g->has_vertices() ); $g->add_vertex("a"); ok( $g->has_vertices() ); $g->add_vertex("b"); ok( $g->has_vertices() ); Graph-0.9735/t/u_bo_ap2.t0000644000175000017500000001366713774262024014752 0ustar osboxesosboxesuse Test::More tests => 20; use Graph::Undirected; use strict; my $g = Graph::Undirected->new; while () { chomp; my ($v1,$v2) = split ','; $g->add_edge($v1, $v2); } my @rts = sort $g->articulation_points; is("@rts", "EBI-621930 EBI-622140 EBI-622203 EBI-622235 EBI-622264 EBI-622281 EBI-622306 EBI-622382") for 1..20; __END__ EBI-622148,EBI-622235 EBI-592020,EBI-592003 EBI-621976,EBI-621986 EBI-622374,EBI-622432 EBI-622165,EBI-592003 EBI-592041,EBI-592136 EBI-592365,EBI-592083 EBI-592058,EBI-621940 EBI-621930,EBI-621968 EBI-621903,EBI-621986 EBI-622174,EBI-622475 EBI-622227,EBI-622416 EBI-622203,EBI-622211 EBI-592343,EBI-621930 EBI-622466,EBI-621968 EBI-592343,EBI-622382 EBI-592343,EBI-621986 EBI-622194,EBI-622390 EBI-592020,EBI-621968 EBI-592020,EBI-621930 EBI-592343,EBI-621940 EBI-622036,EBI-622448 EBI-621986,EBI-622087 EBI-592020,EBI-592304 EBI-622357,EBI-622432 EBI-622131,EBI-622148 EBI-622298,EBI-622466 EBI-621976,EBI-621930 EBI-622448,EBI-622457 EBI-622211,EBI-622349 EBI-621986,EBI-621940 EBI-592058,EBI-592003 EBI-592041,EBI-621921 EBI-592041,EBI-622114 EBI-622096,EBI-621930 EBI-592041,EBI-621949 EBI-622440,EBI-622475 EBI-622264,EBI-622281 EBI-622140,EBI-622298 EBI-622017,EBI-621968 EBI-622366,EBI-622475 EBI-622148,EBI-622484 EBI-622235,EBI-622298 EBI-622264,EBI-622432 EBI-622174,EBI-622382 EBI-622211,EBI-622390 EBI-622272,EBI-622281 EBI-592041,EBI-621940 EBI-622219,EBI-622324 EBI-622067,EBI-622076 EBI-622289,EBI-622382 EBI-621903,EBI-621930 EBI-622306,EBI-622314 EBI-622165,EBI-622211 EBI-592083,EBI-621968 EBI-622235,EBI-622247 EBI-622382,EBI-622399 EBI-622011,EBI-622475 EBI-622349,EBI-622374 EBI-592365,EBI-621903 EBI-621912,EBI-622036 EBI-621986,EBI-621986 EBI-622382,EBI-621949 EBI-622382,EBI-621968 EBI-622067,EBI-592003 EBI-621940,EBI-621968 EBI-621986,EBI-622017 EBI-592058,EBI-592020 EBI-592365,EBI-622067 EBI-622211,EBI-622416 EBI-592365,EBI-621940 EBI-622027,EBI-621940 EBI-622211,EBI-622289 EBI-622165,EBI-622174 EBI-622027,EBI-622382 EBI-622219,EBI-622475 EBI-592136,EBI-622382 EBI-622374,EBI-622475 EBI-622332,EBI-622475 EBI-592041,EBI-621930 EBI-621986,EBI-621930 EBI-622475,EBI-621968 EBI-622131,EBI-622157 EBI-592083,EBI-592304 EBI-622194,EBI-622432 EBI-622122,EBI-622140 EBI-622203,EBI-622349 EBI-622227,EBI-622390 EBI-621986,EBI-621921 EBI-592083,EBI-621930 EBI-621986,EBI-592003 EBI-622185,EBI-622382 EBI-592041,EBI-592041 EBI-622140,EBI-622448 EBI-622466,EBI-592003 EBI-592020,EBI-622057 EBI-592041,EBI-592304 EBI-621986,EBI-592058 EBI-621976,EBI-621940 EBI-621912,EBI-622067 EBI-592083,EBI-622067 EBI-622036,EBI-622475 EBI-622067,EBI-621949 EBI-622194,EBI-622475 EBI-622341,EBI-622382 EBI-592041,EBI-621968 EBI-592365,EBI-621912 EBI-622194,EBI-622289 EBI-592365,EBI-622165 EBI-622324,EBI-622332 EBI-592058,EBI-621968 EBI-622457,EBI-622466 EBI-621949,EBI-621968 EBI-622148,EBI-622165 EBI-621986,EBI-592304 EBI-622211,EBI-622484 EBI-622255,EBI-622382 EBI-622298,EBI-622448 EBI-622140,EBI-622475 EBI-622165,EBI-622194 EBI-622324,EBI-621968 EBI-622211,EBI-622432 EBI-622036,EBI-622382 EBI-622067,EBI-621930 EBI-622036,EBI-592003 EBI-622067,EBI-621968 EBI-622148,EBI-622475 EBI-592304,EBI-621940 EBI-592041,EBI-592020 EBI-622235,EBI-622448 EBI-622211,EBI-622382 EBI-621986,EBI-622114 EBI-592058,EBI-622067 EBI-622057,EBI-622382 EBI-622194,EBI-622255 EBI-622017,EBI-622382 EBI-622366,EBI-622432 EBI-621993,EBI-621940 EBI-622165,EBI-622219 EBI-622067,EBI-592304 EBI-621912,EBI-621968 EBI-622382,EBI-592003 EBI-622227,EBI-622382 EBI-592041,EBI-621986 EBI-622017,EBI-621940 EBI-621986,EBI-592083 EBI-622475,EBI-592003 EBI-622211,EBI-622475 EBI-622264,EBI-622349 EBI-622349,EBI-622357 EBI-592041,EBI-592058 EBI-592083,EBI-621912 EBI-592304,EBI-622382 EBI-592041,EBI-622036 EBI-622219,EBI-622416 EBI-622432,EBI-622440 EBI-622140,EBI-622157 EBI-592041,EBI-622027 EBI-622140,EBI-622148 EBI-622087,EBI-621930 EBI-621986,EBI-621968 EBI-622298,EBI-622475 EBI-622203,EBI-622432 EBI-621921,EBI-622382 EBI-592083,EBI-621940 EBI-592365,EBI-621921 EBI-622382,EBI-622408 EBI-592365,EBI-621986 EBI-592083,EBI-592083 EBI-622324,EBI-622341 EBI-592365,EBI-622475 EBI-592041,EBI-592003 EBI-622122,EBI-622131 EBI-622366,EBI-622382 EBI-622211,EBI-621968 EBI-622140,EBI-622466 EBI-622298,EBI-622382 EBI-622011,EBI-622466 EBI-592041,EBI-622017 EBI-622165,EBI-622185 EBI-622131,EBI-622448 EBI-622165,EBI-622227 EBI-622264,EBI-622475 EBI-622194,EBI-622484 EBI-622203,EBI-622484 EBI-622194,EBI-622382 EBI-622448,EBI-621968 EBI-622219,EBI-622448 EBI-622227,EBI-622484 EBI-622165,EBI-621968 EBI-622011,EBI-622382 EBI-621912,EBI-592020 EBI-622106,EBI-621930 EBI-621940,EBI-621949 EBI-592041,EBI-622096 EBI-621968,EBI-592003 EBI-622227,EBI-622466 EBI-621986,EBI-622096 EBI-621976,EBI-592041 EBI-622332,EBI-622382 EBI-622219,EBI-622484 EBI-622203,EBI-622382 EBI-592041,EBI-592083 EBI-621986,EBI-622076 EBI-622357,EBI-622475 EBI-621912,EBI-621940 EBI-622211,EBI-622466 EBI-622036,EBI-622324 EBI-622174,EBI-622432 EBI-622357,EBI-622382 EBI-592343,EBI-622067 EBI-622264,EBI-621968 EBI-622165,EBI-622203 EBI-622349,EBI-622366 EBI-592041,EBI-622067 EBI-622448,EBI-592003 EBI-622227,EBI-622289 EBI-622185,EBI-622390 EBI-621930,EBI-592003 EBI-621903,EBI-592041 EBI-622011,EBI-621940 EBI-621930,EBI-621949 EBI-592041,EBI-621912 EBI-622087,EBI-622382 EBI-621986,EBI-621912 EBI-592020,EBI-621949 EBI-622203,EBI-622306 EBI-622157,EBI-622235 EBI-622440,EBI-622484 EBI-592365,EBI-592020 EBI-592041,EBI-622087 EBI-622011,EBI-592020 EBI-621930,EBI-622264 EBI-592365,EBI-622382 EBI-622211,EBI-622255 EBI-622366,EBI-622484 EBI-621930,EBI-621930 EBI-622332,EBI-622448 EBI-622194,EBI-622349 EBI-622017,EBI-621930 EBI-622174,EBI-622349 EBI-621930,EBI-592304 EBI-622408,EBI-622475 EBI-622036,EBI-622466 EBI-621930,EBI-621940 EBI-622227,EBI-622255 EBI-592083,EBI-592003 EBI-621986,EBI-621949 EBI-592365,EBI-621930 EBI-622219,EBI-622382 EBI-622227,EBI-622475 EBI-621903,EBI-621993 EBI-621940,EBI-592003 EBI-622298,EBI-622324 EBI-592083,EBI-592020 EBI-621921,EBI-592304 EBI-592343,EBI-592020 EBI-621976,EBI-622382 EBI-622140,EBI-622424 EBI-622203,EBI-622475 Graph-0.9735/t/13_add_vertex.t0000644000175000017500000000050013774262024015672 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 6; use Graph; my $g = Graph->new; ok( $g->add_vertex("a") ); ok( $g->add_vertex("b") ); is( $g->add_vertex("c"), $g ); eval { $g->add_vertex(undef) }; like($@, qr/Graph::add_vertex: undef vertex/); is( $g->add_vertices("x", "y"), $g ); is( $g, "a,b,c,x,y" ); Graph-0.9735/t/MyGraph.pm0000644000175000017500000000011513774262024014764 0ustar osboxesosboxespackage MyGraph; use strict; use warnings; use Graph; use base 'Graph'; 1; Graph-0.9735/t/80_isomorphic.t0000644000175000017500000000372013774262024015734 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 31; use Graph; my $g0 = Graph->new; my $g1 = Graph->new; my $g2 = Graph->new; my $g3 = Graph->new; my $g4 = Graph->new; $g0->add_edges(qw(a b b c a d)); $g1->add_edges(qw(a b b c a d)); $g1->add_vertex('e'); $g2->add_edges(qw(a b b c a d b d)); $g3->add_edges(qw(a b b c b d)); $g4->add_edges(qw(a z a x x y)); ok( $g0->could_be_isomorphic($g0)); ok(!$g0->could_be_isomorphic($g1)); ok(!$g0->could_be_isomorphic($g2)); ok(!$g0->could_be_isomorphic($g3)); ok( $g0->could_be_isomorphic($g4)); ok(!$g1->could_be_isomorphic($g0)); ok( $g1->could_be_isomorphic($g1)); ok(!$g1->could_be_isomorphic($g2)); ok(!$g1->could_be_isomorphic($g3)); ok(!$g1->could_be_isomorphic($g4)); ok(!$g2->could_be_isomorphic($g0)); ok(!$g2->could_be_isomorphic($g1)); ok( $g2->could_be_isomorphic($g2)); ok(!$g2->could_be_isomorphic($g3)); ok(!$g2->could_be_isomorphic($g4)); ok(!$g3->could_be_isomorphic($g0)); ok(!$g3->could_be_isomorphic($g1)); ok(!$g3->could_be_isomorphic($g2)); ok( $g3->could_be_isomorphic($g3)); ok(!$g3->could_be_isomorphic($g4)); ok( $g4->could_be_isomorphic($g0)); ok(!$g4->could_be_isomorphic($g1)); ok(!$g4->could_be_isomorphic($g2)); ok(!$g4->could_be_isomorphic($g3)); ok( $g4->could_be_isomorphic($g4)); my $g5a = Graph->new; my $g5b = Graph->new; $g5a->add_edges(qw(a b a c a d)); $g5b->add_edges(qw(a x a y a z)); is($g5a->could_be_isomorphic($g5b), 6); # 3! is($g5b->could_be_isomorphic($g5a), 6); $g5a->add_edges(qw(e f e g)); $g5b->add_edges(qw(e t e u)); is($g5a->could_be_isomorphic($g5b), 120); # 5! is($g5b->could_be_isomorphic($g5a), 120); $g5a->add_edges(qw(h i h j h k)); $g5b->add_edges(qw(h i h j h k)); is($g5a->could_be_isomorphic($g5b), 80640); # 8! * 2! is($g5b->could_be_isomorphic($g5a), 80640); my $g6a = Graph->new; my $g6b = Graph->new; my $g6c = Graph->new; $g6a->add_vertices(qw(a b c d e f)); $g6a->add_edges(qw(a b b c b d)); $g6b->add_vertices(qw(a b c d e f)); $g6b->add_edges(qw(a b b c b e)); Graph-0.9735/t/83_bitmatrix.t0000644000175000017500000000320114741033474015560 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 29; use Graph; use Graph::BitMatrix; my $g = Graph->new; $g->add_edge(qw(e a)); $g->add_edge(qw(a b)); $g->add_edge(qw(b c)); $g->add_edge(qw(b d)); $g->add_edge(qw(d d)); $g->delete_vertex('e'); my $m = Graph::BitMatrix->new($g); ok(!$m->get(qw(a a)) ); ok( $m->get(qw(a b)) ); ok(!$m->get(qw(a c)) ); ok(!$m->get(qw(a d)) ); ok(!$m->get(qw(b a)) ); ok(!$m->get(qw(b b)) ); ok( $m->get(qw(b c)) ); ok( $m->get(qw(b d)) ); ok(!$m->get(qw(c a)) ); ok(!$m->get(qw(c b)) ); ok(!$m->get(qw(c c)) ); ok(!$m->get(qw(c d)) ); ok(!$m->get(qw(d a)) ); ok(!$m->get(qw(d b)) ); ok(!$m->get(qw(d c)) ); ok( $m->get(qw(d d)) ); $m->set(qw(c c)); ok( $m->get(qw(c c)) ); $m->unset(qw(c c)); ok(!$m->get(qw(c c)) ); is("@{[$m->get_row(qw(a a b c d))]}", "0 1 0 0"); is("@{[$m->get_row(qw(b a b c d))]}", "0 0 1 1"); is("@{[$m->get_row(qw(c a b c d))]}", "0 0 0 0"); is("@{[$m->get_row(qw(d a b c d))]}", "0 0 0 1"); is $m->stringify, <<'EOF'; to: a b c d a 0 1 0 0 b 0 0 1 1 c 0 0 0 0 d 0 0 0 1 EOF is scalar Graph::BitMatrix->new($g, transpose => 1)->stringify, <<'EOF'; to: a b c d a 0 0 0 0 b 1 0 0 0 c 0 1 0 0 d 0 1 0 1 EOF is( $m->get(qw(x x)), undef ); is("@{[sort $m->vertices]}", "a b c d"); $m->set_row(qw(b a c)); is("@{[$m->get_row(qw(b a b c d))]}", "1 0 1 1"); $m->unset_row(qw(b c d)); is("@{[$m->get_row(qw(b a b c d))]}", "1 0 0 0"); eval { Graph::BitMatrix->new($g, nonesuch => 1) }; like($@, qr/Graph::BitMatrix::new: Unknown option: 'nonesuch' /); Graph-0.9735/t/u_cd_rv.t0000644000175000017500000000125014741033474014666 0ustar osboxesosboxesuse Graph; use strict; use warnings; use Test::More tests => 2; package MyNode; use overload ('""' => '_asstring', fallback=>1); sub new { my ($class, %ops) = @_; return bless { %ops }, $class; } sub _asstring { my ($self) = @_; my $str = $self->{'name'}; return $self->{'name'}; } 1; package main; use strict; use warnings; my $gnoref = new Graph; my $gwithref = new Graph(refvertexed_stringified=>1); ok $gwithref->refvertexed_stringified; my $n1 = new MyNode('name'=>'alpha'); my $n2 = new MyNode('name'=>'beta'); $gnoref->add_edge($n1, $n2); $gwithref->add_edge($n1, $n2); is_deeply([sort keys %{$gnoref->[2]->[4]}],[sort keys %{$gwithref->[2]->[4]}]); Graph-0.9735/t/05_ugraph.t0000644000175000017500000000023613774262024015042 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 1; use lib 't'; use MyUGraph; # http://rt.cpan.org/NoAuth/Bug.html?id=6429 ok(ref(new UGraph), "UGraph"); Graph-0.9735/t/u_ro_ra.t0000644000175000017500000000073013774262024014675 0ustar osboxesosboxesuse Graph::Undirected; use strict; use warnings; use Test::More tests => 2; my $g = Graph::Undirected->new; # example graph #1 from http://mathworld.wolfram.com/GraphRadius.html # # A # | # B # / | \ # C D E # | | # F G $g->add_edge( split //, $_ ) for qw[ AB BC BD BE CF EG ]; my $apsp = $g->all_pairs_shortest_paths; is( $apsp->radius, 2, "radius" ); is_deeply( [$apsp->center_vertices], ["B"], "center_vertices" ); Graph-0.9735/t/57_degree.t0000644000175000017500000000365013774262024015021 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 50; use Graph; my $g0 = Graph->new; my $g1 = Graph->new(undirected => 1); $g0->add_edge(1=>1); $g1->add_edge(1=>1); $g0->add_edge(1=>2); $g1->add_edge(1=>2); $g0->add_edge(1=>3); $g1->add_edge(1=>3); $g0->add_edge(2=>4); $g1->add_edge(2=>4); $g0->add_edge(5=>4); $g1->add_edge(5=>4); $g0->add_vertex(6); $g1->add_vertex(6); is( $g0->in_degree(1), 1 ); is( $g0->in_degree(2), 1 ); is( $g0->in_degree(3), 1 ); is( $g0->in_degree(4), 2 ); is( $g0->in_degree(5), 0 ); is( $g0->in_degree(6), 0 ); is( $g0->out_degree(1), 3 ); is( $g0->out_degree(2), 1 ); is( $g0->out_degree(3), 0 ); is( $g0->out_degree(4), 0 ); is( $g0->out_degree(5), 1 ); is( $g0->out_degree(6), 0 ); is( $g0->degree(1), -2 ); is( $g0->degree(2), 0 ); is( $g0->degree(3), 1 ); is( $g0->degree(4), 2 ); is( $g0->degree(5), -1 ); is( $g0->degree(6), 0 ); is( $g0->vertex_degree(1), $g0->degree(1) ); is( $g0->vertex_degree(2), $g0->degree(2) ); is( $g0->vertex_degree(3), $g0->degree(3) ); is( $g0->vertex_degree(4), $g0->degree(4) ); is( $g0->vertex_degree(5), $g0->degree(5) ); is( $g0->vertex_degree(6), $g0->degree(6) ); is( $g1->in_degree(1), 4 ); is( $g1->in_degree(2), 2 ); is( $g1->in_degree(3), 1 ); is( $g1->in_degree(4), 2 ); is( $g1->in_degree(5), 1 ); is( $g1->in_degree(6), 0 ); is( $g1->out_degree(1), 4 ); is( $g1->out_degree(2), 2 ); is( $g1->out_degree(3), 1 ); is( $g1->out_degree(4), 2 ); is( $g1->out_degree(5), 1 ); is( $g1->out_degree(6), 0 ); is( $g1->degree(1), 4 ); is( $g1->degree(2), 2 ); is( $g1->degree(3), 1 ); is( $g1->degree(4), 2 ); is( $g1->degree(5), 1 ); is( $g1->degree(6), 0 ); is( $g1->vertex_degree(1), $g1->degree(1) ); is( $g1->vertex_degree(2), $g1->degree(2) ); is( $g1->vertex_degree(3), $g1->degree(3) ); is( $g1->vertex_degree(4), $g1->degree(4) ); is( $g1->vertex_degree(5), $g1->degree(5) ); is( $g1->vertex_degree(6), $g1->degree(6) ); is( $g0->degree, 0 ); is( $g1->degree, 10 ); Graph-0.9735/t/78_expect.t0000644000175000017500000000332513774262024015060 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 18; use Graph; my $g0 = Graph->new(directed => 1); my $g1 = Graph->new(directed => 0); my $g2 = Graph->new(directed => 1); $g0->add_edge('a', 'b'); $g1->add_edge('a', 'b'); $g2->add_edge('a', 'a'); eval { $g0->expect_undirected }; like($@, qr/expected undirected graph, got directed/); eval { $g1->expect_undirected }; is($@, ''); eval { $g0->expect_directed }; is($@, ''); eval { $g1->expect_directed }; like($@, qr/expected directed graph, got undirected/); eval { $g0->expect_acyclic }; is($@, ''); eval { $g1->expect_acyclic }; is($@, ''); eval { $g2->expect_acyclic }; like($@, qr/expected acyclic graph, got cyclic/); eval { $g0->expect_dag }; is($@, ''); eval { $g1->expect_dag }; like($@, qr/expected directed acyclic graph, got undirected/); eval { $g2->expect_dag }; like($@, qr/expected directed acyclic graph, got cyclic/); eval { Graph->random_graph(42) }; like($@, qr/Graph::random_graph: argument 'vertices' missing or undef/); eval { Graph->random_graph(vertices=>100) }; is($@, ''); eval { Graph->random_graph(42,43,44) }; like($@, qr/Graph::random_graph: argument 'vertices' missing or undef/); eval { Graph::_get_options() }; like($@, qr/internal error: should be called with only one array ref argument/); eval { Graph::_get_options(1) }; like($@, qr/internal error: should be called with only one array ref argument/); eval { Graph::_get_options([]) }; is($@, ''); eval { Graph::_get_options(12,34) }; like($@, qr/internal error: should be called with only one array ref argument/); my $uf = Graph->new(undirected => 1, unionfind => 1); $uf->add_edge(qw(a b)); eval { $uf->delete_edge("a") }; like($@, qr/Graph::delete_edge: expected non-unionfind graph/); Graph-0.9735/t/63_scc.t0000644000175000017500000001174513774262024014337 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 56; use Graph; use Graph::Undirected; my $g0 = Graph->new; $g0->add_cycle(qw(a b)); $g0->add_edge(qw(b c)); my @c0 = $g0->strongly_connected_components; is(@c0, 2); @c0 = sort { @$a <=> @$b } @c0; is("@{$c0[0]}", 'c'); is("@{[sort @{$c0[1]}]}", 'a b'); is($g0->strongly_connected_graph, "a+b-c"); ok(!$g0->is_strongly_connected); my $g1 = Graph->new; $g1->add_path(qw(f f b a c b)); $g1->add_path(qw(c e d e g h g)); $g1->add_path(qw(f d)); my @c1 = $g1->strongly_connected_components; is(@c1, 4); @c1 = sort { @$a <=> @$b } @c1; is("@{[sort @{$c1[0]}]}", 'f'); is("@{[sort @{$c1[1]}]}", 'd e'); is("@{[sort @{$c1[2]}]}", 'g h'); is("@{[sort @{$c1[3]}]}", 'a b c'); my $g1s = $g1->strongly_connected_graph; is($g1s, "a+b+c-d+e,d+e-g+h,f-a+b+c,f-d+e"); is("@{[sort @{$g1s->get_vertex_attribute('a+b+c', 'subvertices')}]}", "a b c"); is("@{[sort @{$g1s->get_vertex_attribute('d+e', 'subvertices')}]}", "d e"); is("@{[sort @{$g1s->get_vertex_attribute('f', 'subvertices')}]}", "f"); is("@{[sort @{$g1s->get_vertex_attribute('g+h', 'subvertices')}]}", "g h"); is($g1s->get_vertex_attribute('h+g', 'subvertices'), undef); ok(!$g1->is_strongly_connected); my $g2 = Graph->new; $g2->add_cycle(qw(a b c)); $g2->add_cycle(qw(a d e)); my @c2 = $g2->strongly_connected_components; is(@c2, 1); @c2 = sort { @$a <=> @$b } @c2; is("@{[sort @{$c2[0]}]}", 'a b c d e'); is($g2->strongly_connected_graph, "a+b+c+d+e"); ok($g2->is_strongly_connected); my $g3 = Graph->new; $g3->add_path(qw(a b c)); $g3->add_vertices(qw(d e f)); my @c3 = $g3->strongly_connected_components; is(@c3, 6); @c3 = sort { @$a <=> @$b || "@$a" cmp "@$b" } @c3; is("@{[sort @{$c3[0]}]}", 'a'); is("@{[sort @{$c3[1]}]}", 'b'); is("@{[sort @{$c3[2]}]}", 'c'); is("@{[sort @{$c3[3]}]}", 'd'); is("@{[sort @{$c3[4]}]}", 'e'); is("@{[sort @{$c3[5]}]}", 'f'); is($g3->strongly_connected_graph, "a-b,b-c,d,e,f"); ok(!$g3->is_strongly_connected); $g3->add_cycle('d', 'a'); $g3->add_cycle('e', 'f'); is($g3->strongly_connected_graph(super_component => sub { my @v = sort @_; "(" . join("|", @v) . ")" } ), "(a|d)-(b),(b)-(c),(e|f)"); eval { $g3->strongly_connected_graph(foobar => 1) }; like($@, qr/Graph::strongly_connected_graph: Unknown option: 'foobar' /); # Example from Sedgewick Algorithms in C Third Edition 19.1 Figure 19.8 (p 150) my $g4 = Graph->new; $g4->add_edges([ 0, 1], [ 0, 5], [0, 6]); $g4->add_edges([ 2, 0], [ 2, 3]); $g4->add_edges([ 3, 2], [ 3, 5]); $g4->add_edges([ 4, 2], [ 4, 3], [4, 11]); $g4->add_edges([ 5, 4]); $g4->add_edges([ 6, 4], [ 6, 9]); $g4->add_edges([ 7, 6], [ 7, 8]); $g4->add_edges([ 8, 7], [ 8, 9]); $g4->add_edges([ 9, 10], [ 9, 11]); $g4->add_edges([10, 12]); $g4->add_edges([11, 12]); $g4->add_edges([12, 9]); my @g4s = sort { $a->[0] <=> $b->[0] } map { [sort { $a <=> $b} @$_] } $g4->strongly_connected_components; is(@g4s, 4); is("@{$g4s[0]}", "0 2 3 4 5 6"); is("@{$g4s[1]}", "1"); is("@{$g4s[2]}", "7 8"); is("@{$g4s[3]}", "9 10 11 12"); ok( $g4->same_strongly_connected_components('0', '2')); ok( $g4->same_strongly_connected_components('0', '6')); ok(!$g4->same_strongly_connected_components('0', '1')); ok( $g4->same_strongly_connected_components('7', '8')); ok( $g4->same_strongly_connected_components('9', '10')); ok( $g4->same_strongly_connected_components('9', '12')); ok(!$g4->same_strongly_connected_components('0', '7')); ok(!$g4->same_strongly_connected_components('0', '9')); is( $g4->strongly_connected_component_by_vertex('0'), $g4->strongly_connected_component_by_vertex('2')); isnt($g4->strongly_connected_component_by_vertex('0'), $g4->strongly_connected_component_by_vertex('1')); my @s = $g4->strongly_connected_components(); is( "@{[ sort $g4->strongly_connected_component_by_index(0) ]}", "@{[ sort @{ $s[0] } ]}" ); is( "@{[ sort $g4->strongly_connected_component_by_index(1) ]}", "@{[ sort @{ $s[1] } ]}" ); is( "@{[ sort $g4->strongly_connected_component_by_index(2) ]}", "@{[ sort @{ $s[2] } ]}" ); is( "@{[ sort $g4->strongly_connected_component_by_index(3) ]}", "@{[ sort @{ $s[3] } ]}" ); is( $g4->strongly_connected_component_by_index(4), undef ); my $g5 = Graph::Undirected->new; eval { $g5->strongly_connected_components }; like($@, qr/Graph::strongly_connected_components: expected directed graph, got undirected/); eval { $g5->strongly_connected_component_by_vertex }; like($@, qr/Graph::strongly_connected_component_by_vertex: expected directed graph, got undirected/); eval { $g5->strongly_connected_component_by_index }; like($@, qr/Graph::strongly_connected_component_by_index: expected directed graph, got undirected/); { # http://rt.cpan.org/NoAuth/Bug.html?id=1193 use Graph::Directed; my $graph = new Graph::Directed; $graph->add_vertex("a"); $graph->add_vertex("b"); $graph->add_vertex("c"); $graph->add_edge("a","c"); $graph->add_edge("b","c"); $graph->add_edge("c","a"); my @cc = $graph->strongly_connected_components; is(@cc, 2); } Graph-0.9735/t/86_bipartite.t0000644000175000017500000000126714741033474015555 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::Undirected; my $g0 = Graph::Undirected->new; $g0->add_path('A'..'Z'); ok($g0->is_bipartite); my $g1 = Graph::Undirected->new; $g1->add_cycle('A'..'C'); ok(!$g1->is_bipartite); my $g2 = Graph::Undirected->new; $g2->add_cycle('A'..'D'); ok($g2->is_bipartite); my $g3 = Graph::Undirected->new; for my $A ('A'..'Z') { for my $B ('1'..'9') { $g3->add_edge($A, $B); } } ok($g3->is_bipartite); my $g4 = Graph::Undirected->new; $g4->add_cycle('A'..'C'); $g4->add_cycle('1'..'6'); ok(!$g4->is_bipartite); my $g5 = Graph::Undirected->new; $g5->add_cycle('A'..'D'); $g5->add_cycle('1'..'6'); ok($g5->is_bipartite); done_testing; Graph-0.9735/t/02_trap.t0000644000175000017500000000031013774262024014510 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 2; use Graph; isnt($SIG{__DIE__}, \&Graph::__carp_confess, '$SIG{__DIE__}' ); isnt($SIG{__WARN__}, \&Graph::__carp_confess, '$SIG{__WARN__}'); Graph-0.9735/t/25_countedged.t0000644000175000017500000000157413774262024015705 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 28; use Graph; my $g1 = Graph->new; ok ( !$g1->countedged ); my $g2 = Graph->new( countedged => 1 ); ok ( $g2->countedged ); is( $g2->edges, 0 ); is( $g2->unique_edges, 0 ); ok( $g2->add_edge('a', 'b') ); is( $g2->edges, 1 ); is( $g2->unique_edges, 1 ); ok( $g2->add_edge('a', 'b') ); is( $g2->edges, 2 ); is( $g2->unique_edges, 1 ); ok( $g2->add_edge('b', 'c') ); is( $g2->edges, 3 ); is( $g2->unique_edges, 2 ); ok( $g2->add_edge('a', 'b') ); is( $g2->edges, 4 ); is( $g2->unique_edges, 2 ); ok( $g2->delete_edge('b', 'c') ); is( $g2->edges, 3 ); is( $g2->unique_edges, 1 ); ok( $g2->delete_edge('a', 'b') ); is( $g2->edges, 2 ); is( $g2->unique_edges, 1 ); ok( $g2->delete_edge('a', 'b') ); is( $g2->edges, 1 ); is( $g2->unique_edges, 1 ); ok( $g2->delete_edge('a', 'b') ); is( $g2->edges, 0 ); is( $g2->unique_edges, 0 ); Graph-0.9735/t/64_mst.t0000644000175000017500000000353613774262024014372 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 22; use Graph::Undirected; use Graph::Directed; my $g0 = Graph::Undirected->new; $g0->add_weighted_edge(qw(a b 1)); $g0->add_weighted_edge(qw(a c 2)); $g0->add_weighted_edge(qw(a d 1)); $g0->add_weighted_edge(qw(b d 2)); $g0->add_weighted_edge(qw(b e 2)); $g0->add_weighted_edge(qw(c d 2)); $g0->add_weighted_edge(qw(c f 1)); $g0->add_weighted_edge(qw(d e 1)); $g0->add_weighted_edge(qw(d f 1)); $g0->add_weighted_edge(qw(d g 2)); $g0->add_weighted_edge(qw(e g 1)); my $g1 = $g0->deep_copy; my $g0t = $g0->MST_Kruskal; ok($g0t->is_undirected); is($g0t->vertices, $g0->vertices); is($g0t->edges, $g0->vertices - 1); is($g0t, "a=b,a=d,c=f,d=e,d=f,e=g"); $g0->add_weighted_edge(qw(c f 3)); my $g0u = $g0->MST_Kruskal; ok($g0u->is_undirected); is($g0u->vertices, $g0->vertices); is($g0u->edges, $g0->vertices - 1); ok($g0u eq "a=b,a=c,a=d,d=e,d=f,e=g" || $g0u eq "a=b,a=d,c=d,d=e,d=f,e=g" || $g0u eq "a=b,a=c,c=f,d=e,e=g"); my $g1t = $g1->MST_Prim; ok($g1t->is_undirected); is($g1t->vertices, $g0->vertices); is($g1t->edges, $g0->vertices - 1); ok($g1t eq "a=b,a=d,c=f,d=e,d=f,e=g" || $g1t eq "a=b,a=c,a=d,d=e,d=f,e=g"); my $g1u = $g1->MST_Prim(first_root => "g"); ok($g1u->is_undirected); is($g1u->vertices, $g0->vertices); is($g1u->edges, $g0->vertices - 1); ok($g1u eq "a=b,a=d,c=f,d=e,d=f,e=g" || $g1u eq "a=b,a=c,a=d,d=e,d=f,e=g"); $g1->add_weighted_edge(qw(c f 3)); my $g1v = $g1->MST_Kruskal; ok($g1v->is_undirected); is($g1v->vertices, $g1->vertices); is($g1v->edges, $g1->vertices - 1); ok($g1v eq "a=b,a=c,a=d,d=e,d=f,e=g" || $g1v eq "a=b,a=d,c=d,d=e,d=f,e=g"); my $g2 = Graph::Directed->new; eval { $g2->MST_Kruskal }; like($@, qr/Graph::MST_Kruskal: expected undirected graph, got directed, /); eval { $g2->MST_Prim }; like($@, qr/Graph::MST_Prim: expected undirected graph, got directed, /); Graph-0.9735/t/24_mixvertexed.t0000644000175000017500000000120713774262024016120 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 56; use Graph; for my $m (0, 1) { for my $r (0, 1) { my $g = Graph->new(countvertexed => $m, refvertexed => $r); note "countvertexed = $m, refvertexed = $r"; $g->add_vertex("a"); $g->add_vertex("a"); $g->add_vertex(my $b = []); $g->add_vertex($b); for (1, 2) { ok( $g->has_vertices( ) ); ok( $g->has_vertex("a") ); ok( $g->has_vertex($b ) ); ok( !$g->has_vertex("e") ); is( $g->get_vertex_count("a"), $m ? 2 : 1 ); is( $g->get_vertex_count($b ), $m ? 2 : 1 ); is( $g->get_vertex_count("e"), 0 ); } } } Graph-0.9735/t/u_te_ea.t0000644000175000017500000000114313774262024014647 0ustar osboxesosboxes#!/usr/bin/perl use strict; use warnings; use Graph; use Test::More; plan tests => 4; my $graph = Graph->new( undirected => 1 ); $graph->add_vertex("Berlin"); $graph->add_vertex("Bonn"); $graph->add_edge("Berlin","Bonn"); is ("$graph","Berlin=Bonn"); $graph->set_edge_attributes("Berlin", "Bonn", { color => "red" }); is ("$graph","Berlin=Bonn"); $graph = Graph->new( undirected => 1 ); $graph->add_vertex("Berlin"); $graph->add_vertex("Bonn"); $graph->add_edge("Bonn","Berlin"); is ("$graph","Berlin=Bonn"); $graph->set_edge_attributes("Bonn", "Berlin", { color => "red" }); is ("$graph","Berlin=Bonn"); Graph-0.9735/t/77_adjacency.t0000644000175000017500000000274213774262024015512 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 18; use Graph; use Graph::AdjacencyMatrix; my $g = Graph->new(vertices => [0..9]); $g->add_edge(qw(2 3)); my $m = Graph::AdjacencyMatrix->new($g); my $am = $m->adjacency_matrix; my $dm = $m->distance_matrix; my @V = $m->vertices; # use Data::Dumper; print Dumper($am); # use Data::Dumper; print Dumper($dm); # use Data::Dumper; print Dumper(\@V); is(@{$am->[0]}, 10); is($am->vertices, 10); is("@{[sort $am->vertices]}", "0 1 2 3 4 5 6 7 8 9"); is($dm, undef); is(@V, 10); is("@{[sort @V]}", "0 1 2 3 4 5 6 7 8 9"); ok( $m->is_adjacent(2, 3)); ok(!$m->is_adjacent(3, 2)); is( $m->distance(2, 3), undef); is( $m->distance(3, 2), undef); $g->add_weighted_edge(2, 3, 45); $m = Graph::AdjacencyMatrix->new($g, distance_matrix => 0); is( $m->distance(2, 3), undef); is( $m->distance(3, 2), undef); $m = Graph::AdjacencyMatrix->new($g, distance_matrix => 1); is( $m->distance(2, 3), 45); is( $m->distance(3, 2), undef); # I for one welcome our new multiedged overlords! $g = Graph->new(vertices => [0..9], multiedged => 1); $g->set_edge_attribute_by_id(2, 3, 'c', 'other', 'hello'); $m = Graph::AdjacencyMatrix->new($g, distance_matrix => 1); is_deeply $m->distance(2, 3), undef; is( $m->distance(3, 2), undef); $g->add_weighted_edge_by_id(2, 3, 'a', 45); $g->add_weighted_edge_by_id(2, 3, 'b', 47); $m = Graph::AdjacencyMatrix->new($g, distance_matrix => 1); is_deeply $m->distance(2, 3), { a => 45, b => 47 }; is( $m->distance(3, 2), undef); Graph-0.9735/t/u_bill.t0000644000175000017500000000047113774262024014517 0ustar osboxesosboxesuse Test::More tests => 30; use strict; use Graph::Undirected; my$g = new Graph::Undirected; $g->add_edges(qw(a1 b1 b1 c1 c1 a1 a2 b2 b2 c2 c2 a2 a1 a2)); $g->add_vertices(1..5); foreach (1..10) { my @b = $g->bridges; is(@b, 1); my ($u, $v) = sort @{ $b[0] }; is($u, "a1"); is($v, "a2"); } Graph-0.9735/t/84_all_cessors.t0000644000175000017500000001135114741024740016070 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::Directed; use Graph::Undirected; sub test_graphs { my ($graphs, $methods, $label) = @_; for my $m (sort keys %$methods) { my $this_m = $methods->{$m}; for my $k (sort keys %$this_m) { my $g = $graphs->{$k}; my $gs = $g->stringify; for my $call ( @{ $this_m->{$k} } ) { my ($arg, $expected) = @$call; my @args = split ' ', $arg; is "@{[sort $g->$m(@args)]}", $expected, "$label $k($gs) $m (@args)"; } } } } sub make_graphs { my ($spec, $class, $l) = @_; +{ map { my ($V, $E) = @{ $spec->{$_} }; my $g = $class->new; $g->add_vertices(@$V); $g->add_edge(@$_) for @$E; ($l.$_ => $g); } keys %$spec }; } my %V_E = ( 0 => [ [], [] ], 1 => [ [qw(a)], [] ], '2a' => [ [qw(a b)], [] ], '2b' => [ [], [[qw(a b)]] ], '2c' => [ [], [[qw(a b)], [qw(b a)]] ], 3 => [ [], [[qw(a b)], [qw(a c)], [qw(b d)], [qw(b e)], [qw(c f)], [qw(c g)]] ], 4 => [ [], [[qw(a b)], [qw(b a)], [qw(a a)]] ], 5 => [ [], [[qw(a a)]] ], ); { my $dg = make_graphs(\%V_E, 'Graph::Directed', 'd'); is $dg->{$_->[0]}, $_->[1], $_->[0] for ( [ d0 => "" ], [ d1 => "a" ], [ d2a => "a,b" ], [ d2b => "a-b" ], [ d2c => "a-b,b-a" ], [ d3 => "a-b,a-c,b-d,b-e,c-f,c-g" ], [ d4 => "a-a,a-b,b-a" ], [ d5 => "a-a" ], ); test_graphs($dg, { all_successors => { d0 => [ ['a', ""] ], d1 => [ ['a', ""] ], d2a => [ ['a', ""], ['b', ""] ], d2b => [ ['a', "b"], ['b', ""] ], d2c => [ ['a', "a b"], ['b', "a b"] ], d3 => [ ['a', "b c d e f g"], ['b', "d e"], ['c', "f g"], ['d', ""], ['e', ""], ['f', ""], ['g', ""] ], d4 => [ ['a', "a b"], ['b', "a b"] ], d5 => [ ['a', "a"] ], }, all_predecessors => { d0 => [ ['a', ""] ], d1 => [ ['a', ""] ], d2a => [ ['a', ""], ['b', ""] ], d2b => [ ['a', ""], ['b', "a"] ], d2c => [ ['a', "a b"], ['b', "a b"] ], d3 => [ ['a', ""], ['b', "a"], ['c', "a"], ['d', "a b"], ['e', "a b"], ['f', "a c"], ['g', "a c"] ], d4 => [ ['a', "a b"], ['b', "a b"] ], d5 => [ ['a', "a"] ], }, predecessors_by_radius => { d0 => [ ['a 1', ""] ], d1 => [ ['a 1', ""] ], d2a => [ ['a 1', ""], ['b 1', ""] ], d2b => [ ['a 1', ""], ['b 1', "a"], ['b 2', "a"] ], d2c => [ ['a 0', ""], ['b 1', "a"], ['b 2', "a b"] ], d3 => [ ['a 1', ""], ['b 1', "a"], ['c 2', "a"], ['d 1', "b"], ['d 2', "a b"], ['e 1', "b"], ['f 1', "c"], ['g 1', "c"], ['g 2', "a c"] ], d4 => [ ['a 1', "a b"], ['b 1', "a"] ], d5 => [ ['a 1', "a"] ], }, all_neighbors => { d0 => [ ['a', ""] ], d1 => [ ['a', ""] ], d2a => [ ['a', ""], ['b', ""] ], d2b => [ ['a', "b"], ['b', "a"] ], d2c => [ ['a', "b"], ['b', "a"] ], d3 => [ ['a', "b c d e f g"], ['b', "a c d e f g"], ['c', "a b d e f g"], ['d', "a b c e f g"], ['e', "a b c d f g"], ['f', "a b c d e g"], ['g', "a b c d e f"] ], d4 => [ ['a', "a b"], ['b', "a"] ], d5 => [ ['a', "a"] ], }, all_reachable => { d0 => [ ['a', ""] ], d1 => [ ['a', ""] ], d2a => [ ['a', ""], ['b', ""] ], d2b => [ ['a', "b"], ['b', ""] ], d2c => [ ['a', "a b"], ['b', "a b"] ], d3 => [ ['a', "b c d e f g"], ['b', "d e"], ['c', "f g"], ['d', ""], ['e', ""], ['f', ""], ['g', ""] ], d4 => [ ['a', "a b"], ['b', "a b"] ], d5 => [ ['a', "a"] ], }, }, 'directed'); } { my $dg = make_graphs(\%V_E, 'Graph::Undirected', 'u'); is $dg->{$_->[0]}, $_->[1], $_->[0] for ( [ u0 => "" ], [ u1 => "a" ], [ u2a => "a,b" ], [ u2b => "a=b" ], [ u2c => "a=b" ], [ u3 => "a=b,a=c,b=d,b=e,c=f,c=g" ], [ u4 => "a=a,a=b" ], [ u5 => "a=a" ], ); test_graphs($dg, { all_neighbors => { u0 => [ ['a', ""] ], u1 => [ ['a', ""] ], u2a => [ ['a', ""], ['b', ""] ], u2b => [ ['a', "b"], ['b', "a"] ], u2c => [ ['a', "b"], ['b', "a"] ], u3 => [ ['a', "b c d e f g"], ['b', "a c d e f g"], ['c', "a b d e f g"], ['d', "a b c e f g"], ['e', "a b c d f g"], ['f', "a b c d e g"], ['g', "a b c d e f"] ], u4 => [ ['a', "a b"], ['b', "a"] ], u5 => [ ['a', "a"] ], }, all_reachable => { u0 => [ ['a', ""] ], u1 => [ ['a', ""] ], u2a => [ ['a', ""], ['b', ""] ], u2b => [ ['a', "b"], ['b', "a"] ], u2c => [ ['a', "b"], ['b', "a"] ], u3 => [ ['a', "b c d e f g"], ['b', "a c d e f g"], ['c', "a b d e f g"], ['d', "a b c e f g"], ['e', "a b c d f g"], ['f', "a b c d e g"], ['g', "a b c d e f"] ], u4 => [ ['a', "a b"], ['b', "a"] ], u5 => [ ['a', "a"] ], }, }, 'undirected'); } { my $d0 = Graph::Directed->new; $d0->add_edge(0,1); $d0->add_edge(1,0); my @g = sort $d0->all_successors(0); is_deeply \@g, [ 0, 1 ], 'all_successors works on false names' or diag explain \@g; } done_testing; Graph-0.9735/t/53_multiedge_attributes.t0000644000175000017500000001463114741033474020010 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph; my $g = Graph->new(multiedged => 1); $g->add_edge_by_id("a", "b", "hot"); ok !$g->has_edge_attributes_by_id("a", "b", "hot"), 'no edge attr hot' for 1..2; ok$g->set_edge_attribute_by_id("a", "b", "hot", "color", "red"), 'set edge attr'; ok( $g->has_edge_attribute_by_id("a", "b", "hot", "color"), 'has edge attr' ) for 1..2; ok( $g->has_edge_attributes_by_id("a", "b", "hot"), 'has edge attr' ) for 1..2; is( $g->get_edge_attribute_by_id("a", "b", "hot", "color"), "red" ) for 1..2; is( $g->get_edge_attribute_by_id("a", "b", "hot", "colour"), undef ) for 1..2; ok( $g->set_edge_attribute_by_id("a", "b", "hot", "color", "green") ); ok( $g->has_edge_attributes_by_id("a", "b", "hot") ) for 1..2; is( $g->get_edge_attribute_by_id("a", "b", "hot", "color"), "green" ) for 1..2; my $attr = $g->get_edge_attributes_by_id("a", "b", "hot"); my @name = $g->get_edge_attribute_names_by_id("a", "b", "hot"); my @val = $g->get_edge_attribute_values_by_id("a", "b", "hot"); is_deeply $attr, { color => "green" }, 'all attr'; is_deeply \@name, [ "color" ], 'attr names'; is_deeply \@val, [ "green" ], 'attr values'; ok $g->set_edge_attribute_by_id("a", "b", "hot", "taste", "rhubarb"), 'set'; ok $g->has_edge_attributes_by_id("a", "b", "hot"), 'has' for 1..2; is $g->get_edge_attribute_by_id("a", "b", "hot", "taste"), "rhubarb", 'get' for 1..2; is $g->get_edge_attribute_by_id("a", "b", "hot", "color"), "green", 'attr id 1'; is $g->get_edge_attribute_by_id("a", "b", "hot", "taste"), "rhubarb", 'attr id 2'; $attr = $g->get_edge_attributes_by_id("a", "b", "hot"); @name = sort $g->get_edge_attribute_names_by_id("a", "b", "hot"); @val = sort $g->get_edge_attribute_values_by_id("a", "b", "hot"); is_deeply $attr, { color => "green", taste => "rhubarb" }, 'all attr'; is_deeply \@name, [ "color", "taste" ], 'all names'; is_deeply \@val, [ "green", "rhubarb" ], 'all vals'; is_deeply +($g->as_hashes)[1], { a => { b => { hot => { color => "green", taste => "rhubarb" } } } }, 'as_hashes'; ok( $g->delete_edge_attribute_by_id("a", "b", "hot", "color" ) ); ok( !$g->has_edge_attribute_by_id("a", "b", "hot", "color" ) ); ok( $g->has_edge_attributes_by_id("a", "b", "hot") ); is( $g->get_edge_attribute_by_id("a", "b", "hot", "taste"), "rhubarb" ); ok( $g->delete_edge_attributes_by_id("a", "b", "hot") ); ok( !$g->has_edge_attributes_by_id("a", "b", "hot") ); is( $g->get_edge_attribute_by_id("a", "b", "hot", "taste"), undef ); ok( !$g->delete_edge_attribute_by_id("a", "b", "hot", "taste" ) ); ok( $g->delete_edge_attributes_by_id("a", "b", "hot") ); $attr = $g->get_edge_attributes_by_id("a", "b", "hot"); @name = $g->get_edge_attribute_names_by_id("a", "b", "hot"); @val = $g->get_edge_attribute_values_by_id("a", "b", "hot"); is_deeply $attr, undef; is_deeply \@name, []; is_deeply \@val, []; is $g->edges, 1, 'Deleting attributes does not delete edge'; $g->add_weighted_edge_by_id("c", "d", "hot", 42); ok( $g->has_edge_by_id("c", "d", "hot") ); is( $g->get_edge_attribute_by_id("c", "d", "hot", "weight"), 42 ); is( $g->edges, 2 ); $g->add_weighted_edges_by_id("c", "d", 43, "e", "f", 44, "hot"); is( $g->get_edge_weight_by_id("c", "d", "hot"), 43 ); is( $g->get_edge_weight_by_id("e", "f", "hot"), 44 ); is( $g->edges, 3 ); $g->add_weighted_path_by_id("c", 45, "d", 46, "e", "hot"); is( $g->get_edge_weight_by_id("c", "d", "hot"), 45 ); is( $g->get_edge_weight_by_id("d", "e", "hot"), 46 ); is( $g->edges, 4 ); use Graph::Undirected; my $u = Graph::Undirected->new(multiedged => 1); $u->add_weighted_edge_by_id('a', 'b', 'hot', 123); is($u->get_edge_weight_by_id('a', 'b', 'hot'), 123); is($u->get_edge_weight_by_id('b', 'a', 'hot'), 123); ok($u->set_edge_attributes_by_id('a', 'b', 'hot', { 'color' => 'pearl', 'weight' => 'heavy' })); $attr = $u->get_edge_attributes_by_id('a', 'b', 'hot'); is_deeply $attr, { color => "pearl", weight => 'heavy' }; ok( $g->set_edge_weight_by_id("a", "b", "hot", 42)); is( $g->get_edge_weight_by_id("a", "b", "hot"), 42); ok( $g->has_edge_weight_by_id("a", "b", "hot")); ok(!$g->has_edge_weight_by_id("a", "c", "hot")); ok( $g->delete_edge_weight_by_id("a", "b", "hot")); ok(!$g->has_edge_weight_by_id("a", "b", "hot")); is( $g->get_edge_weight_by_id("a", "b", "hot"), undef); my $h = Graph->new(multiedged => 1); eval { $h->set_edge_attribute("foo", "bar", "color", "gold") }; like($@, qr/expected non-multiedged/); $h->ingest($g); my $got = ($h->as_hashes)[1]; is_deeply $got, { a => { b => { hot => {} } }, c => { d => { hot => { weight => 45 } } }, d => { e => { hot => { weight => 46 } } }, e => { f => { hot => { weight => 44 } } } } or diag explain $got; ok( $g->set_edge_weight_by_id("a", "b", "hot", 42)); ok( $g->set_edge_weight_by_id("a", "b", "cool", 57)); my @vals = $g->get_edge_attribute_all("a", "b", "weight"); is_deeply [sort {$a<=>$b} @vals], [42, 57], 'get_edge_attribute_all' or diag explain \@vals; $g = Graph->new(hyperedged => 1, multiedged => 1, directed => 0); $g->set_edge_attributes_by_id('a', 'b', 'x', 'hot', { color => 'pearl', weight => 'heavy' }); $g->add_weighted_edge_by_id('a', 'b', 'hot', 123); $g->add_weighted_path_by_id("c", 45, "d", 46, "e", "hot"); $got = ($g->as_hashes)[1]; is_deeply $got, [ { vertices => ['a', 'b', 'x'], attributes => { hot => { color => 'pearl', weight => 'heavy' } }, }, { vertices => ['a', 'b'], attributes => { hot => { weight => 123 } } }, { vertices => ['c', 'd'], attributes => { hot => { weight => 45 } } }, { vertices => ['d', 'e'], attributes => { hot => { weight => 46 } } }, ], 'undirected hyperedge as_hashes' or diag explain $got; $g = Graph->new(hyperedged => 1, multiedged => 1); $g->set_edge_attributes_by_id([qw(a b c)], [qw(f g)], 'hot', { color => 'pearl', weight => 'heavy' }); $g->add_weighted_edge_by_id([qw(a b c)], [qw(f h)], 'hot', 123); $g->add_weighted_path_by_id(["c"], 45, ["d"], 46, ["e"], "hot"); $got = ($g->as_hashes)[1]; is_deeply $got, [ { predecessors => [qw(a b c)], successors => [qw(f g)], attributes => { hot => { color => 'pearl', weight => 'heavy' } }, }, { predecessors => [qw(a b c)], successors => [qw(f h)], attributes => { hot => { weight => 123 } } }, { predecessors => ['c'], successors => ['d'], attributes => { hot => { weight => 45 } } }, { predecessors => ['d'], successors => ['e'], attributes => { hot => { weight => 46 } } }, ], 'directed hyperedge as_hashes' or diag explain $got; done_testing; Graph-0.9735/t/MyUGraph.pm0000644000175000017500000000015413774262024015114 0ustar osboxesosboxespackage UGraph; use strict; use warnings; require Graph::Undirected; @UGraph::ISA=qw(Graph::Undirected); 1; Graph-0.9735/t/12_has_vertex.t0000644000175000017500000000046413774262024015725 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 7; use Graph; my $g = Graph->new; ok( !$g->has_vertex("a") ); ok( !$g->has_vertex("b") ); ok( !$g->has_vertex("a") ); ok( !$g->has_vertex("b") ); $g->add_vertex("a"); ok( $g->has_vertex("a") ); ok( !$g->has_vertex("b") ); ok( !$g->has_vertex("b") ); Graph-0.9735/t/u_ng_path.t0000644000175000017500000001577713774262024015234 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 1196; # http://rt.cpan.org/NoAuth/Bug.html?id=1179 =head1 NAME Test program for Graph. =head2 SYNOPSIS perl test_graph.pl [size (default 3 works well)] =head2 DESCRIPTION This program constructs size x size "square" directed and undirected graphs, then tests various path-finding related methods: TransitiveClosure_Floyd_Warshall, APSP_Floyd_Warshall, and SSSP (all flavors). You can think of each node as a cell in a matrix. In the directed case, each node is has edges from its neighbors down and right (the coordinates growing to down and right), eg, node 1,1 is has edges to nodes 1,2, node 2,1, and node 2,2. In the undirected case, the down left diagonal is present as well, eg, from node 1,1 to node 0,2. All edges have unit weight. This structure makes it easy to calculate the correct answers. For example, the all-pairs-shortest-path in the directed case should have an edge from every node to every other node that is further down or to the right, and the weight should be equal to the maximun difference in the coordinates of the nodes. Eg, the weight from node 1,1 to node 3,3 is 2 along the path 1,1 to 2,2 to 3,3. =head1 AUTHOR Nathan Goodman =cut use Graph; use Graph::Directed; use Graph::Undirected; # set up square graphs for my $size (0..3) { print "# size = $size\n"; my $g=Graph::Directed->new; my $h=Graph::Undirected->new; $g=construct($g, $size); $h=construct($h, $size); test_graph($g, $size); test_graph($h, $size); test_tc($g, $size); test_tc($h, $size); test_apsp($g, $size); test_apsp($h, $size); #test_sssp($g,$size,'Dijkstra'); #test_sssp($h,$size,'Dijkstra'); #test_sssp($g,'Bellman_Ford'); #test_sssp($h,'Bellman_Ford'); #test_sssp($g,'DAG'); } exit; sub construct { my($g, $size)=@_; for (my $i=0;$i<$size;$i++) { for (my $j=0;$j<$size;$j++) { my $node=node($i,$j); $g->add_vertex($node); $g->add_weighted_edge(node($i-1,$j),$node,1) if $i>0; $g->add_weighted_edge(node($i,$j-1),$node,1) if $j>0; $g->add_weighted_edge(node($i-1,$j-1),$node,1) if $i>0 && $j>0; # down-right diagonal $g->add_weighted_edge(node($i-1,$j+1),$node,1) if $g->undirected && $i>0 && $j<$size; # down-left diagonal } } return $g; } # check graph construction # all nodes that are distance 1 apart in the rectangle # should be connected by an edge of weight 1 in the graphs sub test_graph { my($g, $size)=@_; print "# test_graph ",ref $g,"\n"; for (my $i1=0;$i1<$size;$i1++) { for (my $j1=0;$j1<$size;$j1++) { my $node1=node($i1,$j1); for (my $i2=0;$i2<$size;$i2++) { for (my $j2=0;$j2<$size;$j2++) { my $node2=node($i2,$j2); if (dist($g,$node1,$node2)==1) { ok($g->has_edge($node1,$node2), "edge $node1-$node2"); my $weight=weight($g,$node1,$node2); is($weight, 1, "edge weight on edge $node1-$node2"); } else { ok(!$g->has_edge($node1, $node2), "extra edge $node1-$node2"); }}}}}} # check transitive closure # all nodes that are distance 0 or more apart in the rectangle # should be connected by an edge in the grapsh -- weights not used sub test_tc { my($g, $size)=@_; print "# test_tc ",ref $g,"\n"; my $gt=$g->TransitiveClosure_Floyd_Warshall; for (my $i1=0;$i1<$size;$i1++) { for (my $j1=0;$j1<$size;$j1++) { my $node1=node($i1,$j1); for (my $i2=0;$i2<$size;$i2++) { for (my $j2=0;$j2<$size;$j2++) { my $node2=node($i2,$j2); if (dist($gt,$node1,$node2)>=0) { ok( $gt->has_edge($node1,$node2), "edge $node1-$node2"); } else { ok( !$gt->has_edge($node1,$node2), "extra edge $node1-$node2"); }}}}}} # check all pairs shortest path # all nodes that are distance 0 or more apart in the rectangle # should be connected by an edge in the graph with weight equal to distance sub test_apsp { my($g, $size)=@_; print "# test_apsp ",ref $g,"\n"; my $gs=$g->APSP_Floyd_Warshall; for (my $i1=0;$i1<$size;$i1++) { for (my $j1=0;$j1<$size;$j1++) { my $node1=node($i1,$j1); for (my $i2=0;$i2<$size;$i2++) { for (my $j2=0;$j2<$size;$j2++) { my $node2=node($i2,$j2); my $dist=dist($gs,$node1,$node2); if ($dist>=0) { ok($gs->has_edge($node1,$node2), "edge $node1-$node2"); my $weight=weight($gs,$node1,$node2); is( $weight, $dist, "edge weight $node1-$node2" ); test_path($gs,$node1,$node2,path($gs,$node1,$node2),weight($gs,$node1,$node2)); } else { ok( !$gs->has_edge($node1,$node2), "extra edge $node1-$node2" ); }}}}}} # check single source shortest path # all nodes that are distance 0 or more apart in the rectangle # should be connected by an edge in the graph with weight equal to distance sub test_sssp { my($g,$size,$alg)=@_; print "# test_sssp $alg ",ref $g,"\n"; my $sssp=$g->can("SSSP_$alg"); for (my $i1=0;$i1<$size;$i1++) { for (my $j1=0;$j1<$size;$j1++) { my $node1=node($i1,$j1); print "# --- source $node1\n"; my $gs=$g->$sssp($node1); for (my $i2=0;$i2<$size;$i2++) { for (my $j2=0;$j2<$size;$j2++) { my $node2=node($i2,$j2); my $dist=dist($g,$node1,$node2); my $weight=weight($gs,$node2); my $path=path($gs,$node2); test_path($gs,$node1,$node2,$path,$weight); if ($dist>0) { ok( $weight > 0, "path weight $node1-$node2" ); is( $weight, $dist, "path weight $node1-$node2" ); } else { is( $weight, 0, "path weight $node1-$node2" ); }}}}}} sub test_path { my($g,$s,$t,$path,$weight)=@_; return if $s eq $t; is( @$path - 1, $weight, "path $s-$t length does not equal weight"); if ($weight) { # path may be reversed my @path=@$path; @path=reverse @path if $path[$#path] eq $s; is( $path[0], $s, "path $s-$t should start at $s"); for (my $i=0;$i<$#path;$i++) { is(dist($g,$path->[$i],$path->[$i+1]), 1, "adjacent nodes in path $s-$t should be distance 1 apart"); } } } sub node { my($i,$j)=@_; $j=$i unless defined $j; "$i/$j"; } sub weight { my($g,$node1,$node2)=@_; return $g->path_length($node1,$node2); } sub path { my($g,$node1,$node2)=@_; return [ $g->path_vertices($node1,$node2) ]; } #distances in rectangular graphs with diagonal edges sub dist { my($g)=shift @_; _dist($g->directed,@_); } sub _dist { my($directed)=shift @_; my($i1,$j1,$i2,$j2); if (@_==2) { # args are nodes my($node1,$node2)=@_; ($i1,$j1)=split('/',$node1); ($i2,$j2)=split('/',$node2); } else { # args are indices ($i1,$j1,$i2,$j2)=@_; } if ($directed) { return -1 unless $i1<=$i2 && $j1<=$j2; return max($i2-$i1,$j2-$j1); } else { return max(abs $i2-$i1,abs $j2-$j1); } } sub min { if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];} return undef unless @_; if ($#_==1) {my($x,$y)=@_; return ($x<=$y?$x:$y);} my $min=shift @_; map {$min=$_ if $_<$min} @_; $min; } sub max { if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];} return undef unless @_; if ($#_==1) {my($x,$y)=@_; return ($x>=$y?$x:$y);} my $max=shift @_; map {$max=$_ if $_>$max} @_; $max; } Graph-0.9735/t/42_add_path.t0000644000175000017500000000165614102052753015320 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 16; use Graph; my $g = Graph->new; $g->add_path("a", "b", "c"); # @todo: hyperedges is $g, "a-b,b-c"; ok( $g->has_path("a", "b", "c") ); ok( ! $g->has_path("a", "c", "b") ); ok( ! $g->has_path("b", "a", "c") ); ok( ! $g->has_path("b", "c", "a") ); ok( ! $g->has_path("c", "a", "b") ); ok( ! $g->has_path("c", "b", "a") ); my $h = Graph->new(undirected => 1); # @todo: hyperedges $h->add_path("a", "b", "c"); is $h, "a=b,b=c"; ok( $h->has_path("a", "b", "c") ); ok( ! $h->has_path("a", "c", "b") ); ok( ! $h->has_path("b", "a", "c") ); ok( ! $h->has_path("b", "c", "a") ); ok( ! $h->has_path("c", "a", "b") ); ok( $h->has_path("c", "b", "a") ); $g = Graph->new; $g->add_path("a", "b", "c", "d", "e"); $g->delete_path("a", "b", "c"); is $g, "c-d,d-e,a,b"; $h = Graph->new(undirected => 1); $h->add_path("a", "b", "c", "d", "e"); $h->delete_path("a", "b", "c"); is $h, "c=d,d=e,a,b"; Graph-0.9735/t/u_bf.t0000644000175000017500000000277313743337306014175 0ustar osboxesosboxes# rt.cpan.org #20185: problem with SPT_Bellman_Ford use strict; use Test::More tests => 7; use Graph; use Graph::Directed; use Graph::Undirected; my $g_1 = Graph::Undirected -> new(unionfind => 1); my @edge = ( [ '16977', '14903' ], [ '21062', '4504' ], [ '14671', '10554' ], [ '14903', '8891' ], [ '9714', '14671' ], [ '4504', '13544' ], [ '9714', '13544' ], [ '16977', '8891' ], [ '21062', '21062' ], [ '9714', '4504' ], [ '14671', '21687' ], [ '14671', '16977' ], [ '4504', '21687' ], [ '10554', '14903' ], [ '9714', '21687' ], [ '13544', '14671' ], [ '21062', '14671' ], [ '10554', '8891' ], [ '14671', '14903' ], [ '14671', '14671' ], [ '13544', '13544' ], [ '14671', '14026' ], [ '4504', '14671' ], [ '14671', '8891' ], [ '13544', '14026' ], [ '10554', '16977' ], ); $g_1 -> add_edges(@edge); my $spt_1 = $g_1 -> SPT_Bellman_Ford; is($spt_1->vertices, $g_1->vertices); my $g_2 = Graph::Undirected -> new(); $g_2 -> add_edges(@edge); my $spt_2 = $g_2 -> SPT_Bellman_Ford; is($spt_2->vertices, $g_2->vertices); my $g_3 = Graph::Directed -> new(); $g_3 -> add_edges(@edge); my $spt_3a = $g_3 -> SPT_Bellman_Ford('21062'); is($spt_3a->vertices, $g_3->vertices - 1); ok(!$spt_3a->has_vertex('9714')); my $spt_3b = $g_3 -> SPT_Bellman_Ford('4504'); is($spt_3b->vertices, $g_3->vertices - 2); ok(!$spt_3b->has_vertex('9714')); ok(!$spt_3b->has_vertex('21062')); Graph-0.9735/t/u_bo_ap1.t0000644000175000017500000000424013774262024014734 0ustar osboxesosboxesuse Test::More; use Graph::Undirected; use strict; my $g = Graph::Undirected->new; while () { chomp; my ($v1,$v2) = split ','; $g->add_edge($v1, $v2); } my $R = 3; plan tests => $R * scalar $g->vertices; my @exp = "DIP:3047N DIP:3051N DIP:3053N DIP:3056N DIP:3059N DIP:3069N DIP:3075N DIP:3089N DIP:3095N DIP:3101N DIP:3103N DIP:3109N DIP:3120N"; for my $v ($g->vertices) { for (1..$R) { my @rts = sort $g->articulation_points(first_root => $v); is("@rts", "@exp"); $g->biconnectivity_clear_cache; } } __END__ DIP:3048N,DIP:3047N DIP:3050N,DIP:3047N DIP:3051N,DIP:3051N DIP:3052N,DIP:3051N DIP:3053N,DIP:3051N DIP:3054N,DIP:3051N DIP:3055N,DIP:3056N DIP:3057N,DIP:3056N DIP:3058N,DIP:3056N DIP:3059N,DIP:3056N DIP:3060N,DIP:3056N DIP:3061N,DIP:3056N DIP:3062N,DIP:3056N DIP:3063N,DIP:3056N DIP:3064N,DIP:3056N DIP:3065N,DIP:3056N DIP:3066N,DIP:3056N DIP:3067N,DIP:3056N DIP:3053N,DIP:3056N DIP:3068N,DIP:3056N DIP:3070N,DIP:3069N DIP:3071N,DIP:3069N DIP:3072N,DIP:3069N DIP:3073N,DIP:3069N DIP:3074N,DIP:3074N DIP:3053N,DIP:3074N DIP:3075N,DIP:3075N DIP:3076N,DIP:3075N DIP:3077N,DIP:3075N DIP:3078N,DIP:3075N DIP:3079N,DIP:3075N DIP:3080N,DIP:3075N DIP:3081N,DIP:3075N DIP:3082N,DIP:3075N DIP:3083N,DIP:3075N DIP:3084N,DIP:3075N DIP:3085N,DIP:3075N DIP:3086N,DIP:3075N DIP:3087N,DIP:3075N DIP:3088N,DIP:3075N DIP:3090N,DIP:3089N DIP:3091N,DIP:3089N DIP:3092N,DIP:3089N DIP:3093N,DIP:3089N DIP:3094N,DIP:3089N DIP:3082N,DIP:3089N DIP:3083N,DIP:3089N DIP:3095N,DIP:3089N DIP:3096N,DIP:3089N DIP:3097N,DIP:3089N DIP:3087N,DIP:3089N DIP:3098N,DIP:3089N DIP:3099N,DIP:3089N DIP:3100N,DIP:3089N DIP:3102N,DIP:3101N DIP:3103N,DIP:3101N DIP:3104N,DIP:3101N DIP:3095N,DIP:3101N DIP:3105N,DIP:3101N DIP:3106N,DIP:3101N DIP:3107N,DIP:3107N DIP:3108N,DIP:3108N DIP:3110N,DIP:3109N DIP:3111N,DIP:3109N DIP:3112N,DIP:3109N DIP:3112N,DIP:3111N DIP:3059N,DIP:3109N DIP:3113N,DIP:3109N DIP:3114N,DIP:3109N DIP:3115N,DIP:3109N DIP:3116N,DIP:3109N DIP:3117N,DIP:3109N DIP:3118N,DIP:3109N DIP:3119N,DIP:3119N DIP:3053N,DIP:3119N DIP:3120N,DIP:3120N DIP:3121N,DIP:3120N DIP:3122N,DIP:3120N DIP:3103N,DIP:3123N DIP:3103N,DIP:3123N DIP:3103N,DIP:3123N DIP:3122N,DIP:3120N DIP:3122N,DIP:3120N Graph-0.9735/t/03_derived.t0000644000175000017500000000032413774262024015172 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 4; use lib 't'; use MyGraph; my $g = MyGraph->new; isa_ok($g, 'MyGraph'); isa_ok($g, 'Graph'); my $h = $g->new; isa_ok($h, 'MyGraph'); isa_ok($h, 'Graph'); Graph-0.9735/t/62_bcc.t0000644000175000017500000002701114741033474014306 0ustar osboxesosboxesuse strict; use warnings; use Graph; use Test::More; my $N = 5; sub prettyn { join('; ', map { qq[@$_] } sort { my @a = @$a; my @b = @$b; my $c = @b <=> @a; return $c if $c; while (@a && @b) { $c = (shift @a) <=> (shift @b); return $c if $c; } return @a - @b } map { [ sort { $a <=> $b } @$_ ] } @{ $_[0] }); } sub prettya { join('; ', map { qq[@$_] } sort { my @a = @$a; my @b = @$b; my $c = @b <=> @a; return $c if $c; while (@a && @b) { $c = (shift @a) cmp (shift @b); return $c if $c; } return @a - @b } map { [ sort @$_ ] } @{ $_[0] }); } my $g0a = Graph->new(undirected => 1); for (0..$N-1) { my ($ap, $bc, $br) = $g0a->biconnectivity; is("@{[sort { $a <=> $b } @$ap? @$ap : ()]}", ""); is("@{[prettyn($bc)]}", ""); is("@{[prettyn($br)]}", ""); } ok(!$g0a->is_biconnected); is( $g0a->is_edge_connected, undef); is( $g0a->is_edge_separable, undef); is("@{[sort { $a <=> $b } $g0a->articulation_points]}", ""); is("@{[prettyn([$g0a->biconnected_components])]}", ""); is("@{[prettyn([$g0a->bridges])]}", ""); my $g0b = Graph->new(undirected => 1); $g0b->add_vertex("a"); for (0..$N-1) { my ($ap, $bc, $br) = $g0b->biconnectivity; is("@{[sort { $a <=> $b } @$ap? @$ap : ()]}", ""); is("@{[prettyn($bc)]}", ""); is("@{[prettyn($br)]}", ""); } ok(!$g0b->is_biconnected); is( $g0b->is_edge_connected, undef); is( $g0b->is_edge_separable, undef); is("@{[sort { $a <=> $b } $g0b->articulation_points]}", ""); is("@{[prettyn([$g0b->biconnected_components])]}", ""); is("@{[prettyn([$g0b->bridges])]}", ""); my $g0c = Graph->new(undirected => 1); $g0c->add_edge(qw(a b)); for (0..$N-1) { my ($ap, $bc, $br) = $g0c->biconnectivity; is(@$ap, 0); is("@{[sort { $a <=> $b } @$ap? @$ap : ()]}", ""); is("@{[prettya($bc)]}", "a b"); is("@{[prettya($br)]}", "a b"); } ok(!$g0c->is_biconnected); is( $g0c->is_edge_connected, undef); is( $g0c->is_edge_separable, undef); is("@{[sort { $a <=> $b } $g0c->articulation_points]}", ""); is("@{[prettya([$g0c->biconnected_components])]}", "a b"); is("@{[prettya([$g0c->bridges])]}", "a b"); my $g0d = Graph->new(undirected => 1); $g0d->add_edge(qw(a b)); $g0d->add_edge(qw(b c)); for (0..$N-1) { my ($ap, $bc, $br) = $g0d->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b"); is("@{[prettya($bc)]}", "a b; b c"); is("@{[prettya($br)]}", "a b; b c"); } ok(!$g0d->is_biconnected); ok(!$g0d->is_edge_connected); ok( $g0d->is_edge_separable); is("@{[sort $g0d->articulation_points]}", "b"); is("@{[prettya([$g0d->biconnected_components])]}", "a b; b c"); is("@{[prettya([$g0d->bridges])]}", "a b; b c"); my $g0e = Graph->new(undirected => 1); $g0e->add_edge(qw(a b)); $g0e->add_edge(qw(b c)); $g0e->add_edge(qw(c d)); for (0..$N-1) { my ($ap, $bc, $br) = $g0e->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b c"); is("@{[prettya($bc)]}", "a b; b c; c d"); is("@{[prettya($br)]}", "a b; b c; c d"); } ok(!$g0e->is_biconnected); ok(!$g0e->is_edge_connected); ok( $g0e->is_edge_separable); is("@{[sort $g0e->articulation_points]}", "b c"); is("@{[prettya([$g0e->biconnected_components])]}", "a b; b c; c d"); is("@{[prettya([$g0e->bridges])]}", "a b; b c; c d"); my $g0f = Graph->new(undirected => 1); $g0f->add_cycle(qw(a b c)); for (0..$N-1) { my ($ap, $bc, $br) = $g0f->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", ""); is("@{[prettya($bc)]}", "a b c"); is("@{[prettya($br)]}", ""); } ok( $g0f->is_biconnected); ok( $g0f->is_edge_connected); ok(!$g0f->is_edge_separable); is("@{[sort $g0f->articulation_points]}", ""); is("@{[prettya([$g0f->biconnected_components])]}", "a b c"); is("@{[prettya([$g0f->bridges])]}", ""); my $g0g = Graph->new(undirected => 1); $g0g->add_cycle(qw(a b c d)); for (0..$N-1) { my ($ap, $bc, $br) = $g0g->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", ""); is("@{[prettya($bc)]}", "a b c d"); is("@{[prettya($br)]}", ""); } ok( $g0g->is_biconnected); ok( $g0g->is_edge_connected); ok(!$g0g->is_edge_separable); is("@{[sort $g0g->articulation_points]}", ""); is("@{[prettya([$g0g->biconnected_components])]}", "a b c d"); is("@{[prettya([$g0g->bridges])]}", ""); my $g0h = Graph->new(undirected => 1); $g0h->add_cycle(qw(a b c)); $g0h->add_edge(qw(b d)); for (0..$N-1) { my ($ap, $bc, $br) = $g0h->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b"); is("@{[prettya($bc)]}", "a b c; b d"); is("@{[prettya($br)]}", "b d"); } ok(!$g0h->is_biconnected); ok(!$g0h->is_edge_connected); ok( $g0h->is_edge_separable); is("@{[sort $g0h->articulation_points]}", "b"); is("@{[prettya([$g0h->biconnected_components])]}", "a b c; b d"); is("@{[prettya([$g0h->bridges])]}", "b d"); my $g0i = Graph->new(undirected => 1); $g0i->add_cycle(qw(a b c)); $g0i->add_edge(qw(b d)); $g0i->add_edge(qw(d e)); for (0..$N-1) { my ($ap, $bc, $br) = $g0i->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b d"); is("@{[prettya($bc)]}", "a b c; b d; d e"); is("@{[prettya($br)]}", "b d; d e"); } ok(!$g0i->is_biconnected); ok(!$g0i->is_edge_connected); ok( $g0i->is_edge_separable); is("@{[sort $g0i->articulation_points]}", "b d"); is("@{[prettya([$g0i->biconnected_components])]}", "a b c; b d; d e"); is("@{[prettya([$g0i->bridges])]}", "b d; d e"); my $g0j = Graph->new(undirected => 1); $g0j->add_cycle(qw(a b c)); $g0j->add_cycle(qw(b d e)); for (0..$N-1) { my ($ap, $bc, $br) = $g0j->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b"); is("@{[prettya($bc)]}", "a b c; b d e"); is("@{[prettya($br)]}", ""); } ok(!$g0j->is_biconnected); ok( $g0j->is_edge_connected); ok(!$g0j->is_edge_separable); is("@{[sort $g0j->articulation_points]}", "b"); is("@{[prettya([$g0j->biconnected_components])]}", "a b c; b d e"); is("@{[prettya([$g0j->bridges])]}", ""); my $g0k = Graph->new(undirected => 1); $g0k->add_cycle(qw(a b c)); $g0k->add_cycle(qw(d e f)); $g0k->add_edge(qw(b d)); for (0..$N-1) { my ($ap, $bc, $br) = $g0k->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b d"); is("@{[prettya($bc)]}", "a b c; d e f; b d"); is("@{[prettya($br)]}", "b d"); } ok(!$g0k->is_biconnected); ok(!$g0k->is_edge_connected); ok( $g0k->is_edge_separable); is("@{[sort $g0k->articulation_points]}", "b d"); is("@{[prettya([$g0k->biconnected_components])]}", "a b c; d e f; b d"); is("@{[prettya([$g0k->bridges])]}", "b d"); my $g0l = Graph->new(undirected => 1); $g0l->add_cycle(qw(a b c)); $g0l->add_cycle(qw(d e f)); $g0l->add_cycle(qw(g h i)); $g0l->add_edge(qw(b d)); $g0l->add_edge(qw(d g)); for (0..$N-1) { my ($ap, $bc, $br) = $g0l->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b d g"); is("@{[prettya($bc)]}", "a b c; d e f; g h i; b d; d g"); is("@{[prettya($br)]}", "b d; d g"); } ok(!$g0l->is_biconnected); ok(!$g0l->is_edge_connected); ok( $g0l->is_edge_separable); is("@{[sort $g0l->articulation_points]}", "b d g"); is("@{[prettya([$g0l->biconnected_components])]}", "a b c; d e f; g h i; b d; d g"); is("@{[prettya([$g0l->bridges])]}", "b d; d g"); my $g0m = Graph->new(undirected => 1); $g0m->add_cycle(qw(a b c)); $g0m->add_cycle(qw(b d e)); $g0m->add_cycle(qw(b h i)); for (0..$N-1) { my ($ap, $bc, $br) = $g0m->biconnectivity; is("@{[sort @$ap? @$ap : ()]}", "b"); is("@{[prettya($bc)]}", "a b c; b d e; b h i"); is("@{[prettya($br)]}", ""); } ok(!$g0m->is_biconnected); ok( $g0m->is_edge_connected); ok(!$g0m->is_edge_separable); is("@{[sort $g0m->articulation_points]}", "b"); is("@{[prettya([$g0m->biconnected_components])]}", "a b c; b d e; b h i"); is("@{[prettya([$g0m->bridges])]}", ""); is("@{[sort $g0m->cut_vertices]}", "b"); my $g1 = Graph->new(undirected => 1); $g1->add_cycle(qw(0 1 2 6)); $g1->add_cycle(qw(7 8 10)); $g1->add_cycle(qw(3 4 5)); $g1->add_cycle(qw(4 9 11)); $g1->add_edge(qw(11 12)); $g1->add_edge(qw(0 5)); $g1->add_edge(qw(6 7)); for (0..2*$N-1) { my ($ap, $bc, $br) = $g1->biconnectivity; is("@{[sort { $a <=> $b } @$ap]}", "0 4 5 6 7 11"); is("@{[prettyn($bc)]}", "0 1 2 6; 3 4 5; 4 9 11; 7 8 10; 0 5; 6 7; 11 12"); is("@{[prettyn($br)]}", "0 5; 6 7; 11 12"); } my $g2 = Graph->new(undirected => 1); $g2->add_cycle(qw(a b c)); $g2->add_cycle(qw(d e f)); $g2->add_cycle(qw(f g h)); $g2->add_edge(qw(c d)); $g2->add_edge(qw(h i)); $g2->add_edge(qw(i j)); $g2->add_edge(qw(j k)); for (0..2*$N-1) { my ($ap, $bc, $br) = $g2->biconnectivity; is("@{[sort @$ap]}", "c d f h i j"); is("@{[prettya($bc)]}", "a b c; d e f; f g h; c d; h i; i j; j k"); is("@{[prettya($br)]}", "c d; h i; i j; j k"); } my $g3 = Graph->new(undirected => 1); $g3->add_path(qw(s a e i k j i)); $g3->add_path(qw(s b a f e)); $g3->add_path(qw(b f)); $g3->add_path(qw(s c g d h l)); $g3->add_path(qw(s d)); $g3->add_path(qw(c h)); for (0..2*$N-1) { my ($ap, $bc, $br) = $g3->biconnectivity; is("@{[sort @$ap]}", "e h i s"); is("@{[prettya($bc)]}", "a b e f s; c d g h s; i j k; e i; h l"); is("@{[prettya($br)]}", "e i; h l"); } is( $g3->biconnected_components, 5 ); my @c0 = map $g3->biconnected_component_by_index(0), 1..5; my @c1 = map $g3->biconnected_component_by_index(1), 1..5; my @c2 = map $g3->biconnected_component_by_index(2), 1..5; is( "@{$c0[0]}", "@{$c0[$_]}" ) for 1..4; is( "@{$c1[0]}", "@{$c1[$_]}" ) for 1..4; is( "@{$c2[0]}", "@{$c2[$_]}" ) for 1..4; isnt( "@{$c0[0]}", "@{$c1[0]}" ); isnt( "@{$c0[0]}", "@{$c2[0]}" ); is( $g3->biconnected_component_by_index(5), undef ); my $g3c = $g3->biconnected_graph(); is( $g3c, "a+b+e+f+s=c+d+g+h+s,a+b+e+f+s=e+i,c+d+g+h+s=h+l,e+i=i+j+k"); ok( $g3->same_biconnected_components('a', 'b') ); is( $g3->biconnected_component_by_vertex('a'), $g3->biconnected_component_by_vertex('b') ); ok( $g3->same_biconnected_components('a', 'b', 'e') ); ok(!$g3->same_biconnected_components('a', 'c') ); ok(!$g3->same_biconnected_components('a', 'b', 'c') ); is("@{[sort @{ $g3c->get_vertex_attribute('a+b+e+f+s', 'subvertices') }]}", "a b e f s"); is("@{[sort @{ $g3c->get_vertex_attribute('i+j+k', 'subvertices') }]}", "i j k"); is($g3c->get_vertex_attribute('i+k+j', 'subvertices'), undef); my $g4 = Graph->new(undirected => 1, edges => [[qw(a b)], [qw(a c)], [qw(a d)]]); # edges coloured by what comp they're in - >1 colour on vertex = in >1 comp my @component_colours = do { no warnings 'qw'; qw(#FF0000 #00FF00 #0000FF #FFFF00 #FF00FF #00FFFF) }; sub _bicon_graphvizify { my ($g) = @_; my ($gc, @bc) = ($g->copy, $g->biconnected_components); require Set::Object; for my $ci (0..$#bc) { for my $v (@{ $bc[$ci] }) { $gc->set_vertex_attribute($v, 'components', (my $bcs = $gc->get_vertex_attribute($v, 'components') || Set::Object->new)); $bcs->insert($ci); } } $gc->set_graph_attribute(graphviz => { groups => [ map +{ attributes => { subgraph => { pencolor => $component_colours[ $_ % @component_colours ] }, name => "cluster_$_", }, nodes => $bc[$_], }, 0..$#bc ]}); for my $u ($g->vertices) { my $comps_u = $gc->get_vertex_attribute($u, 'components'); for my $v ($g->successors($u)) { my $comps_v = $gc->get_vertex_attribute($v, 'components'); my $colour = $component_colours[ ($comps_u->intersection($comps_v)->members)[0] % @component_colours ]; $gc->set_edge_attribute($u, $v, graphviz => { color => $colour }); } } $gc; } #require GraphViz2; #open my $fh, '>', 'bicon.dot'; #my $g_gv = _bicon_graphvizify($g3); #print $fh GraphViz2->from_graph($g_gv)->dot_input; ok !$g4->same_biconnected_components(qw(a b c)); eval { Graph->new->biconnectivity }; like($@, qr/Graph::biconnectivity: expected undirected graph, got directed/); done_testing; Graph-0.9735/t/06_new.t0000644000175000017500000000432114741033474014345 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph; for my $c (qw(Graph Graph::Directed Graph::Undirected)) { test_prop($c, @$_) for ( # 2nd is whether default is true, then opposites [refvertexed => 0, []], [countvertexed => 0, []], [multivertexed => 0, []], [undirected => $c eq 'Graph::Undirected', [qw(directed)]], [directed => $c ne 'Graph::Undirected', [qw(undirected)]], [countedged => 0, []], [multiedged => 0, []], [hyperedged => 0, []], ); } sub test_prop { my ($class, $prop, $true_by_default, $opposites) = @_; my $g = $class->new; my $got = $g->$prop; $got = !$got if !$true_by_default; ok $got, "$prop correct default value"; $g = $class->new( $prop => 0 ); ok !$g->$prop, "$prop reflects given false value"; ok !$g->new->$prop, "$prop survives $class->new with false value"; ok $g->$_, "$prop opposite=$_ right" for @$opposites; $g = $class->new( $prop => 1 ); ok $g->$prop, "$prop reflects given true value"; ok $g->new->$prop, "$prop survives $class->new with true value"; ok !$g->$_, "$prop opposite=$_ right" for @$opposites; $g = $g->copy; ok $g->$prop, "$prop survives copy"; } { eval { Graph->new(foobar => 1) }; like($@, qr/Graph::new: Unknown option: 'foobar' /); eval { Graph->new(foobar => 0) }; like($@, qr/Graph::new: Unknown option: 'foobar' /); eval { Graph->new(foobar => 1, barfoo => 1) }; like($@, qr/Graph::new: Unknown options: 'barfoo' 'foobar' /); } { my $g = Graph->new(vertices => [0, 1, 2]); ok($g->has_vertex(0)); ok($g->has_vertex(1)); ok($g->has_vertex(2)); } { my $g = Graph->new(edges => [[0, 1], [2, 3]]); is $g, "0-1,2-3"; } { my $g = Graph->new(vertices => [0], edges => [[1, 2], [2, 3]]); ok($g->has_vertex(0)); is $g, "1-2,2-3,0"; } { my $g = Graph->new(multiedged => 1); my $h = $g->new; # The flags should be inherited. ok($h->is_multiedged); $h = $g->new(multiedged => 0); # The flags should be overridable ok !$h->is_multiedged; } use Graph::Directed; my $d = Graph::Directed->new; is(ref $d, 'Graph::Directed'); use Graph::Undirected; my $u = Graph::Undirected->new; is(ref $u, 'Graph::Undirected'); done_testing; Graph-0.9735/t/59_dfs.t0000644000175000017500000004336414102052753014342 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::Directed; use Graph::Undirected; use Graph::Traversal::DFS; my $g0 = Graph::Undirected->new; my $g1 = Graph::Directed->new; my $g2 = Graph::Undirected->new; # cyclic my $g3 = Graph::Undirected->new; # unconnected my $g4 = Graph::Directed->new; # cyclic loop my $g5 = Graph::Directed->new; # cyclic my $g6 = Graph::Directed->new; my $g7 = Graph::Undirected->new; # empty my $g8 = Graph::Undirected->new; # only vertices my $g9 = Graph::Directed->new; my $ga = Graph::Directed->new; $g0->add_path(qw(a b c)); $g0->add_path(qw(a b d)); $g0->add_path(qw(a e f)); $g1->add_path(qw(a b c)); $g1->add_path(qw(a b d)); $g1->add_path(qw(a e f)); $g2->add_cycle(qw(a b c)); $g3->add_path(qw(a b c)); $g3->add_path(qw(d e f)); $g4->add_cycle(qw(a)); $g5->add_cycle(qw(a b c)); $g6->add_path(qw(a b c)); $g6->add_path(qw(d e f)); $g9->add_cycle(qw(a b c)); $g9->add_path(qw(b d e f)); $g9->add_edge(qw(d f)); $ga->add_cycle(qw(a b c)); $ga->add_path(qw(b d e f)); $ga->add_edge(qw(d f)); sub simple { my $g = shift; my @v = $g->vertices; local $Test::Builder::Level = $Test::Builder::Level + 1; is(@_, @v, "vertices") or diag explain \@_; my %v; $v{$_}++ for @_; is_deeply [ grep { ($v{$_} || 0) != 1 } @v ], [], "... once" or diag explain \%v; } { my $t = Graph::Traversal::DFS->new($g0); is($t->unseen, $g0->vertices, "fresh traversal"); is($t->seen, 0); is($t->seeing, 0); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; simple($g0, @t0); simple($g0, @t1); simple($g0, @t2); is($t->graph, $g0, "graph"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g0, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->postorder; simple($g1, @t0); simple($g1, @t1); simple($g1, @t2); is("@pre", "a b c d e f", "pre"); is("@post", "c d b f e a", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "a", "roots"); ok( $t->is_root('a') ); ok(!$t->is_root('b') ); ok(!$t->is_root('c') ); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g1, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1, first_root => 'b'); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; simple($g1, @t0); simple($g1, @t1); simple($g1, @t2); is("@pre", "b c d a e f", "pre"); is("@post", "c d b f e a", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "b a", "roots"); ok( $t->is_root('a') ); ok( $t->is_root('b') ); ok(!$t->is_root('c') ); } { my $t0 = Graph::Traversal::DFS->new($g0, next_alphabetic => 1); is($t0->next, "a", "scalar next"); $t0->terminate; is($t0->next, undef, "terminate"); $t0->reset; is($t0->next, "a", "after reset scalar next"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g2, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; simple($g2, @t0); simple($g2, @t1); simple($g2, @t2); is("@pre", "a b c", "pre"); is("@post", "c b a", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 3, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c", "seen all"); is("@{[$t->roots]}", "a", "roots"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g3, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; simple($g3, @t0); simple($g3, @t1); simple($g3, @t2); is("@pre", "a b c d e f", "pre"); is("@post", "c b a f e d", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "a d", "roots"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g4, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1, find_a_cycle => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; is("@pre", "a", "pre"); is("@post", "a", "post"); is("@t0", "a", "t0"); is("@t1", "a", "t1"); is("@t2", "a", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 1, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a", "seen all"); is("@{[$t->roots]}", "a", "roots"); is("@{$t->{state}->{a_cycle}}", "a", "cycle"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g5, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1, find_a_cycle => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; is("@pre", "a b c", "pre"); is("@post", "c b", "post"); is("@t0", "a b c", "t0"); is("@t1", "c b", "t1"); is("@t2", "c b", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 3, "seen all"); is($t->seeing, 1, "seeing one"); is("@{[sort $t->seen]}", "a b c", "seen all"); is("@{[$t->roots]}", "a", "roots"); is("@{$t->{state}->{a_cycle}}", "b c a", "cycle"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g2, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_alphabetic => 1, find_a_cycle => 1); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; is("@pre", "a b c", "pre"); is("@post", "c b", "post"); is("@t0", "a b c", "t0"); is("@t1", "c b", "t1"); is("@t2", "c b", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 3, "seen all"); is($t->seeing, 1, "seeing one"); is("@{[sort $t->seen]}", "a b c", "seen all"); is("@{[$t->roots]}", "a", "roots"); is("@{$t->{state}->{a_cycle}}", "b c a", "cycle"); } { my $g = Graph::Undirected->new; $g->add_path(qw(a b c d e)); $g->add_path(qw(b f g)); $g->add_cycle(qw(c h i)); my @c = $g->find_a_cycle(next_alphabetic => 1); is(@c, 3, "find_a_cycle"); is("@c", "h i c", "find_a_cycle"); } { my $g = Graph::Directed->new; my $h = Graph::Undirected->new; $g->add_path(qw(a b c d e)); $g->add_path(qw(b f g)); $g->add_path(qw(c h i)); ok($g->is_dag, "is_dag true for dag"); $h->add_path(qw(a b c d e)); $h->add_path(qw(b f g)); $h->add_path(qw(c h i)); ok(!$h->is_dag, "is_dag false for undirected"); my @t = $g->topological_sort(next_alphabetic => 1); is(@t, 9, "topological_sort"); is("@t", "a b f g c h i d e", "topological_sort"); ok($g->is_dag, "directed acyclic is dag"); $g->add_path(qw(i c)); ok(!$g->is_dag, "directed cyclic is not dag"); } { my $g = Graph::Undirected->new; ok(!$g->is_dag, "undirected is not dag"); eval { $g->topological_sort }; like($@, qr/^Graph::topological_sort: expected directed acyclic graph, got undirected, /, "topological_sort not for undirected"); my $d = Graph::Directed->new; $d->add_cycle(qw(a b)); eval { $d->toposort }; like($@, qr/^Graph::topological_sort: expected directed acyclic graph, got cyclic, /, "topological_sort not for cyclic"); } { ok( $g0->is_connected, "is_connected"); eval { $g1->is_connected }; like($@, qr/Graph::is_connected: expected undirected graph, got directed, /, "directed cannot be tested for connectedness/"); ok( $g1->is_weakly_connected, "... directed is weakly connected"); ok( $g2->is_connected, "... cyclic undirected" ); ok(!$g3->is_connected, "... undirected unconnected"); eval { $g4->is_connected }; like($@, qr/Graph::is_connected: expected undirected graph, got directed, /, "... cyclic loop"); ok( $g4->is_weakly_connected, "... cyclic loop weakly connected"); eval { $g5->is_connected }; like( $@, qr/Graph::is_connected: expected undirected graph, got directed, /, "... cyclic directed"); ok( $g5->is_weakly_connected, "... cyclic directed weakly connected"); eval { $g6->is_connected }; like($@, qr/Graph::is_connected: expected undirected graph, got directed, /, "... directed unconnected"); ok(!$g6->is_weakly_connected, "... directed unconnected is not weakly connected"); } { my $t = Graph::Traversal::DFS->new($g7); is($t->unseen, $g7->vertices, "empty graph"); is($t->seen, 0); is($t->seeing, 0); simple($g7, $t->preorder); simple($g7, $t->postorder); simple($g7, $t->dfs); } { $g8->add_vertices(qw(a b c d)); my $t = Graph::Traversal::DFS->new($g8); is($t->unseen, $g8->vertices, "only vertices"); is($t->seen, 0); is($t->seeing, 0); simple($g8, $t->preorder); simple($g8, $t->postorder); simple($g8, $t->dfs); is_deeply [ sort $t->seen ], [ $g8->vertices ] or diag explain [ $t->seen ]; } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g3, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, first_root => "a", next_root => undef); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; is("@pre", "a b c", "pre"); is("@post", "c b a", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 3, "unseen half"); is($t->seen, 3, "seen half"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c", "seen half"); is("@{[$t->roots]}", "a", "roots"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g3, pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, start => "a"); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->dfs; is("@pre", "a b c", "pre"); is("@post", "c b a", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 3, "unseen half"); is($t->seen, 3, "seen half"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c", "seen half"); is("@{[$t->roots]}", "a", "roots"); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g0, pre_edge => sub { push @pre, $_[0], $_[1] }, post_edge => sub { push @post, $_[0], $_[1] }, next_alphabetic => 1); $t->dfs; is("@pre", "a b b c b d a e e f", "pre"); is("@post", "b c b d a b e f a e", "post"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "a", "roots"); } my $gb = Graph->new; $gb->add_cycle(qw(a b c)); $gb->add_path(qw(a c)); $gb->add_path(qw(a d b)); my @gb; my $tb = Graph::Traversal::DFS-> new($gb, next_alphabetic => 1, pre_edge => sub { push @gb, "pre_edge @_[0,1]" }, post_edge => sub { push @gb, "post_edge @_[0,1]" }, non_tree_edge => sub { push @gb, "non_tree_edge @_[0,1]" }, back_edge => sub { push @gb, "back_edge @_[0,1]" }, down_edge => sub { push @gb, "down_edge @_[0,1]" }, cross_edge => sub { push @gb, "cross_edge @_[0,1]" } ); $tb->dfs; is($gb[ 0], "pre_edge a b", "pre_edge"); is($gb[ 1], "pre_edge b c", "pre_edge"); is($gb[ 2], "post_edge b c", "post_edge"); is($gb[ 3], "non_tree_edge c a", "non_tree_edge"); is($gb[ 4], "back_edge c a", "back_edge"); is($gb[ 5], "post_edge a b", "post_edge"); is($gb[ 6], "pre_edge a d", "pre_edge"); is($gb[ 7], "post_edge a d", "post_edge"); is($gb[ 8], "non_tree_edge d b", "non_tree_edge"); is($gb[ 9], "cross_edge d b", "cross_edge"); is($gb[10], "non_tree_edge a c", "non_tree_edge"); is($gb[11], "down_edge a c", "down_edge"); is( @gb, 12 ); is( $tb->tree, "a-b,a-d,b-c", "tree" ); is( $tb->preorder_by_vertex('a'), 0, "preorder of a" ); is( $tb->preorder_by_vertex('b'), 1, "preorder of b" ); is( $tb->preorder_by_vertex('c'), 2, "preorder of c" ); is( $tb->preorder_by_vertex('d'), 3, "preorder of d" ); is( $tb->vertex_by_preorder(0), 'a', "preorder of a" ); is( $tb->vertex_by_preorder(1), 'b', "preorder of b" ); is( $tb->vertex_by_preorder(2), 'c', "preorder of c" ); is( $tb->vertex_by_preorder(3), 'd', "preorder of d" ); is( $tb->postorder_by_vertex('a'), 3, "postorder of a" ); is( $tb->postorder_by_vertex('b'), 1, "postorder of b" ); is( $tb->postorder_by_vertex('c'), 0, "postorder of c" ); is( $tb->postorder_by_vertex('d'), 2, "postorder of d" ); is( $tb->vertex_by_postorder(3), 'a', "postorder of a" ); is( $tb->vertex_by_postorder(1), 'b', "postorder of b" ); is( $tb->vertex_by_postorder(0), 'c', "postorder of c" ); is( $tb->vertex_by_postorder(2), 'd', "postorder of d" ); my %pre = $tb->preorder_vertices(); is( $pre{'a'}, 0, "preorder of a" ); is( $pre{'b'}, 1, "preorder of b" ); is( $pre{'c'}, 2, "preorder of c" ); is( $pre{'d'}, 3, "preorder of d" ); is( keys %pre, 4 ); my %post = $tb->postorder_vertices(); is( $post{'a'}, 3, "postorder of a" ); is( $post{'b'}, 1, "postorder of b" ); is( $post{'c'}, 0, "postorder of c" ); is( $post{'d'}, 2, "postorder of d" ); is( keys %post, 4 ); my $gc = Graph->new(multiedged => 1); $gc->add_path(qw(a b)); $gc->add_path(qw(a b)); my @gc; my $tc = Graph::Traversal::DFS-> new($gc, next_alphabetic => 1, pre_edge => sub { push @gc, "pre_edge @_[0,1]" }, post_edge => sub { push @gc, "post_edge @_[0,1]" }, non_tree_edge => sub { push @gc, "non_tree_edge @_[0,1]" }, back_edge => sub { push @gc, "back_edge @_[0,1]" }, down_edge => sub { push @gc, "down_edge @_[0,1]" }, cross_edge => sub { push @gc, "cross_edge @_[0,1]" }, seen_edge => sub { push @gc, "seen_edge @_[0,1]" } ); $tc->dfs; is( $gc[0], "pre_edge a b", "pre_edge" ); is( $gc[1], "post_edge a b", "post_edge" ); is( $gc[2], "seen_edge a b", "seen_edge" ); is( @gc, 3 ); my $gd = Graph->new; $gd->add_edge(qw(0 1)); $gd->add_edge(qw(0 10)); $gd->add_edge(qw(0 9)); my @gd0; my $td0 = Graph::Traversal::DFS->new($gd, next_numeric => 1, pre => sub { push @gd0, $_[0] }); $td0->dfs; is( "@gd0", "0 1 9 10", "next_numeric" ); my @gd1; my $td1 = Graph::Traversal::DFS->new($gd, next_alphabetic => 1, pre => sub { push @gd1, $_[0] }); $td1->dfs; is( "@gd1", "0 1 10 9", "next_alphabetic" ); eval { Graph::Traversal::DFS->new('next_alphabetic') }; like($@, qr/Graph::Traversal: first argument is not a Graph/, "sane args"); eval { Graph::Traversal::DFS->new($gd, next_alphazetic => 1) }; like($@, qr/Unknown option: 'next_alphazetic'/, "zetic"); ok(!$td1->has_state('zot'), "has_state"); is($td1->get_state('zot'), undef, "get_state"); ok($td1->set_state('zot', 42), "set_state"); ok($td1->has_state('zot'), "has_state"); is($td1->get_state('zot'), 42, "get_state"); ok($td1->delete_state('zot'), "delete_state"); ok(!$td1->has_state('zot'), "has_state"); is($td1->get_state('zot'), undef, "get_state"); { # http://rt.cpan.org/NoAuth/Bug.html?id=4420 use Graph::Directed; my $g = new Graph::Directed; ok($g = $g->add_edge('a','b'), "rt.cpan.org 4420"); ok($g = $g->add_edge('b','a')); is $g, "a-b,b-a"; my @toposort; eval { @toposort = $g->toposort }; like($@, qr/Graph::topological_sort: expected directed acyclic graph, got cyclic/); # http://rt.cpan.org/NoAuth/Bug.html?id=5168 @toposort = $g->toposort(empty_if_cyclic => 1); is(@toposort, 0, "rt.cpan.org 5168"); # http://rt.cpan.org/NoAuth/Bug.html?id=5167 ok( $g->has_a_cycle, "rt.cpan.org 5167" ); my $h = Graph->new; $h->add_edge(qw(a b)); $h->add_edge(qw(a c)); ok(!$h->has_a_cycle); } { my @pre; my @post; my $t = Graph::Traversal::DFS->new($g0, first_root => 'a', pre => sub { push @pre, $_[0] }, post => sub { push @post, $_[0] }, next_successor => sub { shift; (reverse sort keys %{ $_[0] })[0] }); my @t0 = $t->preorder; my @t1 = $t->postorder; my @t2 = $t->postorder; simple($g1, @t0); simple($g1, @t1); simple($g1, @t2); is("@pre", "a e f b d c", "pre"); is("@post", "f e d c b a", "post"); is("@t0", "@pre", "t0"); is("@t1", "@post", "t1"); is("@t2", "@post", "t2"); is($t->unseen, 0, "unseen none"); is($t->seen, 6, "seen all"); is($t->seeing, 0, "seeing none"); is("@{[sort $t->seen]}", "a b c d e f", "seen all"); is("@{[$t->roots]}", "a", "roots"); ok( $t->is_root('a') ); ok(!$t->is_root('b') ); ok(!$t->is_root('c') ); } done_testing; Graph-0.9735/t/85_subgraph.t0000644000175000017500000000320714741033474015400 0ustar osboxesosboxesuse strict; use warnings; use Graph; use Graph::Directed; use Graph::Undirected; use Test::More; my $g0 = Graph::Directed->new; my @E = ([qw(a b)], [qw(a c)], [qw(b d)], [qw(b e)], [qw(c f)], [qw(c g)]); $g0->add_edges(@E); is $g0->subgraph([qw(a b c)], [qw(d e f)]), "b-d,b-e,c-f,a"; is $g0->subgraph([qw(a b c)]), "a-b,a-c"; is $g0->subgraph(['a'],['e']), "a,e"; is($g0->subgraph_by_radius('a', 0)->stringify, "a"); is($g0->subgraph_by_radius('a', 1)->stringify, "a-b,a-c"); is($g0->subgraph_by_radius('a', 2)->stringify, "a-b,a-c,b-d,b-e,c-f,c-g"); is($g0->subgraph_by_radius('a', 3)->stringify, "a-b,a-c,b-d,b-e,c-f,c-g"); is($g0->subgraph_by_radius('b', 0)->stringify, "b"); is($g0->subgraph_by_radius('b', 1)->stringify, "b-d,b-e"); is($g0->subgraph_by_radius('b', 2)->stringify, "b-d,b-e"); is($g0->subgraph_by_radius('b', 3)->stringify, "b-d,b-e"); is($g0->subgraph_by_radius('a', 'b', 1)->stringify, "a-b,a-c,b-d,b-e"); my $g1 = Graph::Undirected->new; $g1->add_edges(@E); is $g1->subgraph([qw(a b c)], [qw(d e f)]), "b=d,b=e,c=f,a"; is $g1->subgraph([qw(a b c)]), "a=b,a=c"; is $g1->subgraph(['a'],['e']), "a,e"; is($g1->subgraph_by_radius('a', 0)->stringify, "a"); is($g1->subgraph_by_radius('a', 1)->stringify, "a=b,a=c"); is($g1->subgraph_by_radius('a', 2)->stringify, "a=b,a=c,b=d,b=e,c=f,c=g"); is($g1->subgraph_by_radius('a', 3)->stringify, "a=b,a=c,b=d,b=e,c=f,c=g"); is($g1->subgraph_by_radius('b', 0)->stringify, "b"); is($g1->subgraph_by_radius('b', 1)->stringify, "a=b,b=d,b=e"); is($g1->subgraph_by_radius('b', 2)->stringify, "a=b,a=c,b=d,b=e"); is($g1->subgraph_by_radius('b', 3)->stringify, "a=b,a=c,b=d,b=e,c=f,c=g"); done_testing(); Graph-0.9735/t/u_dl_uf.t0000644000175000017500000000072013743337306014665 0ustar osboxesosboxes# rt.cpan.org #31608: Graph::Undirected, unionfind and connected_component use strict; use Test::More tests => 2; use Graph::Undirected; sub fill_graph { my $graph = shift; $graph->add_edge('A', 'B'); $graph->add_vertex('A'); } my $graph1 = Graph::Undirected->new('unionfind' => 1); fill_graph($graph1); is($graph1->connected_components(), 1); my $graph2 = Graph::Undirected->new('unionfind' => 0); fill_graph($graph2); is($graph2->connected_components(), 1); Graph-0.9735/t/49_get_edge_count.t0000644000175000017500000000135713774262024016544 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 12; use Graph; my $g = Graph->new; is( $g->get_edge_count("a", "b"), 0 ); is( $g->get_edge_count("b", "c"), 0 ); $g->add_edge("a", "b"); is( $g->get_edge_count("a", "b"), 1 ); is( $g->get_edge_count("b", "c"), 0 ); $g->add_edge("a", "b"); is( $g->get_edge_count("a", "b"), 1 ); is( $g->get_edge_count("b", "c"), 0 ); my $h = $g->new(countedged => 1); $h->add_edge("a", "b"); $h->add_edge("a", "b"); is( $h->get_edge_count("a", "b"), 2 ); is( $h->get_edge_count("b", "c"), 0 ); $h->delete_edge("a", "b"); is( $h->get_edge_count("a", "b"), 1 ); is( $h->get_edge_count("b", "c"), 0 ); $h->delete_edge("a", "b"); is( $h->get_edge_count("a", "b"), 0 ); is( $h->get_edge_count("b", "c"), 0 ); Graph-0.9735/t/07_gen.t0000644000175000017500000000114613774262024014330 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 15; use Graph; my $g = Graph->new; gen_changed( $g->[1] ); # [1] is the generational index ok( $g->add_vertex('a') ); gen_changed( $g->[1] ); ok( $g->add_vertex('b') ); gen_changed( $g->[1] ); ok( $g->add_edge('a', 'b') ); gen_changed( $g->[1] ); ok( $g->delete_edge('a', 'b') ); gen_changed( $g->[1] ); ok( $g->add_edge('a', 'c') ); gen_changed( $g->[1] ); ok( $g->delete_vertex('a') ); gen_changed( $g->[1] ); ok( $g->delete_vertex('b') ); gen_changed( $g->[1] ); # delete vertex my $gen_old; sub gen_changed { isnt $_[0], $gen_old; $gen_old = $_[0],; } Graph-0.9735/t/u_at1.t0000644000175000017500000033340513774262024014270 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 2; use Graph::Undirected; my $g1 = Graph::Undirected->new; my $g2 = Graph::Undirected->new; while () { my ($u, $v) = split(' '); $g1->add_edge($u, $v); $g2->add_edge($u, $v); } my @d = ( 'snOQIeEGLZ', 'WdgaReG7Rl', 'InL4L3gP8B', 'VsRbfKNIdY', 'Me3R0HTEnD', ); $g1->set_edge_weight("HCOpVDLF8S", "kzMilPvDzg", 30); my @w1 = $g1->SP_Dijkstra("snOQIeEGLZ", "Me3R0HTEnD"); is_deeply(\@w1, \@d); my @v2 = $g2->SP_Dijkstra("snOQIeEGLZ", "Me3R0HTEnD"); $g2->set_edge_weight("HCOpVDLF8S", "kzMilPvDzg", 30); my @w2 = $g2->SP_Dijkstra("snOQIeEGLZ", "Me3R0HTEnD"); is_deeply(\@w2, \@d); __END__ A2bNee4LMs BT3jNizL5n A2bNee4LMs FTI9Shcypv A2bNee4LMs U5BoY6tv9F A2bNee4LMs aWxJ1IXGt6 A2bNee4LMs oL6jG5HkJV A2bNee4LMs os8C1a0ovg A2bNee4LMs yEO7ylHAt9 A5SqXiE5RK GF8t7wHqux A5SqXiE5RK KSnU0hIItN A5SqXiE5RK P0vwCQDW6m A5SqXiE5RK PcHGX7DMYO A5SqXiE5RK QOftVKwfC2 A5SqXiE5RK fbLDZ9q3iH A5SqXiE5RK pLC117Kew6 A89PdGxmGI BFvFM3RWTa A89PdGxmGI KXxZfSdUoy A89PdGxmGI PVpkYC8Vhk A89PdGxmGI StlgRkbU88 A89PdGxmGI T4vS2lsKkv A89PdGxmGI TdbAAnj7IR A89PdGxmGI VBeFh2joiE A89PdGxmGI YfM5zyFoHF A89PdGxmGI h9090xBRnh A89PdGxmGI iPZX9ImURe A89PdGxmGI kzMilPvDzg A89PdGxmGI m8xQ57130w A89PdGxmGI mNJUSI0TTF A89PdGxmGI mXTIjIMUdT A89PdGxmGI pbsWMSurgG A89PdGxmGI qX3l34l9vg A89PdGxmGI r6JciJbeWh A89PdGxmGI rtVFNPW6Yb A89PdGxmGI zlUF7FRkOG AEKgzNBgLp CL7InjadLY AEKgzNBgLp DLCemPdUa2 AEKgzNBgLp KSnU0hIItN AEKgzNBgLp MZJO1jWrVr AEKgzNBgLp OkDkTePqup AEKgzNBgLp QBaeM75Es5 AEKgzNBgLp STDKogvNpb AEKgzNBgLp cih0AH9lyP AEKgzNBgLp dyZoZYnzvi AEKgzNBgLp gamVSiwqjr AEKgzNBgLp glnwWdW8Ri AEKgzNBgLp hzYEQ4SmM7 AEKgzNBgLp i00FUjOQxx AEKgzNBgLp i8YnD3sMqM AEKgzNBgLp no0A0GfJ5P AEKgzNBgLp nyAZwNUZqR AEKgzNBgLp odIh2ap0Te AEKgzNBgLp pvyeavNWzt AEKgzNBgLp t6uwSRC0Ba ALlRYyU1EZ B6n7gHBJv4 ALlRYyU1EZ DYahKpmvmE ALlRYyU1EZ HXULiM5bHi ALlRYyU1EZ LKccFxxrwL ALlRYyU1EZ Lgr2XHIkc5 ALlRYyU1EZ Q5gCM7jKEq ALlRYyU1EZ StISRXUIrc ALlRYyU1EZ b5rNzRWDtz ALlRYyU1EZ nUgZ8bkR9P ALlRYyU1EZ r5asrtuIGP ALlRYyU1EZ yucEolJLVv ALlRYyU1EZ zAr9xJc4ZO AMKikpuHf3 K1zX8GgiVK AMKikpuHf3 LEM9xvSXe9 AMKikpuHf3 PXEuxrBBsZ AMKikpuHf3 PuOpNGGc13 AMKikpuHf3 djvYAPRnNk AMKikpuHf3 ghGPODU4bp AMKikpuHf3 leernZlUWX AMKikpuHf3 mcNdnUnvwB AMKikpuHf3 mlPzs61nAP ARitgxyo6H D3qsVMNHYJ ARitgxyo6H InL4L3gP8B ARitgxyo6H KKcQ5rtdSl ARitgxyo6H NeQc8lW94W ARitgxyo6H QaLVi9lXV2 ARitgxyo6H WdgaReG7Rl ARitgxyo6H Z3KCxzBZRg ARitgxyo6H a9aotIHLQP ARitgxyo6H bzoLSMmOvF ARitgxyo6H gkNBwl4RLu ARitgxyo6H lAuS2zWAPn ARitgxyo6H lMqHeAkrTb ARitgxyo6H lgquwFNy5U ARitgxyo6H md4xbbZN0U ARitgxyo6H muHUYCloPL ARitgxyo6H nx1sfwm4YL ARitgxyo6H s4OrzaFL7g ARitgxyo6H sr7yJzlRcM ARitgxyo6H uj1BqLePr7 ARitgxyo6H vV6kWC0oCn ARitgxyo6H wEQA1br8ei ARitgxyo6H wo1cGQsxG3 AzbN73IFHF CLNKQwna5q AzbN73IFHF EIreytW5dM AzbN73IFHF JMYmTSxlx3 AzbN73IFHF OX7TFhvKwV AzbN73IFHF Sx0qNbtZBo AzbN73IFHF VEaWbEgVEo AzbN73IFHF adyvbKMw2p AzbN73IFHF mjkXMevSaO B1c3NQQs27 CFrJfSankS B1c3NQQs27 HFS3yWJkRY B1c3NQQs27 Js1CmTFK4I B1c3NQQs27 Kj20pAyTmb B1c3NQQs27 LP5PM4fEPd B1c3NQQs27 MC1i7asSAF B1c3NQQs27 NfN7fKRqmr B1c3NQQs27 OZ6YsCupAP B1c3NQQs27 ajxV9jwt8z B1c3NQQs27 avg1XJ13Zr B1c3NQQs27 eZZxdI9HOD B1c3NQQs27 j6TSAtVjgt B1c3NQQs27 ku3RU4kQrJ B1c3NQQs27 poreM4rgZB B1c3NQQs27 zCqBU4Ta1g B6n7gHBJv4 ALlRYyU1EZ B6n7gHBJv4 HXULiM5bHi B6n7gHBJv4 LKccFxxrwL B6n7gHBJv4 Lgr2XHIkc5 B6n7gHBJv4 Q5gCM7jKEq B6n7gHBJv4 StISRXUIrc B6n7gHBJv4 b5rNzRWDtz B6n7gHBJv4 nUgZ8bkR9P B6n7gHBJv4 r5asrtuIGP B6n7gHBJv4 yucEolJLVv B6n7gHBJv4 zAr9xJc4ZO BFvFM3RWTa A89PdGxmGI BFvFM3RWTa KXxZfSdUoy BFvFM3RWTa PVpkYC8Vhk BFvFM3RWTa StlgRkbU88 BFvFM3RWTa T4vS2lsKkv BFvFM3RWTa TdbAAnj7IR BFvFM3RWTa VBeFh2joiE BFvFM3RWTa YfM5zyFoHF BFvFM3RWTa h9090xBRnh BFvFM3RWTa iPZX9ImURe BFvFM3RWTa kzMilPvDzg BFvFM3RWTa m8xQ57130w BFvFM3RWTa mNJUSI0TTF BFvFM3RWTa mXTIjIMUdT BFvFM3RWTa pbsWMSurgG BFvFM3RWTa qX3l34l9vg BFvFM3RWTa r6JciJbeWh BFvFM3RWTa rtVFNPW6Yb BFvFM3RWTa zlUF7FRkOG BT3jNizL5n A2bNee4LMs BT3jNizL5n FTI9Shcypv BT3jNizL5n U5BoY6tv9F BT3jNizL5n aWxJ1IXGt6 BT3jNizL5n os8C1a0ovg BT3jNizL5n yEO7ylHAt9 BT3jNizL5n yIzUYqLM7u BVrFmcSl1r FGDogF5C8O BVrFmcSl1r Q7equjW9kG BVrFmcSl1r pDp32ye8dZ BVrFmcSl1r qT3Il3N0Cg BVrFmcSl1r sjbt7X7rss BhOjoVk6Bh Mz8WmYBx4m BhOjoVk6Bh NVY0HmybOG BhOjoVk6Bh Skf9tX0Pdg BhOjoVk6Bh UQIjstoJqc BhOjoVk6Bh V2pif4soQX BhOjoVk6Bh Vb5f0yievL BhOjoVk6Bh YrXI44k7UB BhOjoVk6Bh aFHqNwdyYo BhOjoVk6Bh dMtRfk10B7 BhOjoVk6Bh fShIUDg8WY BhOjoVk6Bh rwiRDya8LR BlJduir4ZF H3yBGQCdoS BlJduir4ZF KZ8Oz3BGyo BlJduir4ZF SCcNT9adIP BlJduir4ZF cdpdBAzJDF BlJduir4ZF oq9uAm8HQ7 BnPab1zOQN ZUpFZT5Wky BnPab1zOQN gXEbqBTGW0 BnPab1zOQN mDrSgV98vb BnPab1zOQN pLC117Kew6 BnPab1zOQN uVqqmtdAGv BnPab1zOQN zfmwbQwtk8 By3ifr7skR PWnohCTMMJ By3ifr7skR QcttPAc76l By3ifr7skR ShPTT2Mg66 By3ifr7skR jQk2isP8og By3ifr7skR wA2rJOcWwU By3ifr7skR x9YzQ9iiwh CFrJfSankS B1c3NQQs27 CFrJfSankS HFS3yWJkRY CFrJfSankS Js1CmTFK4I CFrJfSankS Kj20pAyTmb CFrJfSankS LP5PM4fEPd CFrJfSankS MC1i7asSAF CFrJfSankS NfN7fKRqmr CFrJfSankS OZ6YsCupAP CFrJfSankS ajxV9jwt8z CFrJfSankS avg1XJ13Zr CFrJfSankS eZZxdI9HOD CFrJfSankS j6TSAtVjgt CFrJfSankS ku3RU4kQrJ CFrJfSankS poreM4rgZB CFrJfSankS zCqBU4Ta1g CL7InjadLY AEKgzNBgLp CL7InjadLY DLCemPdUa2 CL7InjadLY KSnU0hIItN CL7InjadLY MZJO1jWrVr CL7InjadLY OkDkTePqup CL7InjadLY QBaeM75Es5 CL7InjadLY STDKogvNpb CL7InjadLY cih0AH9lyP CL7InjadLY dyZoZYnzvi CL7InjadLY gamVSiwqjr CL7InjadLY glnwWdW8Ri CL7InjadLY hzYEQ4SmM7 CL7InjadLY i00FUjOQxx CL7InjadLY i8YnD3sMqM CL7InjadLY jQk2isP8og CL7InjadLY no0A0GfJ5P CL7InjadLY nyAZwNUZqR CL7InjadLY odIh2ap0Te CL7InjadLY pvyeavNWzt CL7InjadLY t6uwSRC0Ba CLNKQwna5q AzbN73IFHF CLNKQwna5q PG3HRvcYYE CLNKQwna5q QUVT7xx0GS CLNKQwna5q hfU4rnQr8C CLNKQwna5q lNnfKNbVWj CLNKQwna5q uLBvOgMNp4 CLNKQwna5q uMtUrHVik9 COW3QMJ85P KL6BLASGwb COW3QMJ85P NrvLRILdiu COW3QMJ85P TrOrQJuYRl COW3QMJ85P auXTz19oRf COW3QMJ85P bAKSD1xNgl COW3QMJ85P bk1hOFMxpV COW3QMJ85P j2XRj0AcEF COW3QMJ85P l8efBc8fhD COW3QMJ85P lzZNPnevUM COW3QMJ85P oL6jG5HkJV COW3QMJ85P q8XaJaEHpz COW3QMJ85P rUpyqd6ACn COW3QMJ85P uf6s6hkKhz COW3QMJ85P xt1v639ujY CT94OMR0el h5PPMDGh9k CT94OMR0el prO1QYA3Lk CT94OMR0el snOQIeEGLZ CT94OMR0el t1QFOCIFgP CT94OMR0el xfxVXqIVeu CYewf1rn1r N646MBv0Yq CYewf1rn1r aKuuvQuyeK CYewf1rn1r cs2tTcKbyD CYewf1rn1r dc2HNm7Ug1 CYewf1rn1r iDifCMJxDo CYewf1rn1r kfMZhQCnm7 CYewf1rn1r nMfKZ3OhnK CYewf1rn1r qz1mi8Zoju CYewf1rn1r y4069LW6DD Co6YOiNWwd HehmZdzxsm Co6YOiNWwd Ldv8qIpfM5 Co6YOiNWwd OtGP45bxdz Co6YOiNWwd PrE5fj14uq Co6YOiNWwd R4VgVqyGtv Co6YOiNWwd TaN1WYgQQo Co6YOiNWwd XnXGS8DYOY Co6YOiNWwd eLsXjGROaL Co6YOiNWwd hleAM6S2d8 CspreEn61Q DtnQIxAWuf CspreEn61Q FxS3sBJA2Y CspreEn61Q WtW8bhpTmG CspreEn61Q Yk5M1fW87z CspreEn61Q hbAACpTBfH CspreEn61Q oq9uAm8HQ7 CspreEn61Q orvvutiOIS CspreEn61Q wl1GfSteFH CspreEn61Q yCMMplAGar CspreEn61Q yjLH0H4leh D3qsVMNHYJ ARitgxyo6H D3qsVMNHYJ InL4L3gP8B D3qsVMNHYJ KKcQ5rtdSl D3qsVMNHYJ NeQc8lW94W D3qsVMNHYJ QaLVi9lXV2 D3qsVMNHYJ WdgaReG7Rl D3qsVMNHYJ Z3KCxzBZRg D3qsVMNHYJ a9aotIHLQP D3qsVMNHYJ bzoLSMmOvF D3qsVMNHYJ gkNBwl4RLu D3qsVMNHYJ lAuS2zWAPn D3qsVMNHYJ lMqHeAkrTb D3qsVMNHYJ lgquwFNy5U D3qsVMNHYJ md4xbbZN0U D3qsVMNHYJ muHUYCloPL D3qsVMNHYJ nx1sfwm4YL D3qsVMNHYJ s4OrzaFL7g D3qsVMNHYJ sr7yJzlRcM D3qsVMNHYJ uj1BqLePr7 D3qsVMNHYJ vV6kWC0oCn D3qsVMNHYJ wEQA1br8ei D3qsVMNHYJ wo1cGQsxG3 DLCemPdUa2 AEKgzNBgLp DLCemPdUa2 CL7InjadLY DLCemPdUa2 KSnU0hIItN DLCemPdUa2 MZJO1jWrVr DLCemPdUa2 OkDkTePqup DLCemPdUa2 QBaeM75Es5 DLCemPdUa2 STDKogvNpb DLCemPdUa2 cih0AH9lyP DLCemPdUa2 dyZoZYnzvi DLCemPdUa2 gamVSiwqjr DLCemPdUa2 glnwWdW8Ri DLCemPdUa2 hzYEQ4SmM7 DLCemPdUa2 i00FUjOQxx DLCemPdUa2 i8YnD3sMqM DLCemPdUa2 no0A0GfJ5P DLCemPdUa2 nyAZwNUZqR DLCemPdUa2 odIh2ap0Te DLCemPdUa2 pvyeavNWzt DLCemPdUa2 t6uwSRC0Ba DSEAKInMON FgtPqv5u6X DSEAKInMON QOftVKwfC2 DSEAKInMON TdhrBfhKA4 DSEAKInMON VY0YXayFZy DSEAKInMON WwQrPLos2H DSEAKInMON dg9My3b63y DSEAKInMON dozVsfXm9d DSEAKInMON ileXvC9I0m DSEAKInMON mIkGeVJqMH DSEAKInMON qI1Uu600tE DSEAKInMON qUu6MtWEti DSEAKInMON vJq2Lg7NqP DSEAKInMON vfjOHm8unN DSEAKInMON xCwj0abqdX DYahKpmvmE ALlRYyU1EZ DYahKpmvmE qxMdGfMei3 DYahKpmvmE wBowW79rK5 DYahKpmvmE zOsDATVDjk DtnQIxAWuf CspreEn61Q DtnQIxAWuf FxS3sBJA2Y DtnQIxAWuf WtW8bhpTmG DtnQIxAWuf Yk5M1fW87z DtnQIxAWuf hbAACpTBfH DtnQIxAWuf oq9uAm8HQ7 DtnQIxAWuf orvvutiOIS DtnQIxAWuf plyHE0u0du DtnQIxAWuf wl1GfSteFH DtnQIxAWuf yCMMplAGar DtnQIxAWuf yjLH0H4leh DxIAA9ZJ3T Ga31WHOJqV DxIAA9ZJ3T JQk2aqVtGs DxIAA9ZJ3T KifgRzbVyt DxIAA9ZJ3T Q9eF9yQN0t DxIAA9ZJ3T S8193nrYCv DxIAA9ZJ3T UuPwyCgvCP DxIAA9ZJ3T nYAc8XCqer DxIAA9ZJ3T oQJaxdHG4N DxIAA9ZJ3T olBdH0jfPB DxIAA9ZJ3T wMkRLgAmqT DxIAA9ZJ3T xZtfO8qwEC DxIAA9ZJ3T xgdjqGL33t DxIAA9ZJ3T yPUgavnkvb DxIAA9ZJ3T z3HLDgXLGK DxIAA9ZJ3T z6bXLibpTn EIreytW5dM AzbN73IFHF EIreytW5dM JMYmTSxlx3 EIreytW5dM OX7TFhvKwV EIreytW5dM Sx0qNbtZBo EIreytW5dM VEaWbEgVEo EIreytW5dM adyvbKMw2p EIreytW5dM mjkXMevSaO EQO5gS68QX FTI9Shcypv EQO5gS68QX G9EvjeK9x3 EQO5gS68QX JLDyuLymKI EQO5gS68QX JbOVP67SGC EQO5gS68QX N9fU30JXks EQO5gS68QX YwQXb3Mqus EcC1w0q8IC Me3R0HTEnD EcC1w0q8IC QzPMOgvbGb EcC1w0q8IC VsRbfKNIdY EcC1w0q8IC ZF1pKK1tfE EcC1w0q8IC nMYnHvEYON EcC1w0q8IC shR39TKy6b FGDogF5C8O BVrFmcSl1r FGDogF5C8O Q7equjW9kG FGDogF5C8O pDp32ye8dZ FGDogF5C8O qT3Il3N0Cg FGDogF5C8O sjbt7X7rss FTI9Shcypv A2bNee4LMs FTI9Shcypv BT3jNizL5n FTI9Shcypv EQO5gS68QX FTI9Shcypv U5BoY6tv9F FTI9Shcypv aWxJ1IXGt6 FTI9Shcypv os8C1a0ovg FTI9Shcypv yEO7ylHAt9 FgtPqv5u6X DSEAKInMON FgtPqv5u6X TdhrBfhKA4 FgtPqv5u6X VY0YXayFZy FgtPqv5u6X WwQrPLos2H FgtPqv5u6X dg9My3b63y FgtPqv5u6X dozVsfXm9d FgtPqv5u6X ileXvC9I0m FgtPqv5u6X l6DiTMZ6Y1 FgtPqv5u6X mIkGeVJqMH FgtPqv5u6X qI1Uu600tE FgtPqv5u6X qUu6MtWEti FgtPqv5u6X vJq2Lg7NqP FgtPqv5u6X vfjOHm8unN FgtPqv5u6X xCwj0abqdX FxRrbPlLBc KdK442vKVQ FxRrbPlLBc MuvfKsKOds FxRrbPlLBc PJdPFUXV4p FxRrbPlLBc Z0W7FSu3Lv FxRrbPlLBc a95TSSt8eF FxRrbPlLBc dCUAioVIEU FxRrbPlLBc g51m2Ar66X FxRrbPlLBc hWzPVctQys FxRrbPlLBc r4sCRIIjjE FxRrbPlLBc tfbkFiMXxB FxS3sBJA2Y CspreEn61Q FxS3sBJA2Y DtnQIxAWuf FxS3sBJA2Y WtW8bhpTmG FxS3sBJA2Y Yk5M1fW87z FxS3sBJA2Y hbAACpTBfH FxS3sBJA2Y oq9uAm8HQ7 FxS3sBJA2Y orvvutiOIS FxS3sBJA2Y pDp32ye8dZ FxS3sBJA2Y wl1GfSteFH FxS3sBJA2Y yCMMplAGar FxS3sBJA2Y yjLH0H4leh G6M6biLXNb MsdnkwG8qI G6M6biLXNb O2N8IPM9SD G6M6biLXNb QWqMOgywY3 G6M6biLXNb UECu5dEunb G6M6biLXNb UdctjCRplG G6M6biLXNb e0jC4Bo2N5 G6M6biLXNb jAEMuG4nj6 G6M6biLXNb wf7yGa9VWa G9EvjeK9x3 EQO5gS68QX G9EvjeK9x3 JLDyuLymKI G9EvjeK9x3 JbOVP67SGC G9EvjeK9x3 N9fU30JXks G9EvjeK9x3 YwQXb3Mqus G9EvjeK9x3 xXtkF1hfGE GDC2l4Qter IYKO1wH0kv GDC2l4Qter IfhyOroLcw GDC2l4Qter JGSZw135s1 GDC2l4Qter QWqMOgywY3 GDC2l4Qter SJJP4L8Mgj GDC2l4Qter XcXj23HQ38 GDC2l4Qter bRhTeE5ynI GDC2l4Qter bpymfnpvaz GDC2l4Qter dIppWC7kET GDC2l4Qter nSGQtlVZxb GDC2l4Qter oS2R0cZHY7 GDC2l4Qter qEmmw8pgBY GDC2l4Qter tIXMeS0Ofn GDC2l4Qter uTM8CnyXtM GF8t7wHqux A5SqXiE5RK GF8t7wHqux P0vwCQDW6m GF8t7wHqux PcHGX7DMYO GF8t7wHqux QOftVKwfC2 GF8t7wHqux fbLDZ9q3iH GF8t7wHqux pLC117Kew6 Ga31WHOJqV DxIAA9ZJ3T Ga31WHOJqV JQk2aqVtGs Ga31WHOJqV KifgRzbVyt Ga31WHOJqV Q9eF9yQN0t Ga31WHOJqV S8193nrYCv Ga31WHOJqV UuPwyCgvCP Ga31WHOJqV nYAc8XCqer Ga31WHOJqV oQJaxdHG4N Ga31WHOJqV olBdH0jfPB Ga31WHOJqV wMkRLgAmqT Ga31WHOJqV xZtfO8qwEC Ga31WHOJqV xgdjqGL33t Ga31WHOJqV yPUgavnkvb Ga31WHOJqV z3HLDgXLGK Ga31WHOJqV z6bXLibpTn GgFF2EOc7i IaHaICPcV1 GgFF2EOc7i IfvxXkNlLu GgFF2EOc7i OciZEaDOPd GgFF2EOc7i S1fBp1z8tl GgFF2EOc7i a8OO37gvE2 GgFF2EOc7i ed8GQOp6Lo GgFF2EOc7i epFMnW3Nn8 GgFF2EOc7i fmCwGLjhRu GgFF2EOc7i kpdFumdStg GgFF2EOc7i l6DiTMZ6Y1 GgFF2EOc7i n5IggTR5tj GgFF2EOc7i plonl31IDU GgFF2EOc7i qn2DhjefKe GiFzzqNblC OXNteCuFGm GiFzzqNblC UYMtwhVMNr GiFzzqNblC XnXGS8DYOY GiFzzqNblC YeRS8002jm GiFzzqNblC nrHzxDHavt GiFzzqNblC pXvezpQ7XS GiFzzqNblC qVIxalGY9L GiFzzqNblC yIzUYqLM7u H3yBGQCdoS BlJduir4ZF H3yBGQCdoS KZ8Oz3BGyo H3yBGQCdoS KqpzATMLe1 H3yBGQCdoS SCcNT9adIP H3yBGQCdoS cdpdBAzJDF HCOpVDLF8S QcttPAc76l HCOpVDLF8S Rzz4iaLvJ7 HCOpVDLF8S YMcnYwVdY5 HCOpVDLF8S ZyjOEhlFQH HCOpVDLF8S fjWTJ6hu4c HCOpVDLF8S kzMilPvDzg HCOpVDLF8S pMTjj8TguG HCOpVDLF8S xkzOvzedbB HFS3yWJkRY B1c3NQQs27 HFS3yWJkRY CFrJfSankS HFS3yWJkRY Js1CmTFK4I HFS3yWJkRY Kj20pAyTmb HFS3yWJkRY LP5PM4fEPd HFS3yWJkRY MC1i7asSAF HFS3yWJkRY NfN7fKRqmr HFS3yWJkRY OZ6YsCupAP HFS3yWJkRY ajxV9jwt8z HFS3yWJkRY avg1XJ13Zr HFS3yWJkRY eZZxdI9HOD HFS3yWJkRY j6TSAtVjgt HFS3yWJkRY ku3RU4kQrJ HFS3yWJkRY poreM4rgZB HFS3yWJkRY zCqBU4Ta1g HHYyhAROeQ IoNI0rkmkW HHYyhAROeQ Pzjk79OhbQ HHYyhAROeQ QYlF02nPq8 HHYyhAROeQ RIi7H4zCvN HHYyhAROeQ WhX0FgFcFC HHYyhAROeQ jFTc1eDTky HHYyhAROeQ oOrogjFpK6 HHYyhAROeQ pdsWKEPXcD HHYyhAROeQ rNr0H6bfQ5 HXULiM5bHi ALlRYyU1EZ HXULiM5bHi B6n7gHBJv4 HXULiM5bHi LKccFxxrwL HXULiM5bHi Lgr2XHIkc5 HXULiM5bHi Q5gCM7jKEq HXULiM5bHi StISRXUIrc HXULiM5bHi b5rNzRWDtz HXULiM5bHi nUgZ8bkR9P HXULiM5bHi r5asrtuIGP HXULiM5bHi yucEolJLVv HXULiM5bHi zAr9xJc4ZO HehmZdzxsm Co6YOiNWwd HehmZdzxsm Ldv8qIpfM5 HehmZdzxsm OtGP45bxdz HehmZdzxsm PrE5fj14uq HehmZdzxsm R4VgVqyGtv HehmZdzxsm TaN1WYgQQo HehmZdzxsm XnXGS8DYOY HehmZdzxsm eLsXjGROaL HehmZdzxsm hleAM6S2d8 IYKO1wH0kv GDC2l4Qter IYKO1wH0kv IfhyOroLcw IYKO1wH0kv JGSZw135s1 IYKO1wH0kv QWqMOgywY3 IYKO1wH0kv SJJP4L8Mgj IYKO1wH0kv XcXj23HQ38 IYKO1wH0kv bRhTeE5ynI IYKO1wH0kv bpymfnpvaz IYKO1wH0kv dIppWC7kET IYKO1wH0kv nSGQtlVZxb IYKO1wH0kv oS2R0cZHY7 IYKO1wH0kv qEmmw8pgBY IYKO1wH0kv tIXMeS0Ofn IYKO1wH0kv uTM8CnyXtM IaHaICPcV1 GgFF2EOc7i IaHaICPcV1 IfvxXkNlLu IaHaICPcV1 OciZEaDOPd IaHaICPcV1 S1fBp1z8tl IaHaICPcV1 a8OO37gvE2 IaHaICPcV1 ed8GQOp6Lo IaHaICPcV1 epFMnW3Nn8 IaHaICPcV1 fmCwGLjhRu IaHaICPcV1 kpdFumdStg IaHaICPcV1 l6DiTMZ6Y1 IaHaICPcV1 n5IggTR5tj IaHaICPcV1 plonl31IDU IaHaICPcV1 qn2DhjefKe IfhyOroLcw GDC2l4Qter IfhyOroLcw IYKO1wH0kv IfhyOroLcw JGSZw135s1 IfhyOroLcw QWqMOgywY3 IfhyOroLcw SJJP4L8Mgj IfhyOroLcw XcXj23HQ38 IfhyOroLcw bRhTeE5ynI IfhyOroLcw bpymfnpvaz IfhyOroLcw dIppWC7kET IfhyOroLcw nSGQtlVZxb IfhyOroLcw oS2R0cZHY7 IfhyOroLcw qEmmw8pgBY IfhyOroLcw tIXMeS0Ofn IfhyOroLcw uTM8CnyXtM IfvxXkNlLu GgFF2EOc7i IfvxXkNlLu IaHaICPcV1 IfvxXkNlLu OciZEaDOPd IfvxXkNlLu S1fBp1z8tl IfvxXkNlLu a8OO37gvE2 IfvxXkNlLu ed8GQOp6Lo IfvxXkNlLu epFMnW3Nn8 IfvxXkNlLu fmCwGLjhRu IfvxXkNlLu kpdFumdStg IfvxXkNlLu l6DiTMZ6Y1 IfvxXkNlLu n5IggTR5tj IfvxXkNlLu plonl31IDU IfvxXkNlLu qn2DhjefKe InL4L3gP8B ARitgxyo6H InL4L3gP8B D3qsVMNHYJ InL4L3gP8B KKcQ5rtdSl InL4L3gP8B NeQc8lW94W InL4L3gP8B QaLVi9lXV2 InL4L3gP8B VsRbfKNIdY InL4L3gP8B WdgaReG7Rl InL4L3gP8B Z3KCxzBZRg InL4L3gP8B a9aotIHLQP InL4L3gP8B bzoLSMmOvF InL4L3gP8B gkNBwl4RLu InL4L3gP8B lAuS2zWAPn InL4L3gP8B lMqHeAkrTb InL4L3gP8B lgquwFNy5U InL4L3gP8B md4xbbZN0U InL4L3gP8B muHUYCloPL InL4L3gP8B nx1sfwm4YL InL4L3gP8B s4OrzaFL7g InL4L3gP8B sr7yJzlRcM InL4L3gP8B uj1BqLePr7 InL4L3gP8B vV6kWC0oCn InL4L3gP8B wEQA1br8ei InL4L3gP8B wo1cGQsxG3 IoNI0rkmkW HHYyhAROeQ IoNI0rkmkW Pzjk79OhbQ IoNI0rkmkW QYlF02nPq8 IoNI0rkmkW RIi7H4zCvN IoNI0rkmkW VY0YXayFZy IoNI0rkmkW WhX0FgFcFC IoNI0rkmkW jFTc1eDTky IoNI0rkmkW oOrogjFpK6 IoNI0rkmkW pdsWKEPXcD IoNI0rkmkW rNr0H6bfQ5 IxJ0H1khHP JIAU1JIHpJ IxJ0H1khHP Ohh0A7h9Lb IxJ0H1khHP SwxZE8dapQ IxJ0H1khHP TMEwmzXwHX IxJ0H1khHP Vp94QEZInp IxJ0H1khHP bHjtTXmzMn IxJ0H1khHP dQ9g9Axvcf IxJ0H1khHP fD6g3ZeOQ1 IxJ0H1khHP g3MqlsjtAp IxJ0H1khHP i1SqlxFZ39 IxJ0H1khHP iXgFa9e2aY IxJ0H1khHP itLVbe0zBv IxJ0H1khHP jzYrwL8zIr IxJ0H1khHP kzxPEcveyR IxJ0H1khHP nLYXHof79N IxJ0H1khHP pGo7sdz80r IxJ0H1khHP rOtv3qBWnW IxJ0H1khHP sLJoKUc1Fv IxJ0H1khHP wjZgToLdJm JGSZw135s1 GDC2l4Qter JGSZw135s1 IYKO1wH0kv JGSZw135s1 IfhyOroLcw JGSZw135s1 QWqMOgywY3 JGSZw135s1 SJJP4L8Mgj JGSZw135s1 XcXj23HQ38 JGSZw135s1 bRhTeE5ynI JGSZw135s1 bpymfnpvaz JGSZw135s1 dIppWC7kET JGSZw135s1 mlPzs61nAP JGSZw135s1 nSGQtlVZxb JGSZw135s1 oS2R0cZHY7 JGSZw135s1 qEmmw8pgBY JGSZw135s1 tIXMeS0Ofn JGSZw135s1 uTM8CnyXtM JIAU1JIHpJ IxJ0H1khHP JIAU1JIHpJ Ohh0A7h9Lb JIAU1JIHpJ SwxZE8dapQ JIAU1JIHpJ TMEwmzXwHX JIAU1JIHpJ UYMtwhVMNr JIAU1JIHpJ Vp94QEZInp JIAU1JIHpJ bHjtTXmzMn JIAU1JIHpJ dQ9g9Axvcf JIAU1JIHpJ fD6g3ZeOQ1 JIAU1JIHpJ g3MqlsjtAp JIAU1JIHpJ i1SqlxFZ39 JIAU1JIHpJ iXgFa9e2aY JIAU1JIHpJ itLVbe0zBv JIAU1JIHpJ jzYrwL8zIr JIAU1JIHpJ kzxPEcveyR JIAU1JIHpJ nLYXHof79N JIAU1JIHpJ pGo7sdz80r JIAU1JIHpJ rOtv3qBWnW JIAU1JIHpJ sLJoKUc1Fv JIAU1JIHpJ wjZgToLdJm JLDyuLymKI EQO5gS68QX JLDyuLymKI G9EvjeK9x3 JLDyuLymKI JbOVP67SGC JLDyuLymKI N9fU30JXks JLDyuLymKI PWnohCTMMJ JLDyuLymKI YwQXb3Mqus JMYmTSxlx3 AzbN73IFHF JMYmTSxlx3 EIreytW5dM JMYmTSxlx3 OX7TFhvKwV JMYmTSxlx3 Sx0qNbtZBo JMYmTSxlx3 VEaWbEgVEo JMYmTSxlx3 adyvbKMw2p JMYmTSxlx3 mjkXMevSaO JQk2aqVtGs DxIAA9ZJ3T JQk2aqVtGs Ga31WHOJqV JQk2aqVtGs KifgRzbVyt JQk2aqVtGs Q9eF9yQN0t JQk2aqVtGs S8193nrYCv JQk2aqVtGs UuPwyCgvCP JQk2aqVtGs iBVBTY5BMg JQk2aqVtGs nYAc8XCqer JQk2aqVtGs oQJaxdHG4N JQk2aqVtGs olBdH0jfPB JQk2aqVtGs wMkRLgAmqT JQk2aqVtGs xZtfO8qwEC JQk2aqVtGs xgdjqGL33t JQk2aqVtGs yPUgavnkvb JQk2aqVtGs z3HLDgXLGK JQk2aqVtGs z6bXLibpTn JbOVP67SGC EQO5gS68QX JbOVP67SGC G9EvjeK9x3 JbOVP67SGC JLDyuLymKI JbOVP67SGC N9fU30JXks JbOVP67SGC YwQXb3Mqus JbOVP67SGC dozVsfXm9d Js1CmTFK4I B1c3NQQs27 Js1CmTFK4I CFrJfSankS Js1CmTFK4I HFS3yWJkRY Js1CmTFK4I Kj20pAyTmb Js1CmTFK4I LP5PM4fEPd Js1CmTFK4I MC1i7asSAF Js1CmTFK4I NfN7fKRqmr Js1CmTFK4I OZ6YsCupAP Js1CmTFK4I ajxV9jwt8z Js1CmTFK4I avg1XJ13Zr Js1CmTFK4I eZZxdI9HOD Js1CmTFK4I j6TSAtVjgt Js1CmTFK4I ku3RU4kQrJ Js1CmTFK4I poreM4rgZB Js1CmTFK4I zCqBU4Ta1g JsB8YadXEJ UXV0MBIG9U JsB8YadXEJ rwiRDya8LR K1zX8GgiVK AMKikpuHf3 K1zX8GgiVK LEM9xvSXe9 K1zX8GgiVK PXEuxrBBsZ K1zX8GgiVK PuOpNGGc13 K1zX8GgiVK djvYAPRnNk K1zX8GgiVK ghGPODU4bp K1zX8GgiVK leernZlUWX K1zX8GgiVK mcNdnUnvwB K1zX8GgiVK mlPzs61nAP K7PU5OVuQg KfwNNX0Wv2 K7PU5OVuQg MRKNAnBkqw K7PU5OVuQg N1p0MmEQt2 K7PU5OVuQg PXldCpcCCr K7PU5OVuQg RK222QTUpQ K7PU5OVuQg WhtLWtpJ5C K7PU5OVuQg WpWMwUAphV K7PU5OVuQg lcNbrMOErd K7PU5OVuQg oQJaxdHG4N K7PU5OVuQg seClx9Il7d K7PU5OVuQg u8bzLFKHM3 KKcQ5rtdSl ARitgxyo6H KKcQ5rtdSl D3qsVMNHYJ KKcQ5rtdSl InL4L3gP8B KKcQ5rtdSl NeQc8lW94W KKcQ5rtdSl QaLVi9lXV2 KKcQ5rtdSl WdgaReG7Rl KKcQ5rtdSl Z3KCxzBZRg KKcQ5rtdSl a9aotIHLQP KKcQ5rtdSl bzoLSMmOvF KKcQ5rtdSl gkNBwl4RLu KKcQ5rtdSl lAuS2zWAPn KKcQ5rtdSl lMqHeAkrTb KKcQ5rtdSl lgquwFNy5U KKcQ5rtdSl md4xbbZN0U KKcQ5rtdSl muHUYCloPL KKcQ5rtdSl nx1sfwm4YL KKcQ5rtdSl s4OrzaFL7g KKcQ5rtdSl sr7yJzlRcM KKcQ5rtdSl uj1BqLePr7 KKcQ5rtdSl vV6kWC0oCn KKcQ5rtdSl wEQA1br8ei KKcQ5rtdSl wo1cGQsxG3 KL6BLASGwb COW3QMJ85P KL6BLASGwb NrvLRILdiu KL6BLASGwb TrOrQJuYRl KL6BLASGwb auXTz19oRf KL6BLASGwb bAKSD1xNgl KL6BLASGwb bk1hOFMxpV KL6BLASGwb j2XRj0AcEF KL6BLASGwb l8efBc8fhD KL6BLASGwb lzZNPnevUM KL6BLASGwb oL6jG5HkJV KL6BLASGwb q8XaJaEHpz KL6BLASGwb rUpyqd6ACn KL6BLASGwb uf6s6hkKhz KL6BLASGwb xt1v639ujY KSnU0hIItN A5SqXiE5RK KSnU0hIItN AEKgzNBgLp KSnU0hIItN CL7InjadLY KSnU0hIItN DLCemPdUa2 KSnU0hIItN MZJO1jWrVr KSnU0hIItN OkDkTePqup KSnU0hIItN QBaeM75Es5 KSnU0hIItN STDKogvNpb KSnU0hIItN cih0AH9lyP KSnU0hIItN dyZoZYnzvi KSnU0hIItN gamVSiwqjr KSnU0hIItN glnwWdW8Ri KSnU0hIItN hzYEQ4SmM7 KSnU0hIItN i00FUjOQxx KSnU0hIItN i8YnD3sMqM KSnU0hIItN no0A0GfJ5P KSnU0hIItN nyAZwNUZqR KSnU0hIItN odIh2ap0Te KSnU0hIItN pvyeavNWzt KSnU0hIItN t6uwSRC0Ba KXxZfSdUoy A89PdGxmGI KXxZfSdUoy BFvFM3RWTa KXxZfSdUoy PVpkYC8Vhk KXxZfSdUoy StlgRkbU88 KXxZfSdUoy T4vS2lsKkv KXxZfSdUoy TdbAAnj7IR KXxZfSdUoy VBeFh2joiE KXxZfSdUoy YfM5zyFoHF KXxZfSdUoy h9090xBRnh KXxZfSdUoy iPZX9ImURe KXxZfSdUoy kzMilPvDzg KXxZfSdUoy m8xQ57130w KXxZfSdUoy mNJUSI0TTF KXxZfSdUoy mXTIjIMUdT KXxZfSdUoy pbsWMSurgG KXxZfSdUoy qX3l34l9vg KXxZfSdUoy r6JciJbeWh KXxZfSdUoy rtVFNPW6Yb KXxZfSdUoy zlUF7FRkOG KZ8Oz3BGyo BlJduir4ZF KZ8Oz3BGyo H3yBGQCdoS KZ8Oz3BGyo SCcNT9adIP KZ8Oz3BGyo cdpdBAzJDF KdK442vKVQ FxRrbPlLBc KdK442vKVQ MuvfKsKOds KdK442vKVQ PJdPFUXV4p KdK442vKVQ Z0W7FSu3Lv KdK442vKVQ a95TSSt8eF KdK442vKVQ dCUAioVIEU KdK442vKVQ g51m2Ar66X KdK442vKVQ hWzPVctQys KdK442vKVQ tfbkFiMXxB KfwNNX0Wv2 K7PU5OVuQg KfwNNX0Wv2 MRKNAnBkqw KfwNNX0Wv2 N1p0MmEQt2 KfwNNX0Wv2 PXldCpcCCr KfwNNX0Wv2 RK222QTUpQ KfwNNX0Wv2 WhtLWtpJ5C KfwNNX0Wv2 WpWMwUAphV KfwNNX0Wv2 lcNbrMOErd KfwNNX0Wv2 seClx9Il7d KfwNNX0Wv2 u8bzLFKHM3 KifgRzbVyt DxIAA9ZJ3T KifgRzbVyt Ga31WHOJqV KifgRzbVyt JQk2aqVtGs KifgRzbVyt Q9eF9yQN0t KifgRzbVyt S8193nrYCv KifgRzbVyt UuPwyCgvCP KifgRzbVyt nYAc8XCqer KifgRzbVyt oQJaxdHG4N KifgRzbVyt olBdH0jfPB KifgRzbVyt wMkRLgAmqT KifgRzbVyt xZtfO8qwEC KifgRzbVyt xgdjqGL33t KifgRzbVyt yPUgavnkvb KifgRzbVyt z3HLDgXLGK KifgRzbVyt z6bXLibpTn Kj20pAyTmb B1c3NQQs27 Kj20pAyTmb CFrJfSankS Kj20pAyTmb HFS3yWJkRY Kj20pAyTmb Js1CmTFK4I Kj20pAyTmb LP5PM4fEPd Kj20pAyTmb MC1i7asSAF Kj20pAyTmb NfN7fKRqmr Kj20pAyTmb OZ6YsCupAP Kj20pAyTmb ajxV9jwt8z Kj20pAyTmb avg1XJ13Zr Kj20pAyTmb eZZxdI9HOD Kj20pAyTmb j6TSAtVjgt Kj20pAyTmb ku3RU4kQrJ Kj20pAyTmb poreM4rgZB Kj20pAyTmb zCqBU4Ta1g KqpzATMLe1 H3yBGQCdoS KqpzATMLe1 RLDnkOU46i KqpzATMLe1 T4QHaY3mvu KqpzATMLe1 UGeImwK7UL KqpzATMLe1 cqiJYAyZ4N KqpzATMLe1 gjyJWmHrdc KqpzATMLe1 mLQdynHm1q KqpzATMLe1 ro4OmYo2Fl KqpzATMLe1 wq1vjepQ9g KqpzATMLe1 zO7KxMkWwX LEM9xvSXe9 AMKikpuHf3 LEM9xvSXe9 K1zX8GgiVK LEM9xvSXe9 PXEuxrBBsZ LEM9xvSXe9 PuOpNGGc13 LEM9xvSXe9 djvYAPRnNk LEM9xvSXe9 ghGPODU4bp LEM9xvSXe9 leernZlUWX LEM9xvSXe9 mcNdnUnvwB LEM9xvSXe9 mlPzs61nAP LKccFxxrwL ALlRYyU1EZ LKccFxxrwL B6n7gHBJv4 LKccFxxrwL HXULiM5bHi LKccFxxrwL Lgr2XHIkc5 LKccFxxrwL Q5gCM7jKEq LKccFxxrwL StISRXUIrc LKccFxxrwL b5rNzRWDtz LKccFxxrwL nUgZ8bkR9P LKccFxxrwL r5asrtuIGP LKccFxxrwL yucEolJLVv LKccFxxrwL zAr9xJc4ZO LP5PM4fEPd B1c3NQQs27 LP5PM4fEPd CFrJfSankS LP5PM4fEPd HFS3yWJkRY LP5PM4fEPd Js1CmTFK4I LP5PM4fEPd Kj20pAyTmb LP5PM4fEPd MC1i7asSAF LP5PM4fEPd NfN7fKRqmr LP5PM4fEPd OZ6YsCupAP LP5PM4fEPd ajxV9jwt8z LP5PM4fEPd avg1XJ13Zr LP5PM4fEPd eZZxdI9HOD LP5PM4fEPd j6TSAtVjgt LP5PM4fEPd ku3RU4kQrJ LP5PM4fEPd poreM4rgZB LP5PM4fEPd zCqBU4Ta1g LV67y8AOJY NrvLRILdiu LV67y8AOJY SW1Py0cYFg LV67y8AOJY VzzvL9hhsp LV67y8AOJY daSo9v7Y6S LV67y8AOJY f9LXtwOwjX LV67y8AOJY qrCt5OycXI LV67y8AOJY vZvVMwIHhe Ldv8qIpfM5 Co6YOiNWwd Ldv8qIpfM5 HehmZdzxsm Ldv8qIpfM5 OtGP45bxdz Ldv8qIpfM5 PrE5fj14uq Ldv8qIpfM5 R4VgVqyGtv Ldv8qIpfM5 TaN1WYgQQo Ldv8qIpfM5 XnXGS8DYOY Ldv8qIpfM5 eLsXjGROaL Ldv8qIpfM5 hleAM6S2d8 Lgr2XHIkc5 ALlRYyU1EZ Lgr2XHIkc5 B6n7gHBJv4 Lgr2XHIkc5 HXULiM5bHi Lgr2XHIkc5 LKccFxxrwL Lgr2XHIkc5 Q5gCM7jKEq Lgr2XHIkc5 StISRXUIrc Lgr2XHIkc5 b5rNzRWDtz Lgr2XHIkc5 nUgZ8bkR9P Lgr2XHIkc5 r5asrtuIGP Lgr2XHIkc5 yucEolJLVv Lgr2XHIkc5 zAr9xJc4ZO MC1i7asSAF B1c3NQQs27 MC1i7asSAF CFrJfSankS MC1i7asSAF HFS3yWJkRY MC1i7asSAF Js1CmTFK4I MC1i7asSAF Kj20pAyTmb MC1i7asSAF LP5PM4fEPd MC1i7asSAF NfN7fKRqmr MC1i7asSAF OZ6YsCupAP MC1i7asSAF ajxV9jwt8z MC1i7asSAF avg1XJ13Zr MC1i7asSAF eZZxdI9HOD MC1i7asSAF j6TSAtVjgt MC1i7asSAF ku3RU4kQrJ MC1i7asSAF poreM4rgZB MC1i7asSAF zCqBU4Ta1g MRKNAnBkqw K7PU5OVuQg MRKNAnBkqw KfwNNX0Wv2 MRKNAnBkqw N1p0MmEQt2 MRKNAnBkqw PXldCpcCCr MRKNAnBkqw RK222QTUpQ MRKNAnBkqw WhtLWtpJ5C MRKNAnBkqw WpWMwUAphV MRKNAnBkqw lcNbrMOErd MRKNAnBkqw seClx9Il7d MRKNAnBkqw u8bzLFKHM3 MZJO1jWrVr AEKgzNBgLp MZJO1jWrVr CL7InjadLY MZJO1jWrVr DLCemPdUa2 MZJO1jWrVr KSnU0hIItN MZJO1jWrVr OkDkTePqup MZJO1jWrVr QBaeM75Es5 MZJO1jWrVr STDKogvNpb MZJO1jWrVr cih0AH9lyP MZJO1jWrVr dyZoZYnzvi MZJO1jWrVr gamVSiwqjr MZJO1jWrVr glnwWdW8Ri MZJO1jWrVr hzYEQ4SmM7 MZJO1jWrVr i00FUjOQxx MZJO1jWrVr i8YnD3sMqM MZJO1jWrVr no0A0GfJ5P MZJO1jWrVr nyAZwNUZqR MZJO1jWrVr odIh2ap0Te MZJO1jWrVr pvyeavNWzt MZJO1jWrVr t6uwSRC0Ba Me3R0HTEnD EcC1w0q8IC Me3R0HTEnD QzPMOgvbGb Me3R0HTEnD VsRbfKNIdY Me3R0HTEnD ZF1pKK1tfE Me3R0HTEnD nMYnHvEYON Me3R0HTEnD shR39TKy6b MsdnkwG8qI G6M6biLXNb MsdnkwG8qI O2N8IPM9SD MsdnkwG8qI RWIbIcuvf4 MsdnkwG8qI UECu5dEunb MsdnkwG8qI UdctjCRplG MsdnkwG8qI e0jC4Bo2N5 MsdnkwG8qI jAEMuG4nj6 MsdnkwG8qI wf7yGa9VWa MuvfKsKOds FxRrbPlLBc MuvfKsKOds KdK442vKVQ MuvfKsKOds PJdPFUXV4p MuvfKsKOds Z0W7FSu3Lv MuvfKsKOds a95TSSt8eF MuvfKsKOds dCUAioVIEU MuvfKsKOds g51m2Ar66X MuvfKsKOds hWzPVctQys MuvfKsKOds tfbkFiMXxB Mz8WmYBx4m BhOjoVk6Bh Mz8WmYBx4m NVY0HmybOG Mz8WmYBx4m Skf9tX0Pdg Mz8WmYBx4m UQIjstoJqc Mz8WmYBx4m V2pif4soQX Mz8WmYBx4m Vb5f0yievL Mz8WmYBx4m YrXI44k7UB Mz8WmYBx4m aFHqNwdyYo Mz8WmYBx4m dMtRfk10B7 Mz8WmYBx4m fShIUDg8WY N0jKYtNamt RXw2k2V7yr N0jKYtNamt p89CV4NSde N0jKYtNamt q8XaJaEHpz N0jKYtNamt rDtYekG2Rb N0jKYtNamt reE5PfMqhg N0jKYtNamt sPevBnM8lL N1p0MmEQt2 K7PU5OVuQg N1p0MmEQt2 KfwNNX0Wv2 N1p0MmEQt2 MRKNAnBkqw N1p0MmEQt2 PXldCpcCCr N1p0MmEQt2 RK222QTUpQ N1p0MmEQt2 WhtLWtpJ5C N1p0MmEQt2 WpWMwUAphV N1p0MmEQt2 lcNbrMOErd N1p0MmEQt2 seClx9Il7d N1p0MmEQt2 u8bzLFKHM3 N646MBv0Yq CYewf1rn1r N646MBv0Yq aKuuvQuyeK N646MBv0Yq cs2tTcKbyD N646MBv0Yq dc2HNm7Ug1 N646MBv0Yq iDifCMJxDo N646MBv0Yq kfMZhQCnm7 N646MBv0Yq nMfKZ3OhnK N646MBv0Yq qz1mi8Zoju N646MBv0Yq y4069LW6DD N9fU30JXks EQO5gS68QX N9fU30JXks G9EvjeK9x3 N9fU30JXks JLDyuLymKI N9fU30JXks JbOVP67SGC N9fU30JXks YwQXb3Mqus N9fU30JXks aWxJ1IXGt6 NTh5zY76FH RWIbIcuvf4 NTh5zY76FH a6ZJ2OpfvC NTh5zY76FH qzSsPB6mfy NTh5zY76FH uivgUSXTDP NTh5zY76FH wf7yGa9VWa NVY0HmybOG BhOjoVk6Bh NVY0HmybOG Mz8WmYBx4m NVY0HmybOG Skf9tX0Pdg NVY0HmybOG UQIjstoJqc NVY0HmybOG V2pif4soQX NVY0HmybOG Vb5f0yievL NVY0HmybOG YrXI44k7UB NVY0HmybOG aFHqNwdyYo NVY0HmybOG dMtRfk10B7 NVY0HmybOG fShIUDg8WY NeQc8lW94W ARitgxyo6H NeQc8lW94W D3qsVMNHYJ NeQc8lW94W InL4L3gP8B NeQc8lW94W KKcQ5rtdSl NeQc8lW94W QaLVi9lXV2 NeQc8lW94W WdgaReG7Rl NeQc8lW94W Z3KCxzBZRg NeQc8lW94W a9aotIHLQP NeQc8lW94W bzoLSMmOvF NeQc8lW94W gkNBwl4RLu NeQc8lW94W lAuS2zWAPn NeQc8lW94W lMqHeAkrTb NeQc8lW94W lgquwFNy5U NeQc8lW94W md4xbbZN0U NeQc8lW94W muHUYCloPL NeQc8lW94W nx1sfwm4YL NeQc8lW94W s4OrzaFL7g NeQc8lW94W sr7yJzlRcM NeQc8lW94W uj1BqLePr7 NeQc8lW94W vV6kWC0oCn NeQc8lW94W wEQA1br8ei NeQc8lW94W wo1cGQsxG3 NfN7fKRqmr B1c3NQQs27 NfN7fKRqmr CFrJfSankS NfN7fKRqmr HFS3yWJkRY NfN7fKRqmr Js1CmTFK4I NfN7fKRqmr Kj20pAyTmb NfN7fKRqmr LP5PM4fEPd NfN7fKRqmr MC1i7asSAF NfN7fKRqmr OZ6YsCupAP NfN7fKRqmr ajxV9jwt8z NfN7fKRqmr avg1XJ13Zr NfN7fKRqmr eZZxdI9HOD NfN7fKRqmr j6TSAtVjgt NfN7fKRqmr ku3RU4kQrJ NfN7fKRqmr poreM4rgZB NfN7fKRqmr zCqBU4Ta1g NrvLRILdiu COW3QMJ85P NrvLRILdiu KL6BLASGwb NrvLRILdiu LV67y8AOJY NrvLRILdiu TrOrQJuYRl NrvLRILdiu auXTz19oRf NrvLRILdiu bAKSD1xNgl NrvLRILdiu bk1hOFMxpV NrvLRILdiu j2XRj0AcEF NrvLRILdiu l8efBc8fhD NrvLRILdiu lzZNPnevUM NrvLRILdiu oL6jG5HkJV NrvLRILdiu q8XaJaEHpz NrvLRILdiu rUpyqd6ACn NrvLRILdiu uf6s6hkKhz NrvLRILdiu xt1v639ujY O2N8IPM9SD G6M6biLXNb O2N8IPM9SD MsdnkwG8qI O2N8IPM9SD UECu5dEunb O2N8IPM9SD UdctjCRplG O2N8IPM9SD e0jC4Bo2N5 O2N8IPM9SD jAEMuG4nj6 O2N8IPM9SD wf7yGa9VWa OX7TFhvKwV AzbN73IFHF OX7TFhvKwV EIreytW5dM OX7TFhvKwV JMYmTSxlx3 OX7TFhvKwV Sx0qNbtZBo OX7TFhvKwV VEaWbEgVEo OX7TFhvKwV adyvbKMw2p OX7TFhvKwV mjkXMevSaO OXNteCuFGm GiFzzqNblC OXNteCuFGm UYMtwhVMNr OXNteCuFGm UZern2cwbM OXNteCuFGm YeRS8002jm OXNteCuFGm nrHzxDHavt OXNteCuFGm pXvezpQ7XS OXNteCuFGm qVIxalGY9L OXNteCuFGm yIzUYqLM7u OZ6YsCupAP B1c3NQQs27 OZ6YsCupAP CFrJfSankS OZ6YsCupAP HFS3yWJkRY OZ6YsCupAP Js1CmTFK4I OZ6YsCupAP Kj20pAyTmb OZ6YsCupAP LP5PM4fEPd OZ6YsCupAP MC1i7asSAF OZ6YsCupAP NfN7fKRqmr OZ6YsCupAP ajxV9jwt8z OZ6YsCupAP avg1XJ13Zr OZ6YsCupAP eZZxdI9HOD OZ6YsCupAP j6TSAtVjgt OZ6YsCupAP ku3RU4kQrJ OZ6YsCupAP poreM4rgZB OZ6YsCupAP zCqBU4Ta1g OciZEaDOPd GgFF2EOc7i OciZEaDOPd IaHaICPcV1 OciZEaDOPd IfvxXkNlLu OciZEaDOPd S1fBp1z8tl OciZEaDOPd a8OO37gvE2 OciZEaDOPd ed8GQOp6Lo OciZEaDOPd epFMnW3Nn8 OciZEaDOPd fmCwGLjhRu OciZEaDOPd kpdFumdStg OciZEaDOPd l6DiTMZ6Y1 OciZEaDOPd n5IggTR5tj OciZEaDOPd plonl31IDU OciZEaDOPd qn2DhjefKe Ohh0A7h9Lb IxJ0H1khHP Ohh0A7h9Lb JIAU1JIHpJ Ohh0A7h9Lb SwxZE8dapQ Ohh0A7h9Lb TMEwmzXwHX Ohh0A7h9Lb Vp94QEZInp Ohh0A7h9Lb bHjtTXmzMn Ohh0A7h9Lb dQ9g9Axvcf Ohh0A7h9Lb fD6g3ZeOQ1 Ohh0A7h9Lb g3MqlsjtAp Ohh0A7h9Lb i1SqlxFZ39 Ohh0A7h9Lb iXgFa9e2aY Ohh0A7h9Lb itLVbe0zBv Ohh0A7h9Lb jzYrwL8zIr Ohh0A7h9Lb kzxPEcveyR Ohh0A7h9Lb nLYXHof79N Ohh0A7h9Lb pGo7sdz80r Ohh0A7h9Lb rOtv3qBWnW Ohh0A7h9Lb sLJoKUc1Fv Ohh0A7h9Lb wjZgToLdJm OkDkTePqup AEKgzNBgLp OkDkTePqup CL7InjadLY OkDkTePqup DLCemPdUa2 OkDkTePqup KSnU0hIItN OkDkTePqup MZJO1jWrVr OkDkTePqup QBaeM75Es5 OkDkTePqup STDKogvNpb OkDkTePqup cih0AH9lyP OkDkTePqup dyZoZYnzvi OkDkTePqup gamVSiwqjr OkDkTePqup glnwWdW8Ri OkDkTePqup hzYEQ4SmM7 OkDkTePqup i00FUjOQxx OkDkTePqup i8YnD3sMqM OkDkTePqup no0A0GfJ5P OkDkTePqup nyAZwNUZqR OkDkTePqup odIh2ap0Te OkDkTePqup pvyeavNWzt OkDkTePqup t6uwSRC0Ba OtGP45bxdz Co6YOiNWwd OtGP45bxdz HehmZdzxsm OtGP45bxdz Ldv8qIpfM5 OtGP45bxdz PrE5fj14uq OtGP45bxdz R4VgVqyGtv OtGP45bxdz TaN1WYgQQo OtGP45bxdz XnXGS8DYOY OtGP45bxdz eLsXjGROaL OtGP45bxdz hleAM6S2d8 P0vwCQDW6m A5SqXiE5RK P0vwCQDW6m GF8t7wHqux P0vwCQDW6m PcHGX7DMYO P0vwCQDW6m QOftVKwfC2 P0vwCQDW6m fbLDZ9q3iH P0vwCQDW6m pLC117Kew6 PG3HRvcYYE CLNKQwna5q PG3HRvcYYE QUVT7xx0GS PG3HRvcYYE hfU4rnQr8C PG3HRvcYYE lNnfKNbVWj PG3HRvcYYE uLBvOgMNp4 PG3HRvcYYE uMtUrHVik9 PJdPFUXV4p FxRrbPlLBc PJdPFUXV4p KdK442vKVQ PJdPFUXV4p MuvfKsKOds PJdPFUXV4p Z0W7FSu3Lv PJdPFUXV4p a95TSSt8eF PJdPFUXV4p dCUAioVIEU PJdPFUXV4p g51m2Ar66X PJdPFUXV4p hWzPVctQys PJdPFUXV4p tfbkFiMXxB PVpkYC8Vhk A89PdGxmGI PVpkYC8Vhk BFvFM3RWTa PVpkYC8Vhk KXxZfSdUoy PVpkYC8Vhk StlgRkbU88 PVpkYC8Vhk T4vS2lsKkv PVpkYC8Vhk TdbAAnj7IR PVpkYC8Vhk VBeFh2joiE PVpkYC8Vhk YfM5zyFoHF PVpkYC8Vhk h9090xBRnh PVpkYC8Vhk iPZX9ImURe PVpkYC8Vhk kzMilPvDzg PVpkYC8Vhk m8xQ57130w PVpkYC8Vhk mNJUSI0TTF PVpkYC8Vhk mXTIjIMUdT PVpkYC8Vhk pbsWMSurgG PVpkYC8Vhk qX3l34l9vg PVpkYC8Vhk r6JciJbeWh PVpkYC8Vhk rtVFNPW6Yb PVpkYC8Vhk zlUF7FRkOG PWnohCTMMJ By3ifr7skR PWnohCTMMJ JLDyuLymKI PWnohCTMMJ ShPTT2Mg66 PWnohCTMMJ jQk2isP8og PWnohCTMMJ wA2rJOcWwU PWnohCTMMJ x9YzQ9iiwh PXEuxrBBsZ AMKikpuHf3 PXEuxrBBsZ K1zX8GgiVK PXEuxrBBsZ LEM9xvSXe9 PXEuxrBBsZ PuOpNGGc13 PXEuxrBBsZ djvYAPRnNk PXEuxrBBsZ ghGPODU4bp PXEuxrBBsZ leernZlUWX PXEuxrBBsZ mcNdnUnvwB PXEuxrBBsZ mlPzs61nAP PXEuxrBBsZ nMfKZ3OhnK PXldCpcCCr K7PU5OVuQg PXldCpcCCr KfwNNX0Wv2 PXldCpcCCr MRKNAnBkqw PXldCpcCCr N1p0MmEQt2 PXldCpcCCr RK222QTUpQ PXldCpcCCr WhtLWtpJ5C PXldCpcCCr WpWMwUAphV PXldCpcCCr g3MqlsjtAp PXldCpcCCr lcNbrMOErd PXldCpcCCr seClx9Il7d PXldCpcCCr u8bzLFKHM3 PbOg86I31Y UOO1WKebx8 PbOg86I31Y YwQXb3Mqus PbOg86I31Y jLRLqiMVgB PbOg86I31Y jreJYcvwJ4 PbOg86I31Y mlRbIMj46E PbOg86I31Y nf7dTNtSdc PbOg86I31Y p09TiO84PT PbOg86I31Y r4sCRIIjjE PbOg86I31Y tGYPXVsQvI PbOg86I31Y trzVFzFzfU PbOg86I31Y vAjTAP480q PbOg86I31Y yRlT8Jv7q3 PcHGX7DMYO A5SqXiE5RK PcHGX7DMYO GF8t7wHqux PcHGX7DMYO P0vwCQDW6m PcHGX7DMYO QOftVKwfC2 PcHGX7DMYO fbLDZ9q3iH PcHGX7DMYO pLC117Kew6 PrE5fj14uq Co6YOiNWwd PrE5fj14uq HehmZdzxsm PrE5fj14uq Ldv8qIpfM5 PrE5fj14uq OtGP45bxdz PrE5fj14uq R4VgVqyGtv PrE5fj14uq TaN1WYgQQo PrE5fj14uq XnXGS8DYOY PrE5fj14uq eLsXjGROaL PrE5fj14uq hleAM6S2d8 PuOpNGGc13 AMKikpuHf3 PuOpNGGc13 K1zX8GgiVK PuOpNGGc13 LEM9xvSXe9 PuOpNGGc13 PXEuxrBBsZ PuOpNGGc13 djvYAPRnNk PuOpNGGc13 ghGPODU4bp PuOpNGGc13 leernZlUWX PuOpNGGc13 mcNdnUnvwB PuOpNGGc13 mlPzs61nAP Pzjk79OhbQ HHYyhAROeQ Pzjk79OhbQ IoNI0rkmkW Pzjk79OhbQ QYlF02nPq8 Pzjk79OhbQ RIi7H4zCvN Pzjk79OhbQ WhX0FgFcFC Pzjk79OhbQ jFTc1eDTky Pzjk79OhbQ oOrogjFpK6 Pzjk79OhbQ pdsWKEPXcD Pzjk79OhbQ rNr0H6bfQ5 Q5gCM7jKEq ALlRYyU1EZ Q5gCM7jKEq B6n7gHBJv4 Q5gCM7jKEq HXULiM5bHi Q5gCM7jKEq LKccFxxrwL Q5gCM7jKEq Lgr2XHIkc5 Q5gCM7jKEq StISRXUIrc Q5gCM7jKEq b5rNzRWDtz Q5gCM7jKEq nUgZ8bkR9P Q5gCM7jKEq qxMdGfMei3 Q5gCM7jKEq r5asrtuIGP Q5gCM7jKEq yucEolJLVv Q5gCM7jKEq zAr9xJc4ZO Q7equjW9kG BVrFmcSl1r Q7equjW9kG FGDogF5C8O Q7equjW9kG pDp32ye8dZ Q7equjW9kG qT3Il3N0Cg Q7equjW9kG sjbt7X7rss Q8HrJbjuvO QvBrDnbW5p Q8HrJbjuvO SUQX13ah2K Q8HrJbjuvO UZern2cwbM Q8HrJbjuvO Uge5ENcjA2 Q8HrJbjuvO bHtjkkHFmg Q8HrJbjuvO eJkQkNe3fI Q8HrJbjuvO f8QTHIXoZL Q8HrJbjuvO g3iapDoSzG Q8HrJbjuvO iBVBTY5BMg Q8HrJbjuvO qQXEOrDWg5 Q8HrJbjuvO yjDhgXRv3q Q9eF9yQN0t DxIAA9ZJ3T Q9eF9yQN0t Ga31WHOJqV Q9eF9yQN0t JQk2aqVtGs Q9eF9yQN0t KifgRzbVyt Q9eF9yQN0t S8193nrYCv Q9eF9yQN0t UuPwyCgvCP Q9eF9yQN0t nYAc8XCqer Q9eF9yQN0t oQJaxdHG4N Q9eF9yQN0t olBdH0jfPB Q9eF9yQN0t wMkRLgAmqT Q9eF9yQN0t xZtfO8qwEC Q9eF9yQN0t xgdjqGL33t Q9eF9yQN0t yPUgavnkvb Q9eF9yQN0t z3HLDgXLGK Q9eF9yQN0t z6bXLibpTn QBaeM75Es5 AEKgzNBgLp QBaeM75Es5 CL7InjadLY QBaeM75Es5 DLCemPdUa2 QBaeM75Es5 KSnU0hIItN QBaeM75Es5 MZJO1jWrVr QBaeM75Es5 OkDkTePqup QBaeM75Es5 STDKogvNpb QBaeM75Es5 cih0AH9lyP QBaeM75Es5 dyZoZYnzvi QBaeM75Es5 gamVSiwqjr QBaeM75Es5 glnwWdW8Ri QBaeM75Es5 hzYEQ4SmM7 QBaeM75Es5 i00FUjOQxx QBaeM75Es5 i8YnD3sMqM QBaeM75Es5 no0A0GfJ5P QBaeM75Es5 nyAZwNUZqR QBaeM75Es5 odIh2ap0Te QBaeM75Es5 pvyeavNWzt QBaeM75Es5 t6uwSRC0Ba QOftVKwfC2 A5SqXiE5RK QOftVKwfC2 DSEAKInMON QOftVKwfC2 GF8t7wHqux QOftVKwfC2 P0vwCQDW6m QOftVKwfC2 PcHGX7DMYO QOftVKwfC2 fbLDZ9q3iH QOftVKwfC2 pLC117Kew6 QUVT7xx0GS CLNKQwna5q QUVT7xx0GS PG3HRvcYYE QUVT7xx0GS hfU4rnQr8C QUVT7xx0GS lNnfKNbVWj QUVT7xx0GS uLBvOgMNp4 QUVT7xx0GS uMtUrHVik9 QWqMOgywY3 G6M6biLXNb QWqMOgywY3 GDC2l4Qter QWqMOgywY3 IYKO1wH0kv QWqMOgywY3 IfhyOroLcw QWqMOgywY3 JGSZw135s1 QWqMOgywY3 SJJP4L8Mgj QWqMOgywY3 XcXj23HQ38 QWqMOgywY3 bRhTeE5ynI QWqMOgywY3 bpymfnpvaz QWqMOgywY3 dIppWC7kET QWqMOgywY3 nSGQtlVZxb QWqMOgywY3 oS2R0cZHY7 QWqMOgywY3 qEmmw8pgBY QWqMOgywY3 tIXMeS0Ofn QWqMOgywY3 uTM8CnyXtM QYlF02nPq8 HHYyhAROeQ QYlF02nPq8 IoNI0rkmkW QYlF02nPq8 Pzjk79OhbQ QYlF02nPq8 RIi7H4zCvN QYlF02nPq8 WhX0FgFcFC QYlF02nPq8 jFTc1eDTky QYlF02nPq8 oOrogjFpK6 QYlF02nPq8 pdsWKEPXcD QYlF02nPq8 rNr0H6bfQ5 QaLVi9lXV2 ARitgxyo6H QaLVi9lXV2 D3qsVMNHYJ QaLVi9lXV2 InL4L3gP8B QaLVi9lXV2 KKcQ5rtdSl QaLVi9lXV2 NeQc8lW94W QaLVi9lXV2 WdgaReG7Rl QaLVi9lXV2 Z3KCxzBZRg QaLVi9lXV2 a9aotIHLQP QaLVi9lXV2 bzoLSMmOvF QaLVi9lXV2 gkNBwl4RLu QaLVi9lXV2 lAuS2zWAPn QaLVi9lXV2 lMqHeAkrTb QaLVi9lXV2 lgquwFNy5U QaLVi9lXV2 md4xbbZN0U QaLVi9lXV2 muHUYCloPL QaLVi9lXV2 nx1sfwm4YL QaLVi9lXV2 s4OrzaFL7g QaLVi9lXV2 sr7yJzlRcM QaLVi9lXV2 uj1BqLePr7 QaLVi9lXV2 vV6kWC0oCn QaLVi9lXV2 wEQA1br8ei QaLVi9lXV2 wo1cGQsxG3 QcttPAc76l By3ifr7skR QcttPAc76l HCOpVDLF8S QcttPAc76l Rzz4iaLvJ7 QcttPAc76l YMcnYwVdY5 QcttPAc76l ZyjOEhlFQH QcttPAc76l fjWTJ6hu4c QcttPAc76l pMTjj8TguG QcttPAc76l xkzOvzedbB QvBrDnbW5p Q8HrJbjuvO QvBrDnbW5p SUQX13ah2K QvBrDnbW5p UZern2cwbM QvBrDnbW5p Uge5ENcjA2 QvBrDnbW5p bHtjkkHFmg QvBrDnbW5p eJkQkNe3fI QvBrDnbW5p f8QTHIXoZL QvBrDnbW5p g3iapDoSzG QvBrDnbW5p iBVBTY5BMg QvBrDnbW5p qQXEOrDWg5 QvBrDnbW5p yjDhgXRv3q QzPMOgvbGb EcC1w0q8IC QzPMOgvbGb Me3R0HTEnD QzPMOgvbGb VsRbfKNIdY QzPMOgvbGb ZF1pKK1tfE QzPMOgvbGb nMYnHvEYON QzPMOgvbGb shR39TKy6b R4VgVqyGtv Co6YOiNWwd R4VgVqyGtv HehmZdzxsm R4VgVqyGtv Ldv8qIpfM5 R4VgVqyGtv OtGP45bxdz R4VgVqyGtv PrE5fj14uq R4VgVqyGtv TaN1WYgQQo R4VgVqyGtv XnXGS8DYOY R4VgVqyGtv eLsXjGROaL R4VgVqyGtv hleAM6S2d8 R4VgVqyGtv seClx9Il7d RIi7H4zCvN HHYyhAROeQ RIi7H4zCvN IoNI0rkmkW RIi7H4zCvN Pzjk79OhbQ RIi7H4zCvN QYlF02nPq8 RIi7H4zCvN WhX0FgFcFC RIi7H4zCvN jFTc1eDTky RIi7H4zCvN oOrogjFpK6 RIi7H4zCvN pdsWKEPXcD RIi7H4zCvN pvyeavNWzt RIi7H4zCvN rNr0H6bfQ5 RK222QTUpQ K7PU5OVuQg RK222QTUpQ KfwNNX0Wv2 RK222QTUpQ MRKNAnBkqw RK222QTUpQ N1p0MmEQt2 RK222QTUpQ PXldCpcCCr RK222QTUpQ WhtLWtpJ5C RK222QTUpQ WpWMwUAphV RK222QTUpQ lcNbrMOErd RK222QTUpQ seClx9Il7d RK222QTUpQ u8bzLFKHM3 RLDnkOU46i KqpzATMLe1 RLDnkOU46i T4QHaY3mvu RLDnkOU46i UGeImwK7UL RLDnkOU46i cqiJYAyZ4N RLDnkOU46i gjyJWmHrdc RLDnkOU46i mLQdynHm1q RLDnkOU46i ro4OmYo2Fl RLDnkOU46i wq1vjepQ9g RLDnkOU46i zO7KxMkWwX RLDnkOU46i zhtbMk1QlU RWIbIcuvf4 MsdnkwG8qI RWIbIcuvf4 NTh5zY76FH RWIbIcuvf4 a6ZJ2OpfvC RWIbIcuvf4 qzSsPB6mfy RWIbIcuvf4 uivgUSXTDP RXw2k2V7yr N0jKYtNamt RXw2k2V7yr p89CV4NSde RXw2k2V7yr rDtYekG2Rb RXw2k2V7yr reE5PfMqhg RXw2k2V7yr sPevBnM8lL Rf7Jske2mT x9YzQ9iiwh Rf7Jske2mT xXtkF1hfGE Rzz4iaLvJ7 HCOpVDLF8S Rzz4iaLvJ7 QcttPAc76l Rzz4iaLvJ7 YMcnYwVdY5 Rzz4iaLvJ7 ZyjOEhlFQH Rzz4iaLvJ7 fjWTJ6hu4c Rzz4iaLvJ7 pMTjj8TguG Rzz4iaLvJ7 qT3Il3N0Cg Rzz4iaLvJ7 xkzOvzedbB S1fBp1z8tl GgFF2EOc7i S1fBp1z8tl IaHaICPcV1 S1fBp1z8tl IfvxXkNlLu S1fBp1z8tl OciZEaDOPd S1fBp1z8tl a8OO37gvE2 S1fBp1z8tl ed8GQOp6Lo S1fBp1z8tl epFMnW3Nn8 S1fBp1z8tl fmCwGLjhRu S1fBp1z8tl kpdFumdStg S1fBp1z8tl l6DiTMZ6Y1 S1fBp1z8tl n5IggTR5tj S1fBp1z8tl plonl31IDU S1fBp1z8tl qn2DhjefKe S8193nrYCv DxIAA9ZJ3T S8193nrYCv Ga31WHOJqV S8193nrYCv JQk2aqVtGs S8193nrYCv KifgRzbVyt S8193nrYCv Q9eF9yQN0t S8193nrYCv UuPwyCgvCP S8193nrYCv nYAc8XCqer S8193nrYCv oQJaxdHG4N S8193nrYCv olBdH0jfPB S8193nrYCv wMkRLgAmqT S8193nrYCv xZtfO8qwEC S8193nrYCv xgdjqGL33t S8193nrYCv yPUgavnkvb S8193nrYCv z3HLDgXLGK S8193nrYCv z6bXLibpTn SCcNT9adIP BlJduir4ZF SCcNT9adIP H3yBGQCdoS SCcNT9adIP KZ8Oz3BGyo SCcNT9adIP cdpdBAzJDF SJJP4L8Mgj GDC2l4Qter SJJP4L8Mgj IYKO1wH0kv SJJP4L8Mgj IfhyOroLcw SJJP4L8Mgj JGSZw135s1 SJJP4L8Mgj QWqMOgywY3 SJJP4L8Mgj XcXj23HQ38 SJJP4L8Mgj avg1XJ13Zr SJJP4L8Mgj bRhTeE5ynI SJJP4L8Mgj bpymfnpvaz SJJP4L8Mgj dIppWC7kET SJJP4L8Mgj nSGQtlVZxb SJJP4L8Mgj oS2R0cZHY7 SJJP4L8Mgj qEmmw8pgBY SJJP4L8Mgj tIXMeS0Ofn SJJP4L8Mgj uTM8CnyXtM STDKogvNpb AEKgzNBgLp STDKogvNpb CL7InjadLY STDKogvNpb DLCemPdUa2 STDKogvNpb KSnU0hIItN STDKogvNpb MZJO1jWrVr STDKogvNpb OkDkTePqup STDKogvNpb QBaeM75Es5 STDKogvNpb cih0AH9lyP STDKogvNpb dyZoZYnzvi STDKogvNpb gamVSiwqjr STDKogvNpb glnwWdW8Ri STDKogvNpb hzYEQ4SmM7 STDKogvNpb i00FUjOQxx STDKogvNpb i8YnD3sMqM STDKogvNpb no0A0GfJ5P STDKogvNpb nyAZwNUZqR STDKogvNpb odIh2ap0Te STDKogvNpb pvyeavNWzt STDKogvNpb t6uwSRC0Ba SUQX13ah2K Q8HrJbjuvO SUQX13ah2K QvBrDnbW5p SUQX13ah2K UZern2cwbM SUQX13ah2K Uge5ENcjA2 SUQX13ah2K bHtjkkHFmg SUQX13ah2K eJkQkNe3fI SUQX13ah2K f8QTHIXoZL SUQX13ah2K g3iapDoSzG SUQX13ah2K iBVBTY5BMg SUQX13ah2K qQXEOrDWg5 SUQX13ah2K yjDhgXRv3q SW1Py0cYFg LV67y8AOJY SW1Py0cYFg VzzvL9hhsp SW1Py0cYFg daSo9v7Y6S SW1Py0cYFg f9LXtwOwjX SW1Py0cYFg qrCt5OycXI SW1Py0cYFg vZvVMwIHhe ShPTT2Mg66 By3ifr7skR ShPTT2Mg66 PWnohCTMMJ ShPTT2Mg66 ZyjOEhlFQH ShPTT2Mg66 jQk2isP8og ShPTT2Mg66 wA2rJOcWwU ShPTT2Mg66 x9YzQ9iiwh Skf9tX0Pdg BhOjoVk6Bh Skf9tX0Pdg Mz8WmYBx4m Skf9tX0Pdg NVY0HmybOG Skf9tX0Pdg UQIjstoJqc Skf9tX0Pdg V2pif4soQX Skf9tX0Pdg Vb5f0yievL Skf9tX0Pdg YrXI44k7UB Skf9tX0Pdg aFHqNwdyYo Skf9tX0Pdg dMtRfk10B7 Skf9tX0Pdg fShIUDg8WY StISRXUIrc ALlRYyU1EZ StISRXUIrc B6n7gHBJv4 StISRXUIrc HXULiM5bHi StISRXUIrc LKccFxxrwL StISRXUIrc Lgr2XHIkc5 StISRXUIrc Q5gCM7jKEq StISRXUIrc b5rNzRWDtz StISRXUIrc nUgZ8bkR9P StISRXUIrc r5asrtuIGP StISRXUIrc yucEolJLVv StISRXUIrc zAr9xJc4ZO StlgRkbU88 A89PdGxmGI StlgRkbU88 BFvFM3RWTa StlgRkbU88 KXxZfSdUoy StlgRkbU88 PVpkYC8Vhk StlgRkbU88 T4vS2lsKkv StlgRkbU88 TdbAAnj7IR StlgRkbU88 VBeFh2joiE StlgRkbU88 YfM5zyFoHF StlgRkbU88 h9090xBRnh StlgRkbU88 iPZX9ImURe StlgRkbU88 kzMilPvDzg StlgRkbU88 m8xQ57130w StlgRkbU88 mNJUSI0TTF StlgRkbU88 mXTIjIMUdT StlgRkbU88 pbsWMSurgG StlgRkbU88 qX3l34l9vg StlgRkbU88 r6JciJbeWh StlgRkbU88 rtVFNPW6Yb StlgRkbU88 zlUF7FRkOG SwxZE8dapQ IxJ0H1khHP SwxZE8dapQ JIAU1JIHpJ SwxZE8dapQ Ohh0A7h9Lb SwxZE8dapQ TMEwmzXwHX SwxZE8dapQ Vp94QEZInp SwxZE8dapQ bHjtTXmzMn SwxZE8dapQ dQ9g9Axvcf SwxZE8dapQ fD6g3ZeOQ1 SwxZE8dapQ g3MqlsjtAp SwxZE8dapQ i1SqlxFZ39 SwxZE8dapQ iXgFa9e2aY SwxZE8dapQ itLVbe0zBv SwxZE8dapQ jzYrwL8zIr SwxZE8dapQ kzxPEcveyR SwxZE8dapQ nLYXHof79N SwxZE8dapQ pGo7sdz80r SwxZE8dapQ rOtv3qBWnW SwxZE8dapQ sLJoKUc1Fv SwxZE8dapQ wjZgToLdJm Sx0qNbtZBo AzbN73IFHF Sx0qNbtZBo EIreytW5dM Sx0qNbtZBo JMYmTSxlx3 Sx0qNbtZBo OX7TFhvKwV Sx0qNbtZBo VEaWbEgVEo Sx0qNbtZBo adyvbKMw2p Sx0qNbtZBo mjkXMevSaO T4QHaY3mvu KqpzATMLe1 T4QHaY3mvu RLDnkOU46i T4QHaY3mvu UGeImwK7UL T4QHaY3mvu cqiJYAyZ4N T4QHaY3mvu gjyJWmHrdc T4QHaY3mvu mLQdynHm1q T4QHaY3mvu ro4OmYo2Fl T4QHaY3mvu wq1vjepQ9g T4QHaY3mvu zO7KxMkWwX T4vS2lsKkv A89PdGxmGI T4vS2lsKkv BFvFM3RWTa T4vS2lsKkv KXxZfSdUoy T4vS2lsKkv PVpkYC8Vhk T4vS2lsKkv StlgRkbU88 T4vS2lsKkv TdbAAnj7IR T4vS2lsKkv VBeFh2joiE T4vS2lsKkv YfM5zyFoHF T4vS2lsKkv h9090xBRnh T4vS2lsKkv iPZX9ImURe T4vS2lsKkv kzMilPvDzg T4vS2lsKkv m8xQ57130w T4vS2lsKkv mNJUSI0TTF T4vS2lsKkv mXTIjIMUdT T4vS2lsKkv pbsWMSurgG T4vS2lsKkv qX3l34l9vg T4vS2lsKkv r6JciJbeWh T4vS2lsKkv rtVFNPW6Yb T4vS2lsKkv zlUF7FRkOG TMEwmzXwHX IxJ0H1khHP TMEwmzXwHX JIAU1JIHpJ TMEwmzXwHX Ohh0A7h9Lb TMEwmzXwHX SwxZE8dapQ TMEwmzXwHX Vp94QEZInp TMEwmzXwHX bHjtTXmzMn TMEwmzXwHX dQ9g9Axvcf TMEwmzXwHX fD6g3ZeOQ1 TMEwmzXwHX g3MqlsjtAp TMEwmzXwHX i1SqlxFZ39 TMEwmzXwHX iXgFa9e2aY TMEwmzXwHX itLVbe0zBv TMEwmzXwHX jzYrwL8zIr TMEwmzXwHX kzxPEcveyR TMEwmzXwHX nLYXHof79N TMEwmzXwHX pGo7sdz80r TMEwmzXwHX rOtv3qBWnW TMEwmzXwHX sLJoKUc1Fv TMEwmzXwHX wjZgToLdJm TaN1WYgQQo Co6YOiNWwd TaN1WYgQQo HehmZdzxsm TaN1WYgQQo Ldv8qIpfM5 TaN1WYgQQo OtGP45bxdz TaN1WYgQQo PrE5fj14uq TaN1WYgQQo R4VgVqyGtv TaN1WYgQQo XnXGS8DYOY TaN1WYgQQo eLsXjGROaL TaN1WYgQQo hleAM6S2d8 TdbAAnj7IR A89PdGxmGI TdbAAnj7IR BFvFM3RWTa TdbAAnj7IR KXxZfSdUoy TdbAAnj7IR PVpkYC8Vhk TdbAAnj7IR StlgRkbU88 TdbAAnj7IR T4vS2lsKkv TdbAAnj7IR VBeFh2joiE TdbAAnj7IR YfM5zyFoHF TdbAAnj7IR h9090xBRnh TdbAAnj7IR iPZX9ImURe TdbAAnj7IR kzMilPvDzg TdbAAnj7IR m8xQ57130w TdbAAnj7IR mNJUSI0TTF TdbAAnj7IR mXTIjIMUdT TdbAAnj7IR pbsWMSurgG TdbAAnj7IR qX3l34l9vg TdbAAnj7IR r6JciJbeWh TdbAAnj7IR rtVFNPW6Yb TdbAAnj7IR zlUF7FRkOG TdhrBfhKA4 DSEAKInMON TdhrBfhKA4 FgtPqv5u6X TdhrBfhKA4 VY0YXayFZy TdhrBfhKA4 WwQrPLos2H TdhrBfhKA4 dg9My3b63y TdhrBfhKA4 dozVsfXm9d TdhrBfhKA4 ileXvC9I0m TdhrBfhKA4 mIkGeVJqMH TdhrBfhKA4 qI1Uu600tE TdhrBfhKA4 qUu6MtWEti TdhrBfhKA4 vJq2Lg7NqP TdhrBfhKA4 vfjOHm8unN TdhrBfhKA4 xCwj0abqdX TrOrQJuYRl COW3QMJ85P TrOrQJuYRl KL6BLASGwb TrOrQJuYRl NrvLRILdiu TrOrQJuYRl auXTz19oRf TrOrQJuYRl bAKSD1xNgl TrOrQJuYRl bk1hOFMxpV TrOrQJuYRl j2XRj0AcEF TrOrQJuYRl l8efBc8fhD TrOrQJuYRl lzZNPnevUM TrOrQJuYRl oL6jG5HkJV TrOrQJuYRl q8XaJaEHpz TrOrQJuYRl rUpyqd6ACn TrOrQJuYRl uf6s6hkKhz TrOrQJuYRl xt1v639ujY U5BoY6tv9F A2bNee4LMs U5BoY6tv9F BT3jNizL5n U5BoY6tv9F FTI9Shcypv U5BoY6tv9F WtW8bhpTmG U5BoY6tv9F aWxJ1IXGt6 U5BoY6tv9F os8C1a0ovg U5BoY6tv9F yEO7ylHAt9 UECu5dEunb G6M6biLXNb UECu5dEunb MsdnkwG8qI UECu5dEunb O2N8IPM9SD UECu5dEunb UdctjCRplG UECu5dEunb e0jC4Bo2N5 UECu5dEunb jAEMuG4nj6 UECu5dEunb wf7yGa9VWa UGeImwK7UL KqpzATMLe1 UGeImwK7UL RLDnkOU46i UGeImwK7UL T4QHaY3mvu UGeImwK7UL cqiJYAyZ4N UGeImwK7UL gjyJWmHrdc UGeImwK7UL mLQdynHm1q UGeImwK7UL ro4OmYo2Fl UGeImwK7UL wq1vjepQ9g UGeImwK7UL zO7KxMkWwX UOO1WKebx8 PbOg86I31Y UOO1WKebx8 jLRLqiMVgB UOO1WKebx8 jreJYcvwJ4 UOO1WKebx8 mlRbIMj46E UOO1WKebx8 nf7dTNtSdc UOO1WKebx8 p09TiO84PT UOO1WKebx8 r4sCRIIjjE UOO1WKebx8 tGYPXVsQvI UOO1WKebx8 trzVFzFzfU UOO1WKebx8 vAjTAP480q UOO1WKebx8 yRlT8Jv7q3 UQIjstoJqc BhOjoVk6Bh UQIjstoJqc Mz8WmYBx4m UQIjstoJqc NVY0HmybOG UQIjstoJqc Skf9tX0Pdg UQIjstoJqc V2pif4soQX UQIjstoJqc Vb5f0yievL UQIjstoJqc YrXI44k7UB UQIjstoJqc aFHqNwdyYo UQIjstoJqc dMtRfk10B7 UQIjstoJqc fShIUDg8WY UXV0MBIG9U JsB8YadXEJ UXV0MBIG9U rwiRDya8LR UYMtwhVMNr GiFzzqNblC UYMtwhVMNr JIAU1JIHpJ UYMtwhVMNr OXNteCuFGm UYMtwhVMNr YeRS8002jm UYMtwhVMNr nrHzxDHavt UYMtwhVMNr pXvezpQ7XS UYMtwhVMNr qVIxalGY9L UYMtwhVMNr yIzUYqLM7u UZern2cwbM OXNteCuFGm UZern2cwbM Q8HrJbjuvO UZern2cwbM QvBrDnbW5p UZern2cwbM SUQX13ah2K UZern2cwbM Uge5ENcjA2 UZern2cwbM bHtjkkHFmg UZern2cwbM eJkQkNe3fI UZern2cwbM f8QTHIXoZL UZern2cwbM g3iapDoSzG UZern2cwbM iBVBTY5BMg UZern2cwbM qQXEOrDWg5 UZern2cwbM yjDhgXRv3q UdctjCRplG G6M6biLXNb UdctjCRplG MsdnkwG8qI UdctjCRplG O2N8IPM9SD UdctjCRplG UECu5dEunb UdctjCRplG e0jC4Bo2N5 UdctjCRplG jAEMuG4nj6 UdctjCRplG tIXMeS0Ofn UdctjCRplG wf7yGa9VWa Uge5ENcjA2 Q8HrJbjuvO Uge5ENcjA2 QvBrDnbW5p Uge5ENcjA2 SUQX13ah2K Uge5ENcjA2 UZern2cwbM Uge5ENcjA2 bHtjkkHFmg Uge5ENcjA2 eJkQkNe3fI Uge5ENcjA2 f8QTHIXoZL Uge5ENcjA2 g3iapDoSzG Uge5ENcjA2 iBVBTY5BMg Uge5ENcjA2 qQXEOrDWg5 Uge5ENcjA2 yjDhgXRv3q UuPwyCgvCP DxIAA9ZJ3T UuPwyCgvCP Ga31WHOJqV UuPwyCgvCP JQk2aqVtGs UuPwyCgvCP KifgRzbVyt UuPwyCgvCP Q9eF9yQN0t UuPwyCgvCP S8193nrYCv UuPwyCgvCP nYAc8XCqer UuPwyCgvCP oQJaxdHG4N UuPwyCgvCP olBdH0jfPB UuPwyCgvCP wMkRLgAmqT UuPwyCgvCP xZtfO8qwEC UuPwyCgvCP xgdjqGL33t UuPwyCgvCP yPUgavnkvb UuPwyCgvCP z3HLDgXLGK UuPwyCgvCP z6bXLibpTn V2pif4soQX BhOjoVk6Bh V2pif4soQX Mz8WmYBx4m V2pif4soQX NVY0HmybOG V2pif4soQX Skf9tX0Pdg V2pif4soQX UQIjstoJqc V2pif4soQX Vb5f0yievL V2pif4soQX YrXI44k7UB V2pif4soQX aFHqNwdyYo V2pif4soQX dMtRfk10B7 V2pif4soQX fShIUDg8WY VBeFh2joiE A89PdGxmGI VBeFh2joiE BFvFM3RWTa VBeFh2joiE KXxZfSdUoy VBeFh2joiE PVpkYC8Vhk VBeFh2joiE StlgRkbU88 VBeFh2joiE T4vS2lsKkv VBeFh2joiE TdbAAnj7IR VBeFh2joiE YfM5zyFoHF VBeFh2joiE h9090xBRnh VBeFh2joiE iPZX9ImURe VBeFh2joiE kzMilPvDzg VBeFh2joiE m8xQ57130w VBeFh2joiE mNJUSI0TTF VBeFh2joiE mXTIjIMUdT VBeFh2joiE pbsWMSurgG VBeFh2joiE qX3l34l9vg VBeFh2joiE r6JciJbeWh VBeFh2joiE rtVFNPW6Yb VBeFh2joiE vZvVMwIHhe VBeFh2joiE zlUF7FRkOG VEaWbEgVEo AzbN73IFHF VEaWbEgVEo EIreytW5dM VEaWbEgVEo JMYmTSxlx3 VEaWbEgVEo OX7TFhvKwV VEaWbEgVEo Sx0qNbtZBo VEaWbEgVEo adyvbKMw2p VEaWbEgVEo mjkXMevSaO VY0YXayFZy DSEAKInMON VY0YXayFZy FgtPqv5u6X VY0YXayFZy IoNI0rkmkW VY0YXayFZy TdhrBfhKA4 VY0YXayFZy WwQrPLos2H VY0YXayFZy dg9My3b63y VY0YXayFZy dozVsfXm9d VY0YXayFZy ileXvC9I0m VY0YXayFZy mIkGeVJqMH VY0YXayFZy qI1Uu600tE VY0YXayFZy qUu6MtWEti VY0YXayFZy vJq2Lg7NqP VY0YXayFZy vfjOHm8unN VY0YXayFZy xCwj0abqdX Vb5f0yievL BhOjoVk6Bh Vb5f0yievL Mz8WmYBx4m Vb5f0yievL NVY0HmybOG Vb5f0yievL Skf9tX0Pdg Vb5f0yievL UQIjstoJqc Vb5f0yievL V2pif4soQX Vb5f0yievL YrXI44k7UB Vb5f0yievL aFHqNwdyYo Vb5f0yievL dMtRfk10B7 Vb5f0yievL fShIUDg8WY Vb5f0yievL nf7dTNtSdc Vp94QEZInp IxJ0H1khHP Vp94QEZInp JIAU1JIHpJ Vp94QEZInp Ohh0A7h9Lb Vp94QEZInp SwxZE8dapQ Vp94QEZInp TMEwmzXwHX Vp94QEZInp bHjtTXmzMn Vp94QEZInp dQ9g9Axvcf Vp94QEZInp fD6g3ZeOQ1 Vp94QEZInp g3MqlsjtAp Vp94QEZInp i1SqlxFZ39 Vp94QEZInp iXgFa9e2aY Vp94QEZInp itLVbe0zBv Vp94QEZInp jzYrwL8zIr Vp94QEZInp kzxPEcveyR Vp94QEZInp nLYXHof79N Vp94QEZInp pGo7sdz80r Vp94QEZInp rOtv3qBWnW Vp94QEZInp sLJoKUc1Fv Vp94QEZInp wjZgToLdJm VsRbfKNIdY EcC1w0q8IC VsRbfKNIdY InL4L3gP8B VsRbfKNIdY Me3R0HTEnD VsRbfKNIdY QzPMOgvbGb VsRbfKNIdY ZF1pKK1tfE VsRbfKNIdY nMYnHvEYON VsRbfKNIdY shR39TKy6b VzzvL9hhsp LV67y8AOJY VzzvL9hhsp SW1Py0cYFg VzzvL9hhsp daSo9v7Y6S VzzvL9hhsp f9LXtwOwjX VzzvL9hhsp qrCt5OycXI VzzvL9hhsp vZvVMwIHhe WdgaReG7Rl ARitgxyo6H WdgaReG7Rl D3qsVMNHYJ WdgaReG7Rl InL4L3gP8B WdgaReG7Rl KKcQ5rtdSl WdgaReG7Rl NeQc8lW94W WdgaReG7Rl QaLVi9lXV2 WdgaReG7Rl Z3KCxzBZRg WdgaReG7Rl a9aotIHLQP WdgaReG7Rl bzoLSMmOvF WdgaReG7Rl gkNBwl4RLu WdgaReG7Rl lAuS2zWAPn WdgaReG7Rl lMqHeAkrTb WdgaReG7Rl lgquwFNy5U WdgaReG7Rl md4xbbZN0U WdgaReG7Rl muHUYCloPL WdgaReG7Rl nx1sfwm4YL WdgaReG7Rl s4OrzaFL7g WdgaReG7Rl snOQIeEGLZ WdgaReG7Rl sr7yJzlRcM WdgaReG7Rl uj1BqLePr7 WdgaReG7Rl vV6kWC0oCn WdgaReG7Rl wEQA1br8ei WdgaReG7Rl wo1cGQsxG3 WhX0FgFcFC HHYyhAROeQ WhX0FgFcFC IoNI0rkmkW WhX0FgFcFC Pzjk79OhbQ WhX0FgFcFC QYlF02nPq8 WhX0FgFcFC RIi7H4zCvN WhX0FgFcFC jFTc1eDTky WhX0FgFcFC oOrogjFpK6 WhX0FgFcFC pdsWKEPXcD WhX0FgFcFC rNr0H6bfQ5 WhtLWtpJ5C K7PU5OVuQg WhtLWtpJ5C KfwNNX0Wv2 WhtLWtpJ5C MRKNAnBkqw WhtLWtpJ5C N1p0MmEQt2 WhtLWtpJ5C PXldCpcCCr WhtLWtpJ5C RK222QTUpQ WhtLWtpJ5C WpWMwUAphV WhtLWtpJ5C YMcnYwVdY5 WhtLWtpJ5C lcNbrMOErd WhtLWtpJ5C seClx9Il7d WhtLWtpJ5C u8bzLFKHM3 WpWMwUAphV K7PU5OVuQg WpWMwUAphV KfwNNX0Wv2 WpWMwUAphV MRKNAnBkqw WpWMwUAphV N1p0MmEQt2 WpWMwUAphV PXldCpcCCr WpWMwUAphV RK222QTUpQ WpWMwUAphV WhtLWtpJ5C WpWMwUAphV lcNbrMOErd WpWMwUAphV seClx9Il7d WpWMwUAphV u8bzLFKHM3 WtW8bhpTmG CspreEn61Q WtW8bhpTmG DtnQIxAWuf WtW8bhpTmG FxS3sBJA2Y WtW8bhpTmG U5BoY6tv9F WtW8bhpTmG Yk5M1fW87z WtW8bhpTmG hbAACpTBfH WtW8bhpTmG oq9uAm8HQ7 WtW8bhpTmG orvvutiOIS WtW8bhpTmG wl1GfSteFH WtW8bhpTmG yCMMplAGar WtW8bhpTmG yjLH0H4leh WwQrPLos2H DSEAKInMON WwQrPLos2H FgtPqv5u6X WwQrPLos2H TdhrBfhKA4 WwQrPLos2H VY0YXayFZy WwQrPLos2H dg9My3b63y WwQrPLos2H dozVsfXm9d WwQrPLos2H ileXvC9I0m WwQrPLos2H mIkGeVJqMH WwQrPLos2H qI1Uu600tE WwQrPLos2H qUu6MtWEti WwQrPLos2H vJq2Lg7NqP WwQrPLos2H vfjOHm8unN WwQrPLos2H xCwj0abqdX XcXj23HQ38 GDC2l4Qter XcXj23HQ38 IYKO1wH0kv XcXj23HQ38 IfhyOroLcw XcXj23HQ38 JGSZw135s1 XcXj23HQ38 QWqMOgywY3 XcXj23HQ38 SJJP4L8Mgj XcXj23HQ38 bRhTeE5ynI XcXj23HQ38 bpymfnpvaz XcXj23HQ38 dIppWC7kET XcXj23HQ38 nSGQtlVZxb XcXj23HQ38 oS2R0cZHY7 XcXj23HQ38 qEmmw8pgBY XcXj23HQ38 tIXMeS0Ofn XcXj23HQ38 uTM8CnyXtM XnXGS8DYOY Co6YOiNWwd XnXGS8DYOY GiFzzqNblC XnXGS8DYOY HehmZdzxsm XnXGS8DYOY Ldv8qIpfM5 XnXGS8DYOY OtGP45bxdz XnXGS8DYOY PrE5fj14uq XnXGS8DYOY R4VgVqyGtv XnXGS8DYOY TaN1WYgQQo XnXGS8DYOY eLsXjGROaL XnXGS8DYOY hleAM6S2d8 YMcnYwVdY5 HCOpVDLF8S YMcnYwVdY5 QcttPAc76l YMcnYwVdY5 Rzz4iaLvJ7 YMcnYwVdY5 WhtLWtpJ5C YMcnYwVdY5 ZyjOEhlFQH YMcnYwVdY5 fjWTJ6hu4c YMcnYwVdY5 pMTjj8TguG YMcnYwVdY5 xkzOvzedbB YeRS8002jm GiFzzqNblC YeRS8002jm OXNteCuFGm YeRS8002jm UYMtwhVMNr YeRS8002jm nrHzxDHavt YeRS8002jm pXvezpQ7XS YeRS8002jm qVIxalGY9L YeRS8002jm yIzUYqLM7u YfM5zyFoHF A89PdGxmGI YfM5zyFoHF BFvFM3RWTa YfM5zyFoHF KXxZfSdUoy YfM5zyFoHF PVpkYC8Vhk YfM5zyFoHF StlgRkbU88 YfM5zyFoHF T4vS2lsKkv YfM5zyFoHF TdbAAnj7IR YfM5zyFoHF VBeFh2joiE YfM5zyFoHF h9090xBRnh YfM5zyFoHF iPZX9ImURe YfM5zyFoHF kzMilPvDzg YfM5zyFoHF m8xQ57130w YfM5zyFoHF mNJUSI0TTF YfM5zyFoHF mXTIjIMUdT YfM5zyFoHF pbsWMSurgG YfM5zyFoHF qX3l34l9vg YfM5zyFoHF r6JciJbeWh YfM5zyFoHF rtVFNPW6Yb YfM5zyFoHF zlUF7FRkOG Yk5M1fW87z CspreEn61Q Yk5M1fW87z DtnQIxAWuf Yk5M1fW87z FxS3sBJA2Y Yk5M1fW87z WtW8bhpTmG Yk5M1fW87z hbAACpTBfH Yk5M1fW87z oq9uAm8HQ7 Yk5M1fW87z orvvutiOIS Yk5M1fW87z wl1GfSteFH Yk5M1fW87z yCMMplAGar Yk5M1fW87z yjLH0H4leh YrXI44k7UB BhOjoVk6Bh YrXI44k7UB Mz8WmYBx4m YrXI44k7UB NVY0HmybOG YrXI44k7UB Skf9tX0Pdg YrXI44k7UB UQIjstoJqc YrXI44k7UB V2pif4soQX YrXI44k7UB Vb5f0yievL YrXI44k7UB aFHqNwdyYo YrXI44k7UB ajxV9jwt8z YrXI44k7UB dMtRfk10B7 YrXI44k7UB fShIUDg8WY YwQXb3Mqus EQO5gS68QX YwQXb3Mqus G9EvjeK9x3 YwQXb3Mqus JLDyuLymKI YwQXb3Mqus JbOVP67SGC YwQXb3Mqus N9fU30JXks YwQXb3Mqus PbOg86I31Y Z0W7FSu3Lv FxRrbPlLBc Z0W7FSu3Lv KdK442vKVQ Z0W7FSu3Lv MuvfKsKOds Z0W7FSu3Lv PJdPFUXV4p Z0W7FSu3Lv a95TSSt8eF Z0W7FSu3Lv dCUAioVIEU Z0W7FSu3Lv g51m2Ar66X Z0W7FSu3Lv hWzPVctQys Z0W7FSu3Lv tfbkFiMXxB Z3KCxzBZRg ARitgxyo6H Z3KCxzBZRg D3qsVMNHYJ Z3KCxzBZRg InL4L3gP8B Z3KCxzBZRg KKcQ5rtdSl Z3KCxzBZRg NeQc8lW94W Z3KCxzBZRg QaLVi9lXV2 Z3KCxzBZRg WdgaReG7Rl Z3KCxzBZRg a9aotIHLQP Z3KCxzBZRg bzoLSMmOvF Z3KCxzBZRg gkNBwl4RLu Z3KCxzBZRg lAuS2zWAPn Z3KCxzBZRg lMqHeAkrTb Z3KCxzBZRg lgquwFNy5U Z3KCxzBZRg md4xbbZN0U Z3KCxzBZRg muHUYCloPL Z3KCxzBZRg nx1sfwm4YL Z3KCxzBZRg s4OrzaFL7g Z3KCxzBZRg sr7yJzlRcM Z3KCxzBZRg uj1BqLePr7 Z3KCxzBZRg vV6kWC0oCn Z3KCxzBZRg wEQA1br8ei Z3KCxzBZRg wo1cGQsxG3 ZF1pKK1tfE EcC1w0q8IC ZF1pKK1tfE Me3R0HTEnD ZF1pKK1tfE QzPMOgvbGb ZF1pKK1tfE VsRbfKNIdY ZF1pKK1tfE nMYnHvEYON ZF1pKK1tfE shR39TKy6b ZUpFZT5Wky BnPab1zOQN ZUpFZT5Wky gXEbqBTGW0 ZUpFZT5Wky mDrSgV98vb ZUpFZT5Wky uVqqmtdAGv ZUpFZT5Wky zfmwbQwtk8 ZyjOEhlFQH HCOpVDLF8S ZyjOEhlFQH QcttPAc76l ZyjOEhlFQH Rzz4iaLvJ7 ZyjOEhlFQH ShPTT2Mg66 ZyjOEhlFQH YMcnYwVdY5 ZyjOEhlFQH fjWTJ6hu4c ZyjOEhlFQH pMTjj8TguG ZyjOEhlFQH xkzOvzedbB a6ZJ2OpfvC NTh5zY76FH a6ZJ2OpfvC RWIbIcuvf4 a6ZJ2OpfvC qzSsPB6mfy a6ZJ2OpfvC uVqqmtdAGv a6ZJ2OpfvC uivgUSXTDP a8OO37gvE2 GgFF2EOc7i a8OO37gvE2 IaHaICPcV1 a8OO37gvE2 IfvxXkNlLu a8OO37gvE2 OciZEaDOPd a8OO37gvE2 S1fBp1z8tl a8OO37gvE2 ed8GQOp6Lo a8OO37gvE2 epFMnW3Nn8 a8OO37gvE2 fmCwGLjhRu a8OO37gvE2 kpdFumdStg a8OO37gvE2 l6DiTMZ6Y1 a8OO37gvE2 n5IggTR5tj a8OO37gvE2 plonl31IDU a8OO37gvE2 qn2DhjefKe a95TSSt8eF FxRrbPlLBc a95TSSt8eF KdK442vKVQ a95TSSt8eF MuvfKsKOds a95TSSt8eF PJdPFUXV4p a95TSSt8eF Z0W7FSu3Lv a95TSSt8eF dCUAioVIEU a95TSSt8eF g51m2Ar66X a95TSSt8eF hWzPVctQys a95TSSt8eF tfbkFiMXxB a9aotIHLQP ARitgxyo6H a9aotIHLQP D3qsVMNHYJ a9aotIHLQP InL4L3gP8B a9aotIHLQP KKcQ5rtdSl a9aotIHLQP NeQc8lW94W a9aotIHLQP QaLVi9lXV2 a9aotIHLQP WdgaReG7Rl a9aotIHLQP Z3KCxzBZRg a9aotIHLQP bzoLSMmOvF a9aotIHLQP gkNBwl4RLu a9aotIHLQP lAuS2zWAPn a9aotIHLQP lMqHeAkrTb a9aotIHLQP lgquwFNy5U a9aotIHLQP md4xbbZN0U a9aotIHLQP muHUYCloPL a9aotIHLQP nx1sfwm4YL a9aotIHLQP s4OrzaFL7g a9aotIHLQP sr7yJzlRcM a9aotIHLQP uj1BqLePr7 a9aotIHLQP vV6kWC0oCn a9aotIHLQP wEQA1br8ei a9aotIHLQP wo1cGQsxG3 aFHqNwdyYo BhOjoVk6Bh aFHqNwdyYo Mz8WmYBx4m aFHqNwdyYo NVY0HmybOG aFHqNwdyYo Skf9tX0Pdg aFHqNwdyYo UQIjstoJqc aFHqNwdyYo V2pif4soQX aFHqNwdyYo Vb5f0yievL aFHqNwdyYo YrXI44k7UB aFHqNwdyYo dMtRfk10B7 aFHqNwdyYo fShIUDg8WY aKuuvQuyeK CYewf1rn1r aKuuvQuyeK N646MBv0Yq aKuuvQuyeK cs2tTcKbyD aKuuvQuyeK dc2HNm7Ug1 aKuuvQuyeK iDifCMJxDo aKuuvQuyeK kfMZhQCnm7 aKuuvQuyeK nMfKZ3OhnK aKuuvQuyeK qz1mi8Zoju aKuuvQuyeK y4069LW6DD aWxJ1IXGt6 A2bNee4LMs aWxJ1IXGt6 BT3jNizL5n aWxJ1IXGt6 FTI9Shcypv aWxJ1IXGt6 N9fU30JXks aWxJ1IXGt6 U5BoY6tv9F aWxJ1IXGt6 os8C1a0ovg aWxJ1IXGt6 yEO7ylHAt9 adyvbKMw2p AzbN73IFHF adyvbKMw2p EIreytW5dM adyvbKMw2p JMYmTSxlx3 adyvbKMw2p OX7TFhvKwV adyvbKMw2p Sx0qNbtZBo adyvbKMw2p VEaWbEgVEo adyvbKMw2p mjkXMevSaO adyvbKMw2p qrCt5OycXI ajxV9jwt8z B1c3NQQs27 ajxV9jwt8z CFrJfSankS ajxV9jwt8z HFS3yWJkRY ajxV9jwt8z Js1CmTFK4I ajxV9jwt8z Kj20pAyTmb ajxV9jwt8z LP5PM4fEPd ajxV9jwt8z MC1i7asSAF ajxV9jwt8z NfN7fKRqmr ajxV9jwt8z OZ6YsCupAP ajxV9jwt8z YrXI44k7UB ajxV9jwt8z avg1XJ13Zr ajxV9jwt8z eZZxdI9HOD ajxV9jwt8z j6TSAtVjgt ajxV9jwt8z ku3RU4kQrJ ajxV9jwt8z poreM4rgZB ajxV9jwt8z zCqBU4Ta1g auXTz19oRf COW3QMJ85P auXTz19oRf KL6BLASGwb auXTz19oRf NrvLRILdiu auXTz19oRf TrOrQJuYRl auXTz19oRf bAKSD1xNgl auXTz19oRf bk1hOFMxpV auXTz19oRf j2XRj0AcEF auXTz19oRf l8efBc8fhD auXTz19oRf lzZNPnevUM auXTz19oRf oL6jG5HkJV auXTz19oRf prO1QYA3Lk auXTz19oRf q8XaJaEHpz auXTz19oRf rUpyqd6ACn auXTz19oRf uf6s6hkKhz auXTz19oRf xt1v639ujY avg1XJ13Zr B1c3NQQs27 avg1XJ13Zr CFrJfSankS avg1XJ13Zr HFS3yWJkRY avg1XJ13Zr Js1CmTFK4I avg1XJ13Zr Kj20pAyTmb avg1XJ13Zr LP5PM4fEPd avg1XJ13Zr MC1i7asSAF avg1XJ13Zr NfN7fKRqmr avg1XJ13Zr OZ6YsCupAP avg1XJ13Zr SJJP4L8Mgj avg1XJ13Zr ajxV9jwt8z avg1XJ13Zr eZZxdI9HOD avg1XJ13Zr j6TSAtVjgt avg1XJ13Zr ku3RU4kQrJ avg1XJ13Zr poreM4rgZB avg1XJ13Zr zCqBU4Ta1g b5rNzRWDtz ALlRYyU1EZ b5rNzRWDtz B6n7gHBJv4 b5rNzRWDtz HXULiM5bHi b5rNzRWDtz LKccFxxrwL b5rNzRWDtz Lgr2XHIkc5 b5rNzRWDtz Q5gCM7jKEq b5rNzRWDtz StISRXUIrc b5rNzRWDtz nUgZ8bkR9P b5rNzRWDtz r5asrtuIGP b5rNzRWDtz yucEolJLVv b5rNzRWDtz zAr9xJc4ZO bAKSD1xNgl COW3QMJ85P bAKSD1xNgl KL6BLASGwb bAKSD1xNgl NrvLRILdiu bAKSD1xNgl TrOrQJuYRl bAKSD1xNgl auXTz19oRf bAKSD1xNgl bk1hOFMxpV bAKSD1xNgl j2XRj0AcEF bAKSD1xNgl l8efBc8fhD bAKSD1xNgl lzZNPnevUM bAKSD1xNgl oL6jG5HkJV bAKSD1xNgl q8XaJaEHpz bAKSD1xNgl rUpyqd6ACn bAKSD1xNgl uf6s6hkKhz bAKSD1xNgl xt1v639ujY bHjtTXmzMn IxJ0H1khHP bHjtTXmzMn JIAU1JIHpJ bHjtTXmzMn Ohh0A7h9Lb bHjtTXmzMn SwxZE8dapQ bHjtTXmzMn TMEwmzXwHX bHjtTXmzMn Vp94QEZInp bHjtTXmzMn dQ9g9Axvcf bHjtTXmzMn fD6g3ZeOQ1 bHjtTXmzMn g3MqlsjtAp bHjtTXmzMn i1SqlxFZ39 bHjtTXmzMn iXgFa9e2aY bHjtTXmzMn itLVbe0zBv bHjtTXmzMn jzYrwL8zIr bHjtTXmzMn kzxPEcveyR bHjtTXmzMn nLYXHof79N bHjtTXmzMn pGo7sdz80r bHjtTXmzMn rOtv3qBWnW bHjtTXmzMn sLJoKUc1Fv bHjtTXmzMn wjZgToLdJm bHtjkkHFmg Q8HrJbjuvO bHtjkkHFmg QvBrDnbW5p bHtjkkHFmg SUQX13ah2K bHtjkkHFmg UZern2cwbM bHtjkkHFmg Uge5ENcjA2 bHtjkkHFmg eJkQkNe3fI bHtjkkHFmg f8QTHIXoZL bHtjkkHFmg g3iapDoSzG bHtjkkHFmg iBVBTY5BMg bHtjkkHFmg qQXEOrDWg5 bHtjkkHFmg yjDhgXRv3q bRhTeE5ynI GDC2l4Qter bRhTeE5ynI IYKO1wH0kv bRhTeE5ynI IfhyOroLcw bRhTeE5ynI JGSZw135s1 bRhTeE5ynI QWqMOgywY3 bRhTeE5ynI SJJP4L8Mgj bRhTeE5ynI XcXj23HQ38 bRhTeE5ynI bpymfnpvaz bRhTeE5ynI dIppWC7kET bRhTeE5ynI nSGQtlVZxb bRhTeE5ynI oS2R0cZHY7 bRhTeE5ynI qEmmw8pgBY bRhTeE5ynI tIXMeS0Ofn bRhTeE5ynI uTM8CnyXtM bk1hOFMxpV COW3QMJ85P bk1hOFMxpV KL6BLASGwb bk1hOFMxpV NrvLRILdiu bk1hOFMxpV TrOrQJuYRl bk1hOFMxpV auXTz19oRf bk1hOFMxpV bAKSD1xNgl bk1hOFMxpV j2XRj0AcEF bk1hOFMxpV l8efBc8fhD bk1hOFMxpV lzZNPnevUM bk1hOFMxpV oL6jG5HkJV bk1hOFMxpV q8XaJaEHpz bk1hOFMxpV rUpyqd6ACn bk1hOFMxpV uf6s6hkKhz bk1hOFMxpV xt1v639ujY bpymfnpvaz GDC2l4Qter bpymfnpvaz IYKO1wH0kv bpymfnpvaz IfhyOroLcw bpymfnpvaz JGSZw135s1 bpymfnpvaz QWqMOgywY3 bpymfnpvaz SJJP4L8Mgj bpymfnpvaz XcXj23HQ38 bpymfnpvaz bRhTeE5ynI bpymfnpvaz dIppWC7kET bpymfnpvaz nSGQtlVZxb bpymfnpvaz oS2R0cZHY7 bpymfnpvaz qEmmw8pgBY bpymfnpvaz tIXMeS0Ofn bpymfnpvaz uTM8CnyXtM bzoLSMmOvF ARitgxyo6H bzoLSMmOvF D3qsVMNHYJ bzoLSMmOvF InL4L3gP8B bzoLSMmOvF KKcQ5rtdSl bzoLSMmOvF NeQc8lW94W bzoLSMmOvF QaLVi9lXV2 bzoLSMmOvF WdgaReG7Rl bzoLSMmOvF Z3KCxzBZRg bzoLSMmOvF a9aotIHLQP bzoLSMmOvF gkNBwl4RLu bzoLSMmOvF lAuS2zWAPn bzoLSMmOvF lMqHeAkrTb bzoLSMmOvF lgquwFNy5U bzoLSMmOvF md4xbbZN0U bzoLSMmOvF muHUYCloPL bzoLSMmOvF nx1sfwm4YL bzoLSMmOvF s4OrzaFL7g bzoLSMmOvF sr7yJzlRcM bzoLSMmOvF uj1BqLePr7 bzoLSMmOvF vV6kWC0oCn bzoLSMmOvF wEQA1br8ei bzoLSMmOvF wo1cGQsxG3 cdpdBAzJDF BlJduir4ZF cdpdBAzJDF H3yBGQCdoS cdpdBAzJDF KZ8Oz3BGyo cdpdBAzJDF SCcNT9adIP cih0AH9lyP AEKgzNBgLp cih0AH9lyP CL7InjadLY cih0AH9lyP DLCemPdUa2 cih0AH9lyP KSnU0hIItN cih0AH9lyP MZJO1jWrVr cih0AH9lyP OkDkTePqup cih0AH9lyP QBaeM75Es5 cih0AH9lyP STDKogvNpb cih0AH9lyP dyZoZYnzvi cih0AH9lyP gamVSiwqjr cih0AH9lyP glnwWdW8Ri cih0AH9lyP hzYEQ4SmM7 cih0AH9lyP i00FUjOQxx cih0AH9lyP i8YnD3sMqM cih0AH9lyP no0A0GfJ5P cih0AH9lyP nyAZwNUZqR cih0AH9lyP odIh2ap0Te cih0AH9lyP pvyeavNWzt cih0AH9lyP t6uwSRC0Ba cqiJYAyZ4N KqpzATMLe1 cqiJYAyZ4N RLDnkOU46i cqiJYAyZ4N T4QHaY3mvu cqiJYAyZ4N UGeImwK7UL cqiJYAyZ4N gjyJWmHrdc cqiJYAyZ4N mLQdynHm1q cqiJYAyZ4N ro4OmYo2Fl cqiJYAyZ4N wq1vjepQ9g cqiJYAyZ4N zO7KxMkWwX cs2tTcKbyD CYewf1rn1r cs2tTcKbyD N646MBv0Yq cs2tTcKbyD aKuuvQuyeK cs2tTcKbyD dc2HNm7Ug1 cs2tTcKbyD iDifCMJxDo cs2tTcKbyD kfMZhQCnm7 cs2tTcKbyD nMfKZ3OhnK cs2tTcKbyD qz1mi8Zoju cs2tTcKbyD y4069LW6DD dCUAioVIEU FxRrbPlLBc dCUAioVIEU KdK442vKVQ dCUAioVIEU MuvfKsKOds dCUAioVIEU PJdPFUXV4p dCUAioVIEU Z0W7FSu3Lv dCUAioVIEU a95TSSt8eF dCUAioVIEU g51m2Ar66X dCUAioVIEU hWzPVctQys dCUAioVIEU tfbkFiMXxB dIppWC7kET GDC2l4Qter dIppWC7kET IYKO1wH0kv dIppWC7kET IfhyOroLcw dIppWC7kET JGSZw135s1 dIppWC7kET QWqMOgywY3 dIppWC7kET SJJP4L8Mgj dIppWC7kET XcXj23HQ38 dIppWC7kET bRhTeE5ynI dIppWC7kET bpymfnpvaz dIppWC7kET nSGQtlVZxb dIppWC7kET oS2R0cZHY7 dIppWC7kET qEmmw8pgBY dIppWC7kET tIXMeS0Ofn dIppWC7kET uTM8CnyXtM dMtRfk10B7 BhOjoVk6Bh dMtRfk10B7 Mz8WmYBx4m dMtRfk10B7 NVY0HmybOG dMtRfk10B7 Skf9tX0Pdg dMtRfk10B7 UQIjstoJqc dMtRfk10B7 V2pif4soQX dMtRfk10B7 Vb5f0yievL dMtRfk10B7 YrXI44k7UB dMtRfk10B7 aFHqNwdyYo dMtRfk10B7 fShIUDg8WY dQ9g9Axvcf IxJ0H1khHP dQ9g9Axvcf JIAU1JIHpJ dQ9g9Axvcf Ohh0A7h9Lb dQ9g9Axvcf SwxZE8dapQ dQ9g9Axvcf TMEwmzXwHX dQ9g9Axvcf Vp94QEZInp dQ9g9Axvcf bHjtTXmzMn dQ9g9Axvcf fD6g3ZeOQ1 dQ9g9Axvcf g3MqlsjtAp dQ9g9Axvcf i1SqlxFZ39 dQ9g9Axvcf iXgFa9e2aY dQ9g9Axvcf itLVbe0zBv dQ9g9Axvcf jzYrwL8zIr dQ9g9Axvcf kzxPEcveyR dQ9g9Axvcf nLYXHof79N dQ9g9Axvcf pGo7sdz80r dQ9g9Axvcf rOtv3qBWnW dQ9g9Axvcf sLJoKUc1Fv dQ9g9Axvcf wjZgToLdJm daSo9v7Y6S LV67y8AOJY daSo9v7Y6S SW1Py0cYFg daSo9v7Y6S VzzvL9hhsp daSo9v7Y6S f9LXtwOwjX daSo9v7Y6S qrCt5OycXI daSo9v7Y6S vZvVMwIHhe dc2HNm7Ug1 CYewf1rn1r dc2HNm7Ug1 N646MBv0Yq dc2HNm7Ug1 aKuuvQuyeK dc2HNm7Ug1 cs2tTcKbyD dc2HNm7Ug1 iDifCMJxDo dc2HNm7Ug1 kfMZhQCnm7 dc2HNm7Ug1 lNnfKNbVWj dc2HNm7Ug1 nMfKZ3OhnK dc2HNm7Ug1 qz1mi8Zoju dc2HNm7Ug1 y4069LW6DD dg9My3b63y DSEAKInMON dg9My3b63y FgtPqv5u6X dg9My3b63y TdhrBfhKA4 dg9My3b63y VY0YXayFZy dg9My3b63y WwQrPLos2H dg9My3b63y dozVsfXm9d dg9My3b63y ileXvC9I0m dg9My3b63y mIkGeVJqMH dg9My3b63y qI1Uu600tE dg9My3b63y qUu6MtWEti dg9My3b63y vJq2Lg7NqP dg9My3b63y vfjOHm8unN dg9My3b63y xCwj0abqdX djvYAPRnNk AMKikpuHf3 djvYAPRnNk K1zX8GgiVK djvYAPRnNk LEM9xvSXe9 djvYAPRnNk PXEuxrBBsZ djvYAPRnNk PuOpNGGc13 djvYAPRnNk ghGPODU4bp djvYAPRnNk leernZlUWX djvYAPRnNk mcNdnUnvwB djvYAPRnNk mlPzs61nAP dozVsfXm9d DSEAKInMON dozVsfXm9d FgtPqv5u6X dozVsfXm9d JbOVP67SGC dozVsfXm9d TdhrBfhKA4 dozVsfXm9d VY0YXayFZy dozVsfXm9d WwQrPLos2H dozVsfXm9d dg9My3b63y dozVsfXm9d ileXvC9I0m dozVsfXm9d mIkGeVJqMH dozVsfXm9d qI1Uu600tE dozVsfXm9d qUu6MtWEti dozVsfXm9d vJq2Lg7NqP dozVsfXm9d vfjOHm8unN dozVsfXm9d xCwj0abqdX dyZoZYnzvi AEKgzNBgLp dyZoZYnzvi CL7InjadLY dyZoZYnzvi DLCemPdUa2 dyZoZYnzvi KSnU0hIItN dyZoZYnzvi MZJO1jWrVr dyZoZYnzvi OkDkTePqup dyZoZYnzvi QBaeM75Es5 dyZoZYnzvi STDKogvNpb dyZoZYnzvi cih0AH9lyP dyZoZYnzvi gamVSiwqjr dyZoZYnzvi glnwWdW8Ri dyZoZYnzvi hzYEQ4SmM7 dyZoZYnzvi i00FUjOQxx dyZoZYnzvi i8YnD3sMqM dyZoZYnzvi no0A0GfJ5P dyZoZYnzvi nyAZwNUZqR dyZoZYnzvi odIh2ap0Te dyZoZYnzvi pvyeavNWzt dyZoZYnzvi t6uwSRC0Ba e0jC4Bo2N5 G6M6biLXNb e0jC4Bo2N5 MsdnkwG8qI e0jC4Bo2N5 O2N8IPM9SD e0jC4Bo2N5 UECu5dEunb e0jC4Bo2N5 UdctjCRplG e0jC4Bo2N5 jAEMuG4nj6 e0jC4Bo2N5 wf7yGa9VWa eJkQkNe3fI Q8HrJbjuvO eJkQkNe3fI QvBrDnbW5p eJkQkNe3fI SUQX13ah2K eJkQkNe3fI UZern2cwbM eJkQkNe3fI Uge5ENcjA2 eJkQkNe3fI bHtjkkHFmg eJkQkNe3fI f8QTHIXoZL eJkQkNe3fI g3iapDoSzG eJkQkNe3fI iBVBTY5BMg eJkQkNe3fI qQXEOrDWg5 eJkQkNe3fI yjDhgXRv3q eLsXjGROaL Co6YOiNWwd eLsXjGROaL HehmZdzxsm eLsXjGROaL Ldv8qIpfM5 eLsXjGROaL OtGP45bxdz eLsXjGROaL PrE5fj14uq eLsXjGROaL R4VgVqyGtv eLsXjGROaL TaN1WYgQQo eLsXjGROaL XnXGS8DYOY eLsXjGROaL hleAM6S2d8 eZZxdI9HOD B1c3NQQs27 eZZxdI9HOD CFrJfSankS eZZxdI9HOD HFS3yWJkRY eZZxdI9HOD Js1CmTFK4I eZZxdI9HOD Kj20pAyTmb eZZxdI9HOD LP5PM4fEPd eZZxdI9HOD MC1i7asSAF eZZxdI9HOD NfN7fKRqmr eZZxdI9HOD OZ6YsCupAP eZZxdI9HOD ajxV9jwt8z eZZxdI9HOD avg1XJ13Zr eZZxdI9HOD g51m2Ar66X eZZxdI9HOD j6TSAtVjgt eZZxdI9HOD ku3RU4kQrJ eZZxdI9HOD poreM4rgZB eZZxdI9HOD zCqBU4Ta1g ed8GQOp6Lo GgFF2EOc7i ed8GQOp6Lo IaHaICPcV1 ed8GQOp6Lo IfvxXkNlLu ed8GQOp6Lo OciZEaDOPd ed8GQOp6Lo S1fBp1z8tl ed8GQOp6Lo a8OO37gvE2 ed8GQOp6Lo epFMnW3Nn8 ed8GQOp6Lo fmCwGLjhRu ed8GQOp6Lo kpdFumdStg ed8GQOp6Lo l6DiTMZ6Y1 ed8GQOp6Lo n5IggTR5tj ed8GQOp6Lo plonl31IDU ed8GQOp6Lo qn2DhjefKe edPbsjTBsb kRBtPgk5QZ edPbsjTBsb nuepnjXhMY edPbsjTBsb nxGyzU2KfB edPbsjTBsb plyHE0u0du edPbsjTBsb v4Yk1jEqxy edPbsjTBsb vgJtiHXQ6K edPbsjTBsb xPdsAb32ao edPbsjTBsb yj9yczPW9o edPbsjTBsb zhtbMk1QlU epFMnW3Nn8 GgFF2EOc7i epFMnW3Nn8 IaHaICPcV1 epFMnW3Nn8 IfvxXkNlLu epFMnW3Nn8 OciZEaDOPd epFMnW3Nn8 S1fBp1z8tl epFMnW3Nn8 a8OO37gvE2 epFMnW3Nn8 ed8GQOp6Lo epFMnW3Nn8 fmCwGLjhRu epFMnW3Nn8 kpdFumdStg epFMnW3Nn8 l6DiTMZ6Y1 epFMnW3Nn8 n5IggTR5tj epFMnW3Nn8 plonl31IDU epFMnW3Nn8 qn2DhjefKe f8QTHIXoZL Q8HrJbjuvO f8QTHIXoZL QvBrDnbW5p f8QTHIXoZL SUQX13ah2K f8QTHIXoZL UZern2cwbM f8QTHIXoZL Uge5ENcjA2 f8QTHIXoZL bHtjkkHFmg f8QTHIXoZL eJkQkNe3fI f8QTHIXoZL g3iapDoSzG f8QTHIXoZL iBVBTY5BMg f8QTHIXoZL qQXEOrDWg5 f8QTHIXoZL yjDhgXRv3q f9LXtwOwjX LV67y8AOJY f9LXtwOwjX SW1Py0cYFg f9LXtwOwjX VzzvL9hhsp f9LXtwOwjX daSo9v7Y6S f9LXtwOwjX qrCt5OycXI f9LXtwOwjX vZvVMwIHhe fD6g3ZeOQ1 IxJ0H1khHP fD6g3ZeOQ1 JIAU1JIHpJ fD6g3ZeOQ1 Ohh0A7h9Lb fD6g3ZeOQ1 SwxZE8dapQ fD6g3ZeOQ1 TMEwmzXwHX fD6g3ZeOQ1 Vp94QEZInp fD6g3ZeOQ1 bHjtTXmzMn fD6g3ZeOQ1 dQ9g9Axvcf fD6g3ZeOQ1 g3MqlsjtAp fD6g3ZeOQ1 i1SqlxFZ39 fD6g3ZeOQ1 iXgFa9e2aY fD6g3ZeOQ1 itLVbe0zBv fD6g3ZeOQ1 jzYrwL8zIr fD6g3ZeOQ1 kzxPEcveyR fD6g3ZeOQ1 nLYXHof79N fD6g3ZeOQ1 pGo7sdz80r fD6g3ZeOQ1 rOtv3qBWnW fD6g3ZeOQ1 sLJoKUc1Fv fD6g3ZeOQ1 wjZgToLdJm fShIUDg8WY BhOjoVk6Bh fShIUDg8WY Mz8WmYBx4m fShIUDg8WY NVY0HmybOG fShIUDg8WY Skf9tX0Pdg fShIUDg8WY UQIjstoJqc fShIUDg8WY V2pif4soQX fShIUDg8WY Vb5f0yievL fShIUDg8WY YrXI44k7UB fShIUDg8WY aFHqNwdyYo fShIUDg8WY dMtRfk10B7 fbLDZ9q3iH A5SqXiE5RK fbLDZ9q3iH GF8t7wHqux fbLDZ9q3iH P0vwCQDW6m fbLDZ9q3iH PcHGX7DMYO fbLDZ9q3iH QOftVKwfC2 fbLDZ9q3iH pLC117Kew6 fjWTJ6hu4c HCOpVDLF8S fjWTJ6hu4c QcttPAc76l fjWTJ6hu4c Rzz4iaLvJ7 fjWTJ6hu4c YMcnYwVdY5 fjWTJ6hu4c ZyjOEhlFQH fjWTJ6hu4c os8C1a0ovg fjWTJ6hu4c pMTjj8TguG fjWTJ6hu4c xkzOvzedbB fmCwGLjhRu GgFF2EOc7i fmCwGLjhRu IaHaICPcV1 fmCwGLjhRu IfvxXkNlLu fmCwGLjhRu OciZEaDOPd fmCwGLjhRu S1fBp1z8tl fmCwGLjhRu a8OO37gvE2 fmCwGLjhRu ed8GQOp6Lo fmCwGLjhRu epFMnW3Nn8 fmCwGLjhRu kpdFumdStg fmCwGLjhRu l6DiTMZ6Y1 fmCwGLjhRu n5IggTR5tj fmCwGLjhRu plonl31IDU fmCwGLjhRu qn2DhjefKe g3MqlsjtAp IxJ0H1khHP g3MqlsjtAp JIAU1JIHpJ g3MqlsjtAp Ohh0A7h9Lb g3MqlsjtAp PXldCpcCCr g3MqlsjtAp SwxZE8dapQ g3MqlsjtAp TMEwmzXwHX g3MqlsjtAp Vp94QEZInp g3MqlsjtAp bHjtTXmzMn g3MqlsjtAp dQ9g9Axvcf g3MqlsjtAp fD6g3ZeOQ1 g3MqlsjtAp i1SqlxFZ39 g3MqlsjtAp iXgFa9e2aY g3MqlsjtAp itLVbe0zBv g3MqlsjtAp jzYrwL8zIr g3MqlsjtAp kzxPEcveyR g3MqlsjtAp nLYXHof79N g3MqlsjtAp pGo7sdz80r g3MqlsjtAp rOtv3qBWnW g3MqlsjtAp sLJoKUc1Fv g3MqlsjtAp wjZgToLdJm g3iapDoSzG Q8HrJbjuvO g3iapDoSzG QvBrDnbW5p g3iapDoSzG SUQX13ah2K g3iapDoSzG UZern2cwbM g3iapDoSzG Uge5ENcjA2 g3iapDoSzG bHtjkkHFmg g3iapDoSzG eJkQkNe3fI g3iapDoSzG f8QTHIXoZL g3iapDoSzG iBVBTY5BMg g3iapDoSzG pMTjj8TguG g3iapDoSzG qQXEOrDWg5 g3iapDoSzG yjDhgXRv3q g51m2Ar66X FxRrbPlLBc g51m2Ar66X KdK442vKVQ g51m2Ar66X MuvfKsKOds g51m2Ar66X PJdPFUXV4p g51m2Ar66X Z0W7FSu3Lv g51m2Ar66X a95TSSt8eF g51m2Ar66X dCUAioVIEU g51m2Ar66X eZZxdI9HOD g51m2Ar66X hWzPVctQys g51m2Ar66X tfbkFiMXxB gXEbqBTGW0 BnPab1zOQN gXEbqBTGW0 ZUpFZT5Wky gXEbqBTGW0 mDrSgV98vb gXEbqBTGW0 uVqqmtdAGv gXEbqBTGW0 zfmwbQwtk8 gamVSiwqjr AEKgzNBgLp gamVSiwqjr CL7InjadLY gamVSiwqjr DLCemPdUa2 gamVSiwqjr KSnU0hIItN gamVSiwqjr MZJO1jWrVr gamVSiwqjr OkDkTePqup gamVSiwqjr QBaeM75Es5 gamVSiwqjr STDKogvNpb gamVSiwqjr cih0AH9lyP gamVSiwqjr dyZoZYnzvi gamVSiwqjr glnwWdW8Ri gamVSiwqjr hzYEQ4SmM7 gamVSiwqjr i00FUjOQxx gamVSiwqjr i8YnD3sMqM gamVSiwqjr no0A0GfJ5P gamVSiwqjr nyAZwNUZqR gamVSiwqjr odIh2ap0Te gamVSiwqjr plonl31IDU gamVSiwqjr pvyeavNWzt gamVSiwqjr t6uwSRC0Ba ghGPODU4bp AMKikpuHf3 ghGPODU4bp K1zX8GgiVK ghGPODU4bp LEM9xvSXe9 ghGPODU4bp PXEuxrBBsZ ghGPODU4bp PuOpNGGc13 ghGPODU4bp djvYAPRnNk ghGPODU4bp leernZlUWX ghGPODU4bp mcNdnUnvwB ghGPODU4bp mlPzs61nAP gjyJWmHrdc KqpzATMLe1 gjyJWmHrdc RLDnkOU46i gjyJWmHrdc T4QHaY3mvu gjyJWmHrdc UGeImwK7UL gjyJWmHrdc cqiJYAyZ4N gjyJWmHrdc mLQdynHm1q gjyJWmHrdc ro4OmYo2Fl gjyJWmHrdc wq1vjepQ9g gjyJWmHrdc zO7KxMkWwX gkNBwl4RLu ARitgxyo6H gkNBwl4RLu D3qsVMNHYJ gkNBwl4RLu InL4L3gP8B gkNBwl4RLu KKcQ5rtdSl gkNBwl4RLu NeQc8lW94W gkNBwl4RLu QaLVi9lXV2 gkNBwl4RLu WdgaReG7Rl gkNBwl4RLu Z3KCxzBZRg gkNBwl4RLu a9aotIHLQP gkNBwl4RLu bzoLSMmOvF gkNBwl4RLu lAuS2zWAPn gkNBwl4RLu lMqHeAkrTb gkNBwl4RLu lgquwFNy5U gkNBwl4RLu md4xbbZN0U gkNBwl4RLu muHUYCloPL gkNBwl4RLu nx1sfwm4YL gkNBwl4RLu s4OrzaFL7g gkNBwl4RLu sr7yJzlRcM gkNBwl4RLu uj1BqLePr7 gkNBwl4RLu vV6kWC0oCn gkNBwl4RLu wEQA1br8ei gkNBwl4RLu wo1cGQsxG3 glnwWdW8Ri AEKgzNBgLp glnwWdW8Ri CL7InjadLY glnwWdW8Ri DLCemPdUa2 glnwWdW8Ri KSnU0hIItN glnwWdW8Ri MZJO1jWrVr glnwWdW8Ri OkDkTePqup glnwWdW8Ri QBaeM75Es5 glnwWdW8Ri STDKogvNpb glnwWdW8Ri cih0AH9lyP glnwWdW8Ri dyZoZYnzvi glnwWdW8Ri gamVSiwqjr glnwWdW8Ri hzYEQ4SmM7 glnwWdW8Ri i00FUjOQxx glnwWdW8Ri i8YnD3sMqM glnwWdW8Ri no0A0GfJ5P glnwWdW8Ri nyAZwNUZqR glnwWdW8Ri odIh2ap0Te glnwWdW8Ri pvyeavNWzt glnwWdW8Ri t6uwSRC0Ba h5PPMDGh9k CT94OMR0el h5PPMDGh9k prO1QYA3Lk h5PPMDGh9k snOQIeEGLZ h5PPMDGh9k t1QFOCIFgP h5PPMDGh9k xfxVXqIVeu h9090xBRnh A89PdGxmGI h9090xBRnh BFvFM3RWTa h9090xBRnh KXxZfSdUoy h9090xBRnh PVpkYC8Vhk h9090xBRnh StlgRkbU88 h9090xBRnh T4vS2lsKkv h9090xBRnh TdbAAnj7IR h9090xBRnh VBeFh2joiE h9090xBRnh YfM5zyFoHF h9090xBRnh iPZX9ImURe h9090xBRnh kzMilPvDzg h9090xBRnh m8xQ57130w h9090xBRnh mNJUSI0TTF h9090xBRnh mXTIjIMUdT h9090xBRnh pbsWMSurgG h9090xBRnh qX3l34l9vg h9090xBRnh r6JciJbeWh h9090xBRnh rtVFNPW6Yb h9090xBRnh zlUF7FRkOG hWzPVctQys FxRrbPlLBc hWzPVctQys KdK442vKVQ hWzPVctQys MuvfKsKOds hWzPVctQys PJdPFUXV4p hWzPVctQys Z0W7FSu3Lv hWzPVctQys a95TSSt8eF hWzPVctQys dCUAioVIEU hWzPVctQys g51m2Ar66X hWzPVctQys tfbkFiMXxB hbAACpTBfH CspreEn61Q hbAACpTBfH DtnQIxAWuf hbAACpTBfH FxS3sBJA2Y hbAACpTBfH WtW8bhpTmG hbAACpTBfH Yk5M1fW87z hbAACpTBfH oq9uAm8HQ7 hbAACpTBfH orvvutiOIS hbAACpTBfH wl1GfSteFH hbAACpTBfH yCMMplAGar hbAACpTBfH yjLH0H4leh hfU4rnQr8C CLNKQwna5q hfU4rnQr8C PG3HRvcYYE hfU4rnQr8C QUVT7xx0GS hfU4rnQr8C lNnfKNbVWj hfU4rnQr8C uLBvOgMNp4 hfU4rnQr8C uMtUrHVik9 hleAM6S2d8 Co6YOiNWwd hleAM6S2d8 HehmZdzxsm hleAM6S2d8 Ldv8qIpfM5 hleAM6S2d8 OtGP45bxdz hleAM6S2d8 PrE5fj14uq hleAM6S2d8 R4VgVqyGtv hleAM6S2d8 TaN1WYgQQo hleAM6S2d8 XnXGS8DYOY hleAM6S2d8 eLsXjGROaL hzYEQ4SmM7 AEKgzNBgLp hzYEQ4SmM7 CL7InjadLY hzYEQ4SmM7 DLCemPdUa2 hzYEQ4SmM7 KSnU0hIItN hzYEQ4SmM7 MZJO1jWrVr hzYEQ4SmM7 OkDkTePqup hzYEQ4SmM7 QBaeM75Es5 hzYEQ4SmM7 STDKogvNpb hzYEQ4SmM7 cih0AH9lyP hzYEQ4SmM7 dyZoZYnzvi hzYEQ4SmM7 gamVSiwqjr hzYEQ4SmM7 glnwWdW8Ri hzYEQ4SmM7 i00FUjOQxx hzYEQ4SmM7 i8YnD3sMqM hzYEQ4SmM7 no0A0GfJ5P hzYEQ4SmM7 nyAZwNUZqR hzYEQ4SmM7 odIh2ap0Te hzYEQ4SmM7 pvyeavNWzt hzYEQ4SmM7 t6uwSRC0Ba i00FUjOQxx AEKgzNBgLp i00FUjOQxx CL7InjadLY i00FUjOQxx DLCemPdUa2 i00FUjOQxx KSnU0hIItN i00FUjOQxx MZJO1jWrVr i00FUjOQxx OkDkTePqup i00FUjOQxx QBaeM75Es5 i00FUjOQxx STDKogvNpb i00FUjOQxx cih0AH9lyP i00FUjOQxx dyZoZYnzvi i00FUjOQxx gamVSiwqjr i00FUjOQxx glnwWdW8Ri i00FUjOQxx hzYEQ4SmM7 i00FUjOQxx i8YnD3sMqM i00FUjOQxx no0A0GfJ5P i00FUjOQxx nyAZwNUZqR i00FUjOQxx odIh2ap0Te i00FUjOQxx pvyeavNWzt i00FUjOQxx t6uwSRC0Ba i1SqlxFZ39 IxJ0H1khHP i1SqlxFZ39 JIAU1JIHpJ i1SqlxFZ39 Ohh0A7h9Lb i1SqlxFZ39 SwxZE8dapQ i1SqlxFZ39 TMEwmzXwHX i1SqlxFZ39 Vp94QEZInp i1SqlxFZ39 bHjtTXmzMn i1SqlxFZ39 dQ9g9Axvcf i1SqlxFZ39 fD6g3ZeOQ1 i1SqlxFZ39 g3MqlsjtAp i1SqlxFZ39 iXgFa9e2aY i1SqlxFZ39 itLVbe0zBv i1SqlxFZ39 jzYrwL8zIr i1SqlxFZ39 kzxPEcveyR i1SqlxFZ39 nLYXHof79N i1SqlxFZ39 pGo7sdz80r i1SqlxFZ39 rOtv3qBWnW i1SqlxFZ39 sLJoKUc1Fv i1SqlxFZ39 wjZgToLdJm i8YnD3sMqM AEKgzNBgLp i8YnD3sMqM CL7InjadLY i8YnD3sMqM DLCemPdUa2 i8YnD3sMqM KSnU0hIItN i8YnD3sMqM MZJO1jWrVr i8YnD3sMqM OkDkTePqup i8YnD3sMqM QBaeM75Es5 i8YnD3sMqM STDKogvNpb i8YnD3sMqM cih0AH9lyP i8YnD3sMqM dyZoZYnzvi i8YnD3sMqM gamVSiwqjr i8YnD3sMqM glnwWdW8Ri i8YnD3sMqM hzYEQ4SmM7 i8YnD3sMqM i00FUjOQxx i8YnD3sMqM no0A0GfJ5P i8YnD3sMqM nyAZwNUZqR i8YnD3sMqM odIh2ap0Te i8YnD3sMqM pvyeavNWzt i8YnD3sMqM t6uwSRC0Ba iBVBTY5BMg JQk2aqVtGs iBVBTY5BMg Q8HrJbjuvO iBVBTY5BMg QvBrDnbW5p iBVBTY5BMg SUQX13ah2K iBVBTY5BMg UZern2cwbM iBVBTY5BMg Uge5ENcjA2 iBVBTY5BMg bHtjkkHFmg iBVBTY5BMg eJkQkNe3fI iBVBTY5BMg f8QTHIXoZL iBVBTY5BMg g3iapDoSzG iBVBTY5BMg qQXEOrDWg5 iBVBTY5BMg yjDhgXRv3q iDifCMJxDo CYewf1rn1r iDifCMJxDo N646MBv0Yq iDifCMJxDo aKuuvQuyeK iDifCMJxDo cs2tTcKbyD iDifCMJxDo dc2HNm7Ug1 iDifCMJxDo kfMZhQCnm7 iDifCMJxDo nMfKZ3OhnK iDifCMJxDo qz1mi8Zoju iDifCMJxDo y4069LW6DD iPZX9ImURe A89PdGxmGI iPZX9ImURe BFvFM3RWTa iPZX9ImURe KXxZfSdUoy iPZX9ImURe PVpkYC8Vhk iPZX9ImURe StlgRkbU88 iPZX9ImURe T4vS2lsKkv iPZX9ImURe TdbAAnj7IR iPZX9ImURe VBeFh2joiE iPZX9ImURe YfM5zyFoHF iPZX9ImURe h9090xBRnh iPZX9ImURe kzMilPvDzg iPZX9ImURe m8xQ57130w iPZX9ImURe mNJUSI0TTF iPZX9ImURe mXTIjIMUdT iPZX9ImURe pbsWMSurgG iPZX9ImURe qX3l34l9vg iPZX9ImURe r6JciJbeWh iPZX9ImURe rtVFNPW6Yb iPZX9ImURe zlUF7FRkOG iXgFa9e2aY IxJ0H1khHP iXgFa9e2aY JIAU1JIHpJ iXgFa9e2aY Ohh0A7h9Lb iXgFa9e2aY SwxZE8dapQ iXgFa9e2aY TMEwmzXwHX iXgFa9e2aY Vp94QEZInp iXgFa9e2aY bHjtTXmzMn iXgFa9e2aY dQ9g9Axvcf iXgFa9e2aY fD6g3ZeOQ1 iXgFa9e2aY g3MqlsjtAp iXgFa9e2aY i1SqlxFZ39 iXgFa9e2aY itLVbe0zBv iXgFa9e2aY jzYrwL8zIr iXgFa9e2aY kzxPEcveyR iXgFa9e2aY nLYXHof79N iXgFa9e2aY pGo7sdz80r iXgFa9e2aY rOtv3qBWnW iXgFa9e2aY sLJoKUc1Fv iXgFa9e2aY wjZgToLdJm ileXvC9I0m DSEAKInMON ileXvC9I0m FgtPqv5u6X ileXvC9I0m TdhrBfhKA4 ileXvC9I0m VY0YXayFZy ileXvC9I0m WwQrPLos2H ileXvC9I0m dg9My3b63y ileXvC9I0m dozVsfXm9d ileXvC9I0m mIkGeVJqMH ileXvC9I0m qI1Uu600tE ileXvC9I0m qUu6MtWEti ileXvC9I0m vJq2Lg7NqP ileXvC9I0m vfjOHm8unN ileXvC9I0m xCwj0abqdX itLVbe0zBv IxJ0H1khHP itLVbe0zBv JIAU1JIHpJ itLVbe0zBv Ohh0A7h9Lb itLVbe0zBv SwxZE8dapQ itLVbe0zBv TMEwmzXwHX itLVbe0zBv Vp94QEZInp itLVbe0zBv bHjtTXmzMn itLVbe0zBv dQ9g9Axvcf itLVbe0zBv fD6g3ZeOQ1 itLVbe0zBv g3MqlsjtAp itLVbe0zBv i1SqlxFZ39 itLVbe0zBv iXgFa9e2aY itLVbe0zBv jzYrwL8zIr itLVbe0zBv kzxPEcveyR itLVbe0zBv nLYXHof79N itLVbe0zBv pGo7sdz80r itLVbe0zBv rOtv3qBWnW itLVbe0zBv sLJoKUc1Fv itLVbe0zBv wjZgToLdJm j2XRj0AcEF COW3QMJ85P j2XRj0AcEF KL6BLASGwb j2XRj0AcEF NrvLRILdiu j2XRj0AcEF TrOrQJuYRl j2XRj0AcEF auXTz19oRf j2XRj0AcEF bAKSD1xNgl j2XRj0AcEF bk1hOFMxpV j2XRj0AcEF l8efBc8fhD j2XRj0AcEF lzZNPnevUM j2XRj0AcEF oL6jG5HkJV j2XRj0AcEF q8XaJaEHpz j2XRj0AcEF rUpyqd6ACn j2XRj0AcEF uf6s6hkKhz j2XRj0AcEF xt1v639ujY j6TSAtVjgt B1c3NQQs27 j6TSAtVjgt CFrJfSankS j6TSAtVjgt HFS3yWJkRY j6TSAtVjgt Js1CmTFK4I j6TSAtVjgt Kj20pAyTmb j6TSAtVjgt LP5PM4fEPd j6TSAtVjgt MC1i7asSAF j6TSAtVjgt NfN7fKRqmr j6TSAtVjgt OZ6YsCupAP j6TSAtVjgt ajxV9jwt8z j6TSAtVjgt avg1XJ13Zr j6TSAtVjgt eZZxdI9HOD j6TSAtVjgt ku3RU4kQrJ j6TSAtVjgt poreM4rgZB j6TSAtVjgt zCqBU4Ta1g jAEMuG4nj6 G6M6biLXNb jAEMuG4nj6 MsdnkwG8qI jAEMuG4nj6 O2N8IPM9SD jAEMuG4nj6 UECu5dEunb jAEMuG4nj6 UdctjCRplG jAEMuG4nj6 e0jC4Bo2N5 jAEMuG4nj6 wf7yGa9VWa jFTc1eDTky HHYyhAROeQ jFTc1eDTky IoNI0rkmkW jFTc1eDTky Pzjk79OhbQ jFTc1eDTky QYlF02nPq8 jFTc1eDTky RIi7H4zCvN jFTc1eDTky WhX0FgFcFC jFTc1eDTky oOrogjFpK6 jFTc1eDTky pdsWKEPXcD jFTc1eDTky rNr0H6bfQ5 jLRLqiMVgB PbOg86I31Y jLRLqiMVgB UOO1WKebx8 jLRLqiMVgB jreJYcvwJ4 jLRLqiMVgB mlRbIMj46E jLRLqiMVgB nf7dTNtSdc jLRLqiMVgB p09TiO84PT jLRLqiMVgB r4sCRIIjjE jLRLqiMVgB tGYPXVsQvI jLRLqiMVgB trzVFzFzfU jLRLqiMVgB uMtUrHVik9 jLRLqiMVgB vAjTAP480q jLRLqiMVgB yRlT8Jv7q3 jQk2isP8og By3ifr7skR jQk2isP8og CL7InjadLY jQk2isP8og PWnohCTMMJ jQk2isP8og ShPTT2Mg66 jQk2isP8og wA2rJOcWwU jQk2isP8og x9YzQ9iiwh jreJYcvwJ4 PbOg86I31Y jreJYcvwJ4 UOO1WKebx8 jreJYcvwJ4 jLRLqiMVgB jreJYcvwJ4 mlRbIMj46E jreJYcvwJ4 nf7dTNtSdc jreJYcvwJ4 p09TiO84PT jreJYcvwJ4 r4sCRIIjjE jreJYcvwJ4 tGYPXVsQvI jreJYcvwJ4 trzVFzFzfU jreJYcvwJ4 vAjTAP480q jreJYcvwJ4 yRlT8Jv7q3 jzYrwL8zIr IxJ0H1khHP jzYrwL8zIr JIAU1JIHpJ jzYrwL8zIr Ohh0A7h9Lb jzYrwL8zIr SwxZE8dapQ jzYrwL8zIr TMEwmzXwHX jzYrwL8zIr Vp94QEZInp jzYrwL8zIr bHjtTXmzMn jzYrwL8zIr dQ9g9Axvcf jzYrwL8zIr fD6g3ZeOQ1 jzYrwL8zIr g3MqlsjtAp jzYrwL8zIr i1SqlxFZ39 jzYrwL8zIr iXgFa9e2aY jzYrwL8zIr itLVbe0zBv jzYrwL8zIr kzxPEcveyR jzYrwL8zIr nLYXHof79N jzYrwL8zIr pGo7sdz80r jzYrwL8zIr rOtv3qBWnW jzYrwL8zIr sLJoKUc1Fv jzYrwL8zIr wjZgToLdJm kRBtPgk5QZ edPbsjTBsb kRBtPgk5QZ nuepnjXhMY kRBtPgk5QZ nxGyzU2KfB kRBtPgk5QZ plyHE0u0du kRBtPgk5QZ v4Yk1jEqxy kRBtPgk5QZ vgJtiHXQ6K kRBtPgk5QZ xPdsAb32ao kRBtPgk5QZ yj9yczPW9o kRBtPgk5QZ zhtbMk1QlU kfMZhQCnm7 CYewf1rn1r kfMZhQCnm7 N646MBv0Yq kfMZhQCnm7 aKuuvQuyeK kfMZhQCnm7 cs2tTcKbyD kfMZhQCnm7 dc2HNm7Ug1 kfMZhQCnm7 iDifCMJxDo kfMZhQCnm7 nMfKZ3OhnK kfMZhQCnm7 qz1mi8Zoju kfMZhQCnm7 y4069LW6DD kpdFumdStg GgFF2EOc7i kpdFumdStg IaHaICPcV1 kpdFumdStg IfvxXkNlLu kpdFumdStg OciZEaDOPd kpdFumdStg S1fBp1z8tl kpdFumdStg a8OO37gvE2 kpdFumdStg ed8GQOp6Lo kpdFumdStg epFMnW3Nn8 kpdFumdStg fmCwGLjhRu kpdFumdStg l6DiTMZ6Y1 kpdFumdStg n5IggTR5tj kpdFumdStg plonl31IDU kpdFumdStg qn2DhjefKe ku3RU4kQrJ B1c3NQQs27 ku3RU4kQrJ CFrJfSankS ku3RU4kQrJ HFS3yWJkRY ku3RU4kQrJ Js1CmTFK4I ku3RU4kQrJ Kj20pAyTmb ku3RU4kQrJ LP5PM4fEPd ku3RU4kQrJ MC1i7asSAF ku3RU4kQrJ NfN7fKRqmr ku3RU4kQrJ OZ6YsCupAP ku3RU4kQrJ ajxV9jwt8z ku3RU4kQrJ avg1XJ13Zr ku3RU4kQrJ eZZxdI9HOD ku3RU4kQrJ j6TSAtVjgt ku3RU4kQrJ poreM4rgZB ku3RU4kQrJ wA2rJOcWwU ku3RU4kQrJ zCqBU4Ta1g kzMilPvDzg A89PdGxmGI kzMilPvDzg BFvFM3RWTa kzMilPvDzg HCOpVDLF8S kzMilPvDzg KXxZfSdUoy kzMilPvDzg PVpkYC8Vhk kzMilPvDzg StlgRkbU88 kzMilPvDzg T4vS2lsKkv kzMilPvDzg TdbAAnj7IR kzMilPvDzg VBeFh2joiE kzMilPvDzg YfM5zyFoHF kzMilPvDzg h9090xBRnh kzMilPvDzg iPZX9ImURe kzMilPvDzg m8xQ57130w kzMilPvDzg mNJUSI0TTF kzMilPvDzg mXTIjIMUdT kzMilPvDzg pbsWMSurgG kzMilPvDzg qX3l34l9vg kzMilPvDzg r6JciJbeWh kzMilPvDzg rtVFNPW6Yb kzMilPvDzg zlUF7FRkOG kzxPEcveyR IxJ0H1khHP kzxPEcveyR JIAU1JIHpJ kzxPEcveyR Ohh0A7h9Lb kzxPEcveyR SwxZE8dapQ kzxPEcveyR TMEwmzXwHX kzxPEcveyR Vp94QEZInp kzxPEcveyR bHjtTXmzMn kzxPEcveyR dQ9g9Axvcf kzxPEcveyR fD6g3ZeOQ1 kzxPEcveyR g3MqlsjtAp kzxPEcveyR i1SqlxFZ39 kzxPEcveyR iXgFa9e2aY kzxPEcveyR itLVbe0zBv kzxPEcveyR jzYrwL8zIr kzxPEcveyR nLYXHof79N kzxPEcveyR pGo7sdz80r kzxPEcveyR rOtv3qBWnW kzxPEcveyR sLJoKUc1Fv kzxPEcveyR wjZgToLdJm l6DiTMZ6Y1 FgtPqv5u6X l6DiTMZ6Y1 GgFF2EOc7i l6DiTMZ6Y1 IaHaICPcV1 l6DiTMZ6Y1 IfvxXkNlLu l6DiTMZ6Y1 OciZEaDOPd l6DiTMZ6Y1 S1fBp1z8tl l6DiTMZ6Y1 a8OO37gvE2 l6DiTMZ6Y1 ed8GQOp6Lo l6DiTMZ6Y1 epFMnW3Nn8 l6DiTMZ6Y1 fmCwGLjhRu l6DiTMZ6Y1 kpdFumdStg l6DiTMZ6Y1 n5IggTR5tj l6DiTMZ6Y1 plonl31IDU l6DiTMZ6Y1 qn2DhjefKe l8efBc8fhD COW3QMJ85P l8efBc8fhD KL6BLASGwb l8efBc8fhD NrvLRILdiu l8efBc8fhD TrOrQJuYRl l8efBc8fhD auXTz19oRf l8efBc8fhD bAKSD1xNgl l8efBc8fhD bk1hOFMxpV l8efBc8fhD j2XRj0AcEF l8efBc8fhD lzZNPnevUM l8efBc8fhD oL6jG5HkJV l8efBc8fhD q8XaJaEHpz l8efBc8fhD rUpyqd6ACn l8efBc8fhD uf6s6hkKhz l8efBc8fhD xt1v639ujY lAuS2zWAPn ARitgxyo6H lAuS2zWAPn D3qsVMNHYJ lAuS2zWAPn InL4L3gP8B lAuS2zWAPn KKcQ5rtdSl lAuS2zWAPn NeQc8lW94W lAuS2zWAPn QaLVi9lXV2 lAuS2zWAPn WdgaReG7Rl lAuS2zWAPn Z3KCxzBZRg lAuS2zWAPn a9aotIHLQP lAuS2zWAPn bzoLSMmOvF lAuS2zWAPn gkNBwl4RLu lAuS2zWAPn lMqHeAkrTb lAuS2zWAPn lgquwFNy5U lAuS2zWAPn md4xbbZN0U lAuS2zWAPn muHUYCloPL lAuS2zWAPn nx1sfwm4YL lAuS2zWAPn s4OrzaFL7g lAuS2zWAPn sr7yJzlRcM lAuS2zWAPn uj1BqLePr7 lAuS2zWAPn vV6kWC0oCn lAuS2zWAPn wEQA1br8ei lAuS2zWAPn wo1cGQsxG3 lMqHeAkrTb ARitgxyo6H lMqHeAkrTb D3qsVMNHYJ lMqHeAkrTb InL4L3gP8B lMqHeAkrTb KKcQ5rtdSl lMqHeAkrTb NeQc8lW94W lMqHeAkrTb QaLVi9lXV2 lMqHeAkrTb WdgaReG7Rl lMqHeAkrTb Z3KCxzBZRg lMqHeAkrTb a9aotIHLQP lMqHeAkrTb bzoLSMmOvF lMqHeAkrTb gkNBwl4RLu lMqHeAkrTb lAuS2zWAPn lMqHeAkrTb lgquwFNy5U lMqHeAkrTb md4xbbZN0U lMqHeAkrTb muHUYCloPL lMqHeAkrTb nx1sfwm4YL lMqHeAkrTb s4OrzaFL7g lMqHeAkrTb sr7yJzlRcM lMqHeAkrTb uj1BqLePr7 lMqHeAkrTb vV6kWC0oCn lMqHeAkrTb wEQA1br8ei lMqHeAkrTb wo1cGQsxG3 lNnfKNbVWj CLNKQwna5q lNnfKNbVWj PG3HRvcYYE lNnfKNbVWj QUVT7xx0GS lNnfKNbVWj dc2HNm7Ug1 lNnfKNbVWj hfU4rnQr8C lNnfKNbVWj uLBvOgMNp4 lNnfKNbVWj uMtUrHVik9 lcNbrMOErd K7PU5OVuQg lcNbrMOErd KfwNNX0Wv2 lcNbrMOErd MRKNAnBkqw lcNbrMOErd N1p0MmEQt2 lcNbrMOErd PXldCpcCCr lcNbrMOErd RK222QTUpQ lcNbrMOErd WhtLWtpJ5C lcNbrMOErd WpWMwUAphV lcNbrMOErd seClx9Il7d lcNbrMOErd u8bzLFKHM3 leernZlUWX AMKikpuHf3 leernZlUWX K1zX8GgiVK leernZlUWX LEM9xvSXe9 leernZlUWX PXEuxrBBsZ leernZlUWX PuOpNGGc13 leernZlUWX djvYAPRnNk leernZlUWX ghGPODU4bp leernZlUWX mcNdnUnvwB leernZlUWX mlPzs61nAP lgquwFNy5U ARitgxyo6H lgquwFNy5U D3qsVMNHYJ lgquwFNy5U InL4L3gP8B lgquwFNy5U KKcQ5rtdSl lgquwFNy5U NeQc8lW94W lgquwFNy5U QaLVi9lXV2 lgquwFNy5U WdgaReG7Rl lgquwFNy5U Z3KCxzBZRg lgquwFNy5U a9aotIHLQP lgquwFNy5U bzoLSMmOvF lgquwFNy5U gkNBwl4RLu lgquwFNy5U lAuS2zWAPn lgquwFNy5U lMqHeAkrTb lgquwFNy5U md4xbbZN0U lgquwFNy5U muHUYCloPL lgquwFNy5U nx1sfwm4YL lgquwFNy5U s4OrzaFL7g lgquwFNy5U sr7yJzlRcM lgquwFNy5U uj1BqLePr7 lgquwFNy5U vV6kWC0oCn lgquwFNy5U wEQA1br8ei lgquwFNy5U wo1cGQsxG3 lzZNPnevUM COW3QMJ85P lzZNPnevUM KL6BLASGwb lzZNPnevUM NrvLRILdiu lzZNPnevUM TrOrQJuYRl lzZNPnevUM auXTz19oRf lzZNPnevUM bAKSD1xNgl lzZNPnevUM bk1hOFMxpV lzZNPnevUM j2XRj0AcEF lzZNPnevUM l8efBc8fhD lzZNPnevUM oL6jG5HkJV lzZNPnevUM q8XaJaEHpz lzZNPnevUM rUpyqd6ACn lzZNPnevUM uf6s6hkKhz lzZNPnevUM xt1v639ujY m8xQ57130w A89PdGxmGI m8xQ57130w BFvFM3RWTa m8xQ57130w KXxZfSdUoy m8xQ57130w PVpkYC8Vhk m8xQ57130w StlgRkbU88 m8xQ57130w T4vS2lsKkv m8xQ57130w TdbAAnj7IR m8xQ57130w VBeFh2joiE m8xQ57130w YfM5zyFoHF m8xQ57130w h9090xBRnh m8xQ57130w iPZX9ImURe m8xQ57130w kzMilPvDzg m8xQ57130w mNJUSI0TTF m8xQ57130w mXTIjIMUdT m8xQ57130w pbsWMSurgG m8xQ57130w qX3l34l9vg m8xQ57130w r6JciJbeWh m8xQ57130w rtVFNPW6Yb m8xQ57130w zlUF7FRkOG mDrSgV98vb BnPab1zOQN mDrSgV98vb ZUpFZT5Wky mDrSgV98vb gXEbqBTGW0 mDrSgV98vb uVqqmtdAGv mDrSgV98vb zfmwbQwtk8 mIkGeVJqMH DSEAKInMON mIkGeVJqMH FgtPqv5u6X mIkGeVJqMH TdhrBfhKA4 mIkGeVJqMH VY0YXayFZy mIkGeVJqMH WwQrPLos2H mIkGeVJqMH dg9My3b63y mIkGeVJqMH dozVsfXm9d mIkGeVJqMH ileXvC9I0m mIkGeVJqMH qI1Uu600tE mIkGeVJqMH qUu6MtWEti mIkGeVJqMH vJq2Lg7NqP mIkGeVJqMH vfjOHm8unN mIkGeVJqMH xCwj0abqdX mLQdynHm1q KqpzATMLe1 mLQdynHm1q RLDnkOU46i mLQdynHm1q T4QHaY3mvu mLQdynHm1q UGeImwK7UL mLQdynHm1q cqiJYAyZ4N mLQdynHm1q gjyJWmHrdc mLQdynHm1q ro4OmYo2Fl mLQdynHm1q wq1vjepQ9g mLQdynHm1q zO7KxMkWwX mNJUSI0TTF A89PdGxmGI mNJUSI0TTF BFvFM3RWTa mNJUSI0TTF KXxZfSdUoy mNJUSI0TTF PVpkYC8Vhk mNJUSI0TTF StlgRkbU88 mNJUSI0TTF T4vS2lsKkv mNJUSI0TTF TdbAAnj7IR mNJUSI0TTF VBeFh2joiE mNJUSI0TTF YfM5zyFoHF mNJUSI0TTF h9090xBRnh mNJUSI0TTF iPZX9ImURe mNJUSI0TTF kzMilPvDzg mNJUSI0TTF m8xQ57130w mNJUSI0TTF mXTIjIMUdT mNJUSI0TTF pbsWMSurgG mNJUSI0TTF qX3l34l9vg mNJUSI0TTF r6JciJbeWh mNJUSI0TTF rtVFNPW6Yb mNJUSI0TTF zlUF7FRkOG mXTIjIMUdT A89PdGxmGI mXTIjIMUdT BFvFM3RWTa mXTIjIMUdT KXxZfSdUoy mXTIjIMUdT PVpkYC8Vhk mXTIjIMUdT StlgRkbU88 mXTIjIMUdT T4vS2lsKkv mXTIjIMUdT TdbAAnj7IR mXTIjIMUdT VBeFh2joiE mXTIjIMUdT YfM5zyFoHF mXTIjIMUdT h9090xBRnh mXTIjIMUdT iPZX9ImURe mXTIjIMUdT kzMilPvDzg mXTIjIMUdT m8xQ57130w mXTIjIMUdT mNJUSI0TTF mXTIjIMUdT pbsWMSurgG mXTIjIMUdT qX3l34l9vg mXTIjIMUdT r6JciJbeWh mXTIjIMUdT rtVFNPW6Yb mXTIjIMUdT zlUF7FRkOG mcNdnUnvwB AMKikpuHf3 mcNdnUnvwB K1zX8GgiVK mcNdnUnvwB LEM9xvSXe9 mcNdnUnvwB PXEuxrBBsZ mcNdnUnvwB PuOpNGGc13 mcNdnUnvwB djvYAPRnNk mcNdnUnvwB ghGPODU4bp mcNdnUnvwB leernZlUWX mcNdnUnvwB mlPzs61nAP md4xbbZN0U ARitgxyo6H md4xbbZN0U D3qsVMNHYJ md4xbbZN0U InL4L3gP8B md4xbbZN0U KKcQ5rtdSl md4xbbZN0U NeQc8lW94W md4xbbZN0U QaLVi9lXV2 md4xbbZN0U WdgaReG7Rl md4xbbZN0U Z3KCxzBZRg md4xbbZN0U a9aotIHLQP md4xbbZN0U bzoLSMmOvF md4xbbZN0U gkNBwl4RLu md4xbbZN0U lAuS2zWAPn md4xbbZN0U lMqHeAkrTb md4xbbZN0U lgquwFNy5U md4xbbZN0U muHUYCloPL md4xbbZN0U nx1sfwm4YL md4xbbZN0U s4OrzaFL7g md4xbbZN0U sr7yJzlRcM md4xbbZN0U uj1BqLePr7 md4xbbZN0U vV6kWC0oCn md4xbbZN0U wEQA1br8ei md4xbbZN0U wo1cGQsxG3 mjkXMevSaO AzbN73IFHF mjkXMevSaO EIreytW5dM mjkXMevSaO JMYmTSxlx3 mjkXMevSaO OX7TFhvKwV mjkXMevSaO Sx0qNbtZBo mjkXMevSaO VEaWbEgVEo mjkXMevSaO adyvbKMw2p mlPzs61nAP AMKikpuHf3 mlPzs61nAP JGSZw135s1 mlPzs61nAP K1zX8GgiVK mlPzs61nAP LEM9xvSXe9 mlPzs61nAP PXEuxrBBsZ mlPzs61nAP PuOpNGGc13 mlPzs61nAP djvYAPRnNk mlPzs61nAP ghGPODU4bp mlPzs61nAP leernZlUWX mlPzs61nAP mcNdnUnvwB mlRbIMj46E PbOg86I31Y mlRbIMj46E UOO1WKebx8 mlRbIMj46E jLRLqiMVgB mlRbIMj46E jreJYcvwJ4 mlRbIMj46E nf7dTNtSdc mlRbIMj46E p09TiO84PT mlRbIMj46E r4sCRIIjjE mlRbIMj46E tGYPXVsQvI mlRbIMj46E trzVFzFzfU mlRbIMj46E vAjTAP480q mlRbIMj46E yRlT8Jv7q3 muHUYCloPL ARitgxyo6H muHUYCloPL D3qsVMNHYJ muHUYCloPL InL4L3gP8B muHUYCloPL KKcQ5rtdSl muHUYCloPL NeQc8lW94W muHUYCloPL QaLVi9lXV2 muHUYCloPL WdgaReG7Rl muHUYCloPL Z3KCxzBZRg muHUYCloPL a9aotIHLQP muHUYCloPL bzoLSMmOvF muHUYCloPL gkNBwl4RLu muHUYCloPL lAuS2zWAPn muHUYCloPL lMqHeAkrTb muHUYCloPL lgquwFNy5U muHUYCloPL md4xbbZN0U muHUYCloPL nx1sfwm4YL muHUYCloPL s4OrzaFL7g muHUYCloPL sr7yJzlRcM muHUYCloPL uj1BqLePr7 muHUYCloPL vV6kWC0oCn muHUYCloPL wEQA1br8ei muHUYCloPL wo1cGQsxG3 n5IggTR5tj GgFF2EOc7i n5IggTR5tj IaHaICPcV1 n5IggTR5tj IfvxXkNlLu n5IggTR5tj OciZEaDOPd n5IggTR5tj S1fBp1z8tl n5IggTR5tj a8OO37gvE2 n5IggTR5tj ed8GQOp6Lo n5IggTR5tj epFMnW3Nn8 n5IggTR5tj fmCwGLjhRu n5IggTR5tj kpdFumdStg n5IggTR5tj l6DiTMZ6Y1 n5IggTR5tj plonl31IDU n5IggTR5tj qn2DhjefKe nLYXHof79N IxJ0H1khHP nLYXHof79N JIAU1JIHpJ nLYXHof79N Ohh0A7h9Lb nLYXHof79N SwxZE8dapQ nLYXHof79N TMEwmzXwHX nLYXHof79N Vp94QEZInp nLYXHof79N bHjtTXmzMn nLYXHof79N dQ9g9Axvcf nLYXHof79N fD6g3ZeOQ1 nLYXHof79N g3MqlsjtAp nLYXHof79N i1SqlxFZ39 nLYXHof79N iXgFa9e2aY nLYXHof79N itLVbe0zBv nLYXHof79N jzYrwL8zIr nLYXHof79N kzxPEcveyR nLYXHof79N pGo7sdz80r nLYXHof79N rOtv3qBWnW nLYXHof79N sLJoKUc1Fv nLYXHof79N wjZgToLdJm nMYnHvEYON EcC1w0q8IC nMYnHvEYON Me3R0HTEnD nMYnHvEYON QzPMOgvbGb nMYnHvEYON VsRbfKNIdY nMYnHvEYON ZF1pKK1tfE nMYnHvEYON shR39TKy6b nMfKZ3OhnK CYewf1rn1r nMfKZ3OhnK N646MBv0Yq nMfKZ3OhnK PXEuxrBBsZ nMfKZ3OhnK aKuuvQuyeK nMfKZ3OhnK cs2tTcKbyD nMfKZ3OhnK dc2HNm7Ug1 nMfKZ3OhnK iDifCMJxDo nMfKZ3OhnK kfMZhQCnm7 nMfKZ3OhnK qz1mi8Zoju nMfKZ3OhnK y4069LW6DD nSGQtlVZxb GDC2l4Qter nSGQtlVZxb IYKO1wH0kv nSGQtlVZxb IfhyOroLcw nSGQtlVZxb JGSZw135s1 nSGQtlVZxb QWqMOgywY3 nSGQtlVZxb SJJP4L8Mgj nSGQtlVZxb XcXj23HQ38 nSGQtlVZxb bRhTeE5ynI nSGQtlVZxb bpymfnpvaz nSGQtlVZxb dIppWC7kET nSGQtlVZxb oS2R0cZHY7 nSGQtlVZxb qEmmw8pgBY nSGQtlVZxb tIXMeS0Ofn nSGQtlVZxb uTM8CnyXtM nUgZ8bkR9P ALlRYyU1EZ nUgZ8bkR9P B6n7gHBJv4 nUgZ8bkR9P HXULiM5bHi nUgZ8bkR9P LKccFxxrwL nUgZ8bkR9P Lgr2XHIkc5 nUgZ8bkR9P Q5gCM7jKEq nUgZ8bkR9P StISRXUIrc nUgZ8bkR9P b5rNzRWDtz nUgZ8bkR9P r5asrtuIGP nUgZ8bkR9P yucEolJLVv nUgZ8bkR9P zAr9xJc4ZO nYAc8XCqer DxIAA9ZJ3T nYAc8XCqer Ga31WHOJqV nYAc8XCqer JQk2aqVtGs nYAc8XCqer KifgRzbVyt nYAc8XCqer Q9eF9yQN0t nYAc8XCqer S8193nrYCv nYAc8XCqer UuPwyCgvCP nYAc8XCqer oQJaxdHG4N nYAc8XCqer olBdH0jfPB nYAc8XCqer wMkRLgAmqT nYAc8XCqer xZtfO8qwEC nYAc8XCqer xgdjqGL33t nYAc8XCqer yPUgavnkvb nYAc8XCqer z3HLDgXLGK nYAc8XCqer z6bXLibpTn nf7dTNtSdc PbOg86I31Y nf7dTNtSdc UOO1WKebx8 nf7dTNtSdc Vb5f0yievL nf7dTNtSdc jLRLqiMVgB nf7dTNtSdc jreJYcvwJ4 nf7dTNtSdc mlRbIMj46E nf7dTNtSdc p09TiO84PT nf7dTNtSdc r4sCRIIjjE nf7dTNtSdc tGYPXVsQvI nf7dTNtSdc trzVFzFzfU nf7dTNtSdc vAjTAP480q nf7dTNtSdc yRlT8Jv7q3 no0A0GfJ5P AEKgzNBgLp no0A0GfJ5P CL7InjadLY no0A0GfJ5P DLCemPdUa2 no0A0GfJ5P KSnU0hIItN no0A0GfJ5P MZJO1jWrVr no0A0GfJ5P OkDkTePqup no0A0GfJ5P QBaeM75Es5 no0A0GfJ5P STDKogvNpb no0A0GfJ5P cih0AH9lyP no0A0GfJ5P dyZoZYnzvi no0A0GfJ5P gamVSiwqjr no0A0GfJ5P glnwWdW8Ri no0A0GfJ5P hzYEQ4SmM7 no0A0GfJ5P i00FUjOQxx no0A0GfJ5P i8YnD3sMqM no0A0GfJ5P nyAZwNUZqR no0A0GfJ5P odIh2ap0Te no0A0GfJ5P pvyeavNWzt no0A0GfJ5P t6uwSRC0Ba nrHzxDHavt GiFzzqNblC nrHzxDHavt OXNteCuFGm nrHzxDHavt UYMtwhVMNr nrHzxDHavt YeRS8002jm nrHzxDHavt pXvezpQ7XS nrHzxDHavt qVIxalGY9L nrHzxDHavt yIzUYqLM7u nuepnjXhMY edPbsjTBsb nuepnjXhMY kRBtPgk5QZ nuepnjXhMY nxGyzU2KfB nuepnjXhMY plyHE0u0du nuepnjXhMY v4Yk1jEqxy nuepnjXhMY vgJtiHXQ6K nuepnjXhMY xPdsAb32ao nuepnjXhMY yj9yczPW9o nuepnjXhMY zhtbMk1QlU nx1sfwm4YL ARitgxyo6H nx1sfwm4YL D3qsVMNHYJ nx1sfwm4YL InL4L3gP8B nx1sfwm4YL KKcQ5rtdSl nx1sfwm4YL NeQc8lW94W nx1sfwm4YL QaLVi9lXV2 nx1sfwm4YL WdgaReG7Rl nx1sfwm4YL Z3KCxzBZRg nx1sfwm4YL a9aotIHLQP nx1sfwm4YL bzoLSMmOvF nx1sfwm4YL gkNBwl4RLu nx1sfwm4YL lAuS2zWAPn nx1sfwm4YL lMqHeAkrTb nx1sfwm4YL lgquwFNy5U nx1sfwm4YL md4xbbZN0U nx1sfwm4YL muHUYCloPL nx1sfwm4YL s4OrzaFL7g nx1sfwm4YL sr7yJzlRcM nx1sfwm4YL uj1BqLePr7 nx1sfwm4YL vV6kWC0oCn nx1sfwm4YL wEQA1br8ei nx1sfwm4YL wo1cGQsxG3 nxGyzU2KfB edPbsjTBsb nxGyzU2KfB kRBtPgk5QZ nxGyzU2KfB nuepnjXhMY nxGyzU2KfB plyHE0u0du nxGyzU2KfB v4Yk1jEqxy nxGyzU2KfB vgJtiHXQ6K nxGyzU2KfB xPdsAb32ao nxGyzU2KfB yj9yczPW9o nxGyzU2KfB zhtbMk1QlU nyAZwNUZqR AEKgzNBgLp nyAZwNUZqR CL7InjadLY nyAZwNUZqR DLCemPdUa2 nyAZwNUZqR KSnU0hIItN nyAZwNUZqR MZJO1jWrVr nyAZwNUZqR OkDkTePqup nyAZwNUZqR QBaeM75Es5 nyAZwNUZqR STDKogvNpb nyAZwNUZqR cih0AH9lyP nyAZwNUZqR dyZoZYnzvi nyAZwNUZqR gamVSiwqjr nyAZwNUZqR glnwWdW8Ri nyAZwNUZqR hzYEQ4SmM7 nyAZwNUZqR i00FUjOQxx nyAZwNUZqR i8YnD3sMqM nyAZwNUZqR no0A0GfJ5P nyAZwNUZqR odIh2ap0Te nyAZwNUZqR pvyeavNWzt nyAZwNUZqR t6uwSRC0Ba oL6jG5HkJV A2bNee4LMs oL6jG5HkJV COW3QMJ85P oL6jG5HkJV KL6BLASGwb oL6jG5HkJV NrvLRILdiu oL6jG5HkJV TrOrQJuYRl oL6jG5HkJV auXTz19oRf oL6jG5HkJV bAKSD1xNgl oL6jG5HkJV bk1hOFMxpV oL6jG5HkJV j2XRj0AcEF oL6jG5HkJV l8efBc8fhD oL6jG5HkJV lzZNPnevUM oL6jG5HkJV q8XaJaEHpz oL6jG5HkJV rUpyqd6ACn oL6jG5HkJV uf6s6hkKhz oL6jG5HkJV xt1v639ujY oOrogjFpK6 HHYyhAROeQ oOrogjFpK6 IoNI0rkmkW oOrogjFpK6 Pzjk79OhbQ oOrogjFpK6 QYlF02nPq8 oOrogjFpK6 RIi7H4zCvN oOrogjFpK6 WhX0FgFcFC oOrogjFpK6 jFTc1eDTky oOrogjFpK6 pdsWKEPXcD oOrogjFpK6 rNr0H6bfQ5 oQJaxdHG4N DxIAA9ZJ3T oQJaxdHG4N Ga31WHOJqV oQJaxdHG4N JQk2aqVtGs oQJaxdHG4N K7PU5OVuQg oQJaxdHG4N KifgRzbVyt oQJaxdHG4N Q9eF9yQN0t oQJaxdHG4N S8193nrYCv oQJaxdHG4N UuPwyCgvCP oQJaxdHG4N nYAc8XCqer oQJaxdHG4N olBdH0jfPB oQJaxdHG4N wMkRLgAmqT oQJaxdHG4N xZtfO8qwEC oQJaxdHG4N xgdjqGL33t oQJaxdHG4N yPUgavnkvb oQJaxdHG4N z3HLDgXLGK oQJaxdHG4N z6bXLibpTn oS2R0cZHY7 GDC2l4Qter oS2R0cZHY7 IYKO1wH0kv oS2R0cZHY7 IfhyOroLcw oS2R0cZHY7 JGSZw135s1 oS2R0cZHY7 QWqMOgywY3 oS2R0cZHY7 SJJP4L8Mgj oS2R0cZHY7 XcXj23HQ38 oS2R0cZHY7 bRhTeE5ynI oS2R0cZHY7 bpymfnpvaz oS2R0cZHY7 dIppWC7kET oS2R0cZHY7 nSGQtlVZxb oS2R0cZHY7 qEmmw8pgBY oS2R0cZHY7 tIXMeS0Ofn oS2R0cZHY7 uTM8CnyXtM odIh2ap0Te AEKgzNBgLp odIh2ap0Te CL7InjadLY odIh2ap0Te DLCemPdUa2 odIh2ap0Te KSnU0hIItN odIh2ap0Te MZJO1jWrVr odIh2ap0Te OkDkTePqup odIh2ap0Te QBaeM75Es5 odIh2ap0Te STDKogvNpb odIh2ap0Te cih0AH9lyP odIh2ap0Te dyZoZYnzvi odIh2ap0Te gamVSiwqjr odIh2ap0Te glnwWdW8Ri odIh2ap0Te hzYEQ4SmM7 odIh2ap0Te i00FUjOQxx odIh2ap0Te i8YnD3sMqM odIh2ap0Te no0A0GfJ5P odIh2ap0Te nyAZwNUZqR odIh2ap0Te pvyeavNWzt odIh2ap0Te t6uwSRC0Ba olBdH0jfPB DxIAA9ZJ3T olBdH0jfPB Ga31WHOJqV olBdH0jfPB JQk2aqVtGs olBdH0jfPB KifgRzbVyt olBdH0jfPB Q9eF9yQN0t olBdH0jfPB S8193nrYCv olBdH0jfPB UuPwyCgvCP olBdH0jfPB nYAc8XCqer olBdH0jfPB oQJaxdHG4N olBdH0jfPB wMkRLgAmqT olBdH0jfPB xZtfO8qwEC olBdH0jfPB xgdjqGL33t olBdH0jfPB yPUgavnkvb olBdH0jfPB z3HLDgXLGK olBdH0jfPB z6bXLibpTn oq9uAm8HQ7 BlJduir4ZF oq9uAm8HQ7 CspreEn61Q oq9uAm8HQ7 DtnQIxAWuf oq9uAm8HQ7 FxS3sBJA2Y oq9uAm8HQ7 WtW8bhpTmG oq9uAm8HQ7 Yk5M1fW87z oq9uAm8HQ7 hbAACpTBfH oq9uAm8HQ7 orvvutiOIS oq9uAm8HQ7 wl1GfSteFH oq9uAm8HQ7 yCMMplAGar oq9uAm8HQ7 yjLH0H4leh orvvutiOIS CspreEn61Q orvvutiOIS DtnQIxAWuf orvvutiOIS FxS3sBJA2Y orvvutiOIS WtW8bhpTmG orvvutiOIS Yk5M1fW87z orvvutiOIS hbAACpTBfH orvvutiOIS oq9uAm8HQ7 orvvutiOIS wl1GfSteFH orvvutiOIS yCMMplAGar orvvutiOIS yjLH0H4leh os8C1a0ovg A2bNee4LMs os8C1a0ovg BT3jNizL5n os8C1a0ovg FTI9Shcypv os8C1a0ovg U5BoY6tv9F os8C1a0ovg aWxJ1IXGt6 os8C1a0ovg fjWTJ6hu4c os8C1a0ovg yEO7ylHAt9 p09TiO84PT PbOg86I31Y p09TiO84PT UOO1WKebx8 p09TiO84PT jLRLqiMVgB p09TiO84PT jreJYcvwJ4 p09TiO84PT mlRbIMj46E p09TiO84PT nf7dTNtSdc p09TiO84PT r4sCRIIjjE p09TiO84PT tGYPXVsQvI p09TiO84PT trzVFzFzfU p09TiO84PT vAjTAP480q p09TiO84PT yRlT8Jv7q3 p89CV4NSde N0jKYtNamt p89CV4NSde RXw2k2V7yr p89CV4NSde rDtYekG2Rb p89CV4NSde reE5PfMqhg p89CV4NSde sPevBnM8lL pDp32ye8dZ BVrFmcSl1r pDp32ye8dZ FGDogF5C8O pDp32ye8dZ FxS3sBJA2Y pDp32ye8dZ Q7equjW9kG pDp32ye8dZ qT3Il3N0Cg pDp32ye8dZ sjbt7X7rss pGo7sdz80r IxJ0H1khHP pGo7sdz80r JIAU1JIHpJ pGo7sdz80r Ohh0A7h9Lb pGo7sdz80r SwxZE8dapQ pGo7sdz80r TMEwmzXwHX pGo7sdz80r Vp94QEZInp pGo7sdz80r bHjtTXmzMn pGo7sdz80r dQ9g9Axvcf pGo7sdz80r fD6g3ZeOQ1 pGo7sdz80r g3MqlsjtAp pGo7sdz80r i1SqlxFZ39 pGo7sdz80r iXgFa9e2aY pGo7sdz80r itLVbe0zBv pGo7sdz80r jzYrwL8zIr pGo7sdz80r kzxPEcveyR pGo7sdz80r nLYXHof79N pGo7sdz80r rOtv3qBWnW pGo7sdz80r sLJoKUc1Fv pGo7sdz80r wjZgToLdJm pLC117Kew6 A5SqXiE5RK pLC117Kew6 BnPab1zOQN pLC117Kew6 GF8t7wHqux pLC117Kew6 P0vwCQDW6m pLC117Kew6 PcHGX7DMYO pLC117Kew6 QOftVKwfC2 pLC117Kew6 fbLDZ9q3iH pMTjj8TguG HCOpVDLF8S pMTjj8TguG QcttPAc76l pMTjj8TguG Rzz4iaLvJ7 pMTjj8TguG YMcnYwVdY5 pMTjj8TguG ZyjOEhlFQH pMTjj8TguG fjWTJ6hu4c pMTjj8TguG g3iapDoSzG pMTjj8TguG xkzOvzedbB pXvezpQ7XS GiFzzqNblC pXvezpQ7XS OXNteCuFGm pXvezpQ7XS UYMtwhVMNr pXvezpQ7XS YeRS8002jm pXvezpQ7XS nrHzxDHavt pXvezpQ7XS qVIxalGY9L pXvezpQ7XS yIzUYqLM7u pbsWMSurgG A89PdGxmGI pbsWMSurgG BFvFM3RWTa pbsWMSurgG KXxZfSdUoy pbsWMSurgG PVpkYC8Vhk pbsWMSurgG StlgRkbU88 pbsWMSurgG T4vS2lsKkv pbsWMSurgG TdbAAnj7IR pbsWMSurgG VBeFh2joiE pbsWMSurgG YfM5zyFoHF pbsWMSurgG h9090xBRnh pbsWMSurgG iPZX9ImURe pbsWMSurgG kzMilPvDzg pbsWMSurgG m8xQ57130w pbsWMSurgG mNJUSI0TTF pbsWMSurgG mXTIjIMUdT pbsWMSurgG qX3l34l9vg pbsWMSurgG r6JciJbeWh pbsWMSurgG rtVFNPW6Yb pbsWMSurgG zlUF7FRkOG pdsWKEPXcD HHYyhAROeQ pdsWKEPXcD IoNI0rkmkW pdsWKEPXcD Pzjk79OhbQ pdsWKEPXcD QYlF02nPq8 pdsWKEPXcD RIi7H4zCvN pdsWKEPXcD WhX0FgFcFC pdsWKEPXcD jFTc1eDTky pdsWKEPXcD oOrogjFpK6 pdsWKEPXcD rNr0H6bfQ5 plonl31IDU GgFF2EOc7i plonl31IDU IaHaICPcV1 plonl31IDU IfvxXkNlLu plonl31IDU OciZEaDOPd plonl31IDU S1fBp1z8tl plonl31IDU a8OO37gvE2 plonl31IDU ed8GQOp6Lo plonl31IDU epFMnW3Nn8 plonl31IDU fmCwGLjhRu plonl31IDU gamVSiwqjr plonl31IDU kpdFumdStg plonl31IDU l6DiTMZ6Y1 plonl31IDU n5IggTR5tj plonl31IDU qn2DhjefKe plyHE0u0du DtnQIxAWuf plyHE0u0du edPbsjTBsb plyHE0u0du kRBtPgk5QZ plyHE0u0du nuepnjXhMY plyHE0u0du nxGyzU2KfB plyHE0u0du v4Yk1jEqxy plyHE0u0du vgJtiHXQ6K plyHE0u0du xPdsAb32ao plyHE0u0du yj9yczPW9o plyHE0u0du zhtbMk1QlU poreM4rgZB B1c3NQQs27 poreM4rgZB CFrJfSankS poreM4rgZB HFS3yWJkRY poreM4rgZB Js1CmTFK4I poreM4rgZB Kj20pAyTmb poreM4rgZB LP5PM4fEPd poreM4rgZB MC1i7asSAF poreM4rgZB NfN7fKRqmr poreM4rgZB OZ6YsCupAP poreM4rgZB ajxV9jwt8z poreM4rgZB avg1XJ13Zr poreM4rgZB eZZxdI9HOD poreM4rgZB j6TSAtVjgt poreM4rgZB ku3RU4kQrJ poreM4rgZB zCqBU4Ta1g prO1QYA3Lk CT94OMR0el prO1QYA3Lk auXTz19oRf prO1QYA3Lk h5PPMDGh9k prO1QYA3Lk snOQIeEGLZ prO1QYA3Lk t1QFOCIFgP prO1QYA3Lk xfxVXqIVeu pvyeavNWzt AEKgzNBgLp pvyeavNWzt CL7InjadLY pvyeavNWzt DLCemPdUa2 pvyeavNWzt KSnU0hIItN pvyeavNWzt MZJO1jWrVr pvyeavNWzt OkDkTePqup pvyeavNWzt QBaeM75Es5 pvyeavNWzt RIi7H4zCvN pvyeavNWzt STDKogvNpb pvyeavNWzt cih0AH9lyP pvyeavNWzt dyZoZYnzvi pvyeavNWzt gamVSiwqjr pvyeavNWzt glnwWdW8Ri pvyeavNWzt hzYEQ4SmM7 pvyeavNWzt i00FUjOQxx pvyeavNWzt i8YnD3sMqM pvyeavNWzt no0A0GfJ5P pvyeavNWzt nyAZwNUZqR pvyeavNWzt odIh2ap0Te pvyeavNWzt t6uwSRC0Ba q8XaJaEHpz COW3QMJ85P q8XaJaEHpz KL6BLASGwb q8XaJaEHpz N0jKYtNamt q8XaJaEHpz NrvLRILdiu q8XaJaEHpz TrOrQJuYRl q8XaJaEHpz auXTz19oRf q8XaJaEHpz bAKSD1xNgl q8XaJaEHpz bk1hOFMxpV q8XaJaEHpz j2XRj0AcEF q8XaJaEHpz l8efBc8fhD q8XaJaEHpz lzZNPnevUM q8XaJaEHpz oL6jG5HkJV q8XaJaEHpz rUpyqd6ACn q8XaJaEHpz uf6s6hkKhz q8XaJaEHpz xt1v639ujY qEmmw8pgBY GDC2l4Qter qEmmw8pgBY IYKO1wH0kv qEmmw8pgBY IfhyOroLcw qEmmw8pgBY JGSZw135s1 qEmmw8pgBY QWqMOgywY3 qEmmw8pgBY SJJP4L8Mgj qEmmw8pgBY XcXj23HQ38 qEmmw8pgBY bRhTeE5ynI qEmmw8pgBY bpymfnpvaz qEmmw8pgBY dIppWC7kET qEmmw8pgBY nSGQtlVZxb qEmmw8pgBY oS2R0cZHY7 qEmmw8pgBY tIXMeS0Ofn qEmmw8pgBY uTM8CnyXtM qI1Uu600tE DSEAKInMON qI1Uu600tE FgtPqv5u6X qI1Uu600tE TdhrBfhKA4 qI1Uu600tE VY0YXayFZy qI1Uu600tE WwQrPLos2H qI1Uu600tE dg9My3b63y qI1Uu600tE dozVsfXm9d qI1Uu600tE ileXvC9I0m qI1Uu600tE mIkGeVJqMH qI1Uu600tE qUu6MtWEti qI1Uu600tE vJq2Lg7NqP qI1Uu600tE vfjOHm8unN qI1Uu600tE xCwj0abqdX qQXEOrDWg5 Q8HrJbjuvO qQXEOrDWg5 QvBrDnbW5p qQXEOrDWg5 SUQX13ah2K qQXEOrDWg5 UZern2cwbM qQXEOrDWg5 Uge5ENcjA2 qQXEOrDWg5 bHtjkkHFmg qQXEOrDWg5 eJkQkNe3fI qQXEOrDWg5 f8QTHIXoZL qQXEOrDWg5 g3iapDoSzG qQXEOrDWg5 iBVBTY5BMg qQXEOrDWg5 yjDhgXRv3q qT3Il3N0Cg BVrFmcSl1r qT3Il3N0Cg FGDogF5C8O qT3Il3N0Cg Q7equjW9kG qT3Il3N0Cg Rzz4iaLvJ7 qT3Il3N0Cg pDp32ye8dZ qT3Il3N0Cg sjbt7X7rss qUu6MtWEti DSEAKInMON qUu6MtWEti FgtPqv5u6X qUu6MtWEti TdhrBfhKA4 qUu6MtWEti VY0YXayFZy qUu6MtWEti WwQrPLos2H qUu6MtWEti dg9My3b63y qUu6MtWEti dozVsfXm9d qUu6MtWEti ileXvC9I0m qUu6MtWEti mIkGeVJqMH qUu6MtWEti qI1Uu600tE qUu6MtWEti vJq2Lg7NqP qUu6MtWEti vfjOHm8unN qUu6MtWEti xCwj0abqdX qVIxalGY9L GiFzzqNblC qVIxalGY9L OXNteCuFGm qVIxalGY9L UYMtwhVMNr qVIxalGY9L YeRS8002jm qVIxalGY9L nrHzxDHavt qVIxalGY9L pXvezpQ7XS qVIxalGY9L yIzUYqLM7u qX3l34l9vg A89PdGxmGI qX3l34l9vg BFvFM3RWTa qX3l34l9vg KXxZfSdUoy qX3l34l9vg PVpkYC8Vhk qX3l34l9vg StlgRkbU88 qX3l34l9vg T4vS2lsKkv qX3l34l9vg TdbAAnj7IR qX3l34l9vg VBeFh2joiE qX3l34l9vg YfM5zyFoHF qX3l34l9vg h9090xBRnh qX3l34l9vg iPZX9ImURe qX3l34l9vg kzMilPvDzg qX3l34l9vg m8xQ57130w qX3l34l9vg mNJUSI0TTF qX3l34l9vg mXTIjIMUdT qX3l34l9vg pbsWMSurgG qX3l34l9vg r6JciJbeWh qX3l34l9vg rtVFNPW6Yb qX3l34l9vg shR39TKy6b qX3l34l9vg zlUF7FRkOG qn2DhjefKe GgFF2EOc7i qn2DhjefKe IaHaICPcV1 qn2DhjefKe IfvxXkNlLu qn2DhjefKe OciZEaDOPd qn2DhjefKe S1fBp1z8tl qn2DhjefKe a8OO37gvE2 qn2DhjefKe ed8GQOp6Lo qn2DhjefKe epFMnW3Nn8 qn2DhjefKe fmCwGLjhRu qn2DhjefKe kpdFumdStg qn2DhjefKe l6DiTMZ6Y1 qn2DhjefKe n5IggTR5tj qn2DhjefKe plonl31IDU qrCt5OycXI LV67y8AOJY qrCt5OycXI SW1Py0cYFg qrCt5OycXI VzzvL9hhsp qrCt5OycXI adyvbKMw2p qrCt5OycXI daSo9v7Y6S qrCt5OycXI f9LXtwOwjX qrCt5OycXI vZvVMwIHhe qxMdGfMei3 DYahKpmvmE qxMdGfMei3 Q5gCM7jKEq qxMdGfMei3 wBowW79rK5 qxMdGfMei3 zOsDATVDjk qz1mi8Zoju CYewf1rn1r qz1mi8Zoju N646MBv0Yq qz1mi8Zoju aKuuvQuyeK qz1mi8Zoju cs2tTcKbyD qz1mi8Zoju dc2HNm7Ug1 qz1mi8Zoju iDifCMJxDo qz1mi8Zoju kfMZhQCnm7 qz1mi8Zoju nMfKZ3OhnK qz1mi8Zoju y4069LW6DD qzSsPB6mfy NTh5zY76FH qzSsPB6mfy RWIbIcuvf4 qzSsPB6mfy a6ZJ2OpfvC qzSsPB6mfy uivgUSXTDP qzSsPB6mfy yucEolJLVv r4sCRIIjjE FxRrbPlLBc r4sCRIIjjE PbOg86I31Y r4sCRIIjjE UOO1WKebx8 r4sCRIIjjE jLRLqiMVgB r4sCRIIjjE jreJYcvwJ4 r4sCRIIjjE mlRbIMj46E r4sCRIIjjE nf7dTNtSdc r4sCRIIjjE p09TiO84PT r4sCRIIjjE tGYPXVsQvI r4sCRIIjjE trzVFzFzfU r4sCRIIjjE vAjTAP480q r4sCRIIjjE yRlT8Jv7q3 r5asrtuIGP ALlRYyU1EZ r5asrtuIGP B6n7gHBJv4 r5asrtuIGP HXULiM5bHi r5asrtuIGP LKccFxxrwL r5asrtuIGP Lgr2XHIkc5 r5asrtuIGP Q5gCM7jKEq r5asrtuIGP StISRXUIrc r5asrtuIGP b5rNzRWDtz r5asrtuIGP nUgZ8bkR9P r5asrtuIGP uivgUSXTDP r5asrtuIGP yucEolJLVv r5asrtuIGP zAr9xJc4ZO r6JciJbeWh A89PdGxmGI r6JciJbeWh BFvFM3RWTa r6JciJbeWh KXxZfSdUoy r6JciJbeWh PVpkYC8Vhk r6JciJbeWh StlgRkbU88 r6JciJbeWh T4vS2lsKkv r6JciJbeWh TdbAAnj7IR r6JciJbeWh VBeFh2joiE r6JciJbeWh YfM5zyFoHF r6JciJbeWh h9090xBRnh r6JciJbeWh iPZX9ImURe r6JciJbeWh kzMilPvDzg r6JciJbeWh m8xQ57130w r6JciJbeWh mNJUSI0TTF r6JciJbeWh mXTIjIMUdT r6JciJbeWh pbsWMSurgG r6JciJbeWh qX3l34l9vg r6JciJbeWh rtVFNPW6Yb r6JciJbeWh sPevBnM8lL r6JciJbeWh zlUF7FRkOG rDtYekG2Rb N0jKYtNamt rDtYekG2Rb RXw2k2V7yr rDtYekG2Rb p89CV4NSde rDtYekG2Rb reE5PfMqhg rDtYekG2Rb sPevBnM8lL rNr0H6bfQ5 HHYyhAROeQ rNr0H6bfQ5 IoNI0rkmkW rNr0H6bfQ5 Pzjk79OhbQ rNr0H6bfQ5 QYlF02nPq8 rNr0H6bfQ5 RIi7H4zCvN rNr0H6bfQ5 WhX0FgFcFC rNr0H6bfQ5 jFTc1eDTky rNr0H6bfQ5 oOrogjFpK6 rNr0H6bfQ5 pdsWKEPXcD rOtv3qBWnW IxJ0H1khHP rOtv3qBWnW JIAU1JIHpJ rOtv3qBWnW Ohh0A7h9Lb rOtv3qBWnW SwxZE8dapQ rOtv3qBWnW TMEwmzXwHX rOtv3qBWnW Vp94QEZInp rOtv3qBWnW bHjtTXmzMn rOtv3qBWnW dQ9g9Axvcf rOtv3qBWnW fD6g3ZeOQ1 rOtv3qBWnW g3MqlsjtAp rOtv3qBWnW i1SqlxFZ39 rOtv3qBWnW iXgFa9e2aY rOtv3qBWnW itLVbe0zBv rOtv3qBWnW jzYrwL8zIr rOtv3qBWnW kzxPEcveyR rOtv3qBWnW nLYXHof79N rOtv3qBWnW pGo7sdz80r rOtv3qBWnW sLJoKUc1Fv rOtv3qBWnW wjZgToLdJm rUpyqd6ACn COW3QMJ85P rUpyqd6ACn KL6BLASGwb rUpyqd6ACn NrvLRILdiu rUpyqd6ACn TrOrQJuYRl rUpyqd6ACn auXTz19oRf rUpyqd6ACn bAKSD1xNgl rUpyqd6ACn bk1hOFMxpV rUpyqd6ACn j2XRj0AcEF rUpyqd6ACn l8efBc8fhD rUpyqd6ACn lzZNPnevUM rUpyqd6ACn oL6jG5HkJV rUpyqd6ACn q8XaJaEHpz rUpyqd6ACn uf6s6hkKhz rUpyqd6ACn xt1v639ujY reE5PfMqhg N0jKYtNamt reE5PfMqhg RXw2k2V7yr reE5PfMqhg p89CV4NSde reE5PfMqhg rDtYekG2Rb reE5PfMqhg sPevBnM8lL ro4OmYo2Fl KqpzATMLe1 ro4OmYo2Fl RLDnkOU46i ro4OmYo2Fl T4QHaY3mvu ro4OmYo2Fl UGeImwK7UL ro4OmYo2Fl cqiJYAyZ4N ro4OmYo2Fl gjyJWmHrdc ro4OmYo2Fl mLQdynHm1q ro4OmYo2Fl wq1vjepQ9g ro4OmYo2Fl zO7KxMkWwX rtVFNPW6Yb A89PdGxmGI rtVFNPW6Yb BFvFM3RWTa rtVFNPW6Yb KXxZfSdUoy rtVFNPW6Yb PVpkYC8Vhk rtVFNPW6Yb StlgRkbU88 rtVFNPW6Yb T4vS2lsKkv rtVFNPW6Yb TdbAAnj7IR rtVFNPW6Yb VBeFh2joiE rtVFNPW6Yb YfM5zyFoHF rtVFNPW6Yb h9090xBRnh rtVFNPW6Yb iPZX9ImURe rtVFNPW6Yb kzMilPvDzg rtVFNPW6Yb m8xQ57130w rtVFNPW6Yb mNJUSI0TTF rtVFNPW6Yb mXTIjIMUdT rtVFNPW6Yb pbsWMSurgG rtVFNPW6Yb qX3l34l9vg rtVFNPW6Yb r6JciJbeWh rtVFNPW6Yb zlUF7FRkOG rwiRDya8LR BhOjoVk6Bh rwiRDya8LR JsB8YadXEJ rwiRDya8LR UXV0MBIG9U s4OrzaFL7g ARitgxyo6H s4OrzaFL7g D3qsVMNHYJ s4OrzaFL7g InL4L3gP8B s4OrzaFL7g KKcQ5rtdSl s4OrzaFL7g NeQc8lW94W s4OrzaFL7g QaLVi9lXV2 s4OrzaFL7g WdgaReG7Rl s4OrzaFL7g Z3KCxzBZRg s4OrzaFL7g a9aotIHLQP s4OrzaFL7g bzoLSMmOvF s4OrzaFL7g gkNBwl4RLu s4OrzaFL7g lAuS2zWAPn s4OrzaFL7g lMqHeAkrTb s4OrzaFL7g lgquwFNy5U s4OrzaFL7g md4xbbZN0U s4OrzaFL7g muHUYCloPL s4OrzaFL7g nx1sfwm4YL s4OrzaFL7g sr7yJzlRcM s4OrzaFL7g uj1BqLePr7 s4OrzaFL7g vV6kWC0oCn s4OrzaFL7g wEQA1br8ei s4OrzaFL7g wo1cGQsxG3 sLJoKUc1Fv IxJ0H1khHP sLJoKUc1Fv JIAU1JIHpJ sLJoKUc1Fv Ohh0A7h9Lb sLJoKUc1Fv SwxZE8dapQ sLJoKUc1Fv TMEwmzXwHX sLJoKUc1Fv Vp94QEZInp sLJoKUc1Fv bHjtTXmzMn sLJoKUc1Fv dQ9g9Axvcf sLJoKUc1Fv fD6g3ZeOQ1 sLJoKUc1Fv g3MqlsjtAp sLJoKUc1Fv i1SqlxFZ39 sLJoKUc1Fv iXgFa9e2aY sLJoKUc1Fv itLVbe0zBv sLJoKUc1Fv jzYrwL8zIr sLJoKUc1Fv kzxPEcveyR sLJoKUc1Fv nLYXHof79N sLJoKUc1Fv pGo7sdz80r sLJoKUc1Fv rOtv3qBWnW sLJoKUc1Fv wjZgToLdJm sPevBnM8lL N0jKYtNamt sPevBnM8lL RXw2k2V7yr sPevBnM8lL p89CV4NSde sPevBnM8lL r6JciJbeWh sPevBnM8lL rDtYekG2Rb sPevBnM8lL reE5PfMqhg seClx9Il7d K7PU5OVuQg seClx9Il7d KfwNNX0Wv2 seClx9Il7d MRKNAnBkqw seClx9Il7d N1p0MmEQt2 seClx9Il7d PXldCpcCCr seClx9Il7d R4VgVqyGtv seClx9Il7d RK222QTUpQ seClx9Il7d WhtLWtpJ5C seClx9Il7d WpWMwUAphV seClx9Il7d lcNbrMOErd seClx9Il7d u8bzLFKHM3 shR39TKy6b EcC1w0q8IC shR39TKy6b Me3R0HTEnD shR39TKy6b QzPMOgvbGb shR39TKy6b VsRbfKNIdY shR39TKy6b ZF1pKK1tfE shR39TKy6b nMYnHvEYON shR39TKy6b qX3l34l9vg sjbt7X7rss BVrFmcSl1r sjbt7X7rss FGDogF5C8O sjbt7X7rss Q7equjW9kG sjbt7X7rss pDp32ye8dZ sjbt7X7rss qT3Il3N0Cg snOQIeEGLZ CT94OMR0el snOQIeEGLZ WdgaReG7Rl snOQIeEGLZ h5PPMDGh9k snOQIeEGLZ prO1QYA3Lk snOQIeEGLZ t1QFOCIFgP snOQIeEGLZ xfxVXqIVeu sr7yJzlRcM ARitgxyo6H sr7yJzlRcM D3qsVMNHYJ sr7yJzlRcM InL4L3gP8B sr7yJzlRcM KKcQ5rtdSl sr7yJzlRcM NeQc8lW94W sr7yJzlRcM QaLVi9lXV2 sr7yJzlRcM WdgaReG7Rl sr7yJzlRcM Z3KCxzBZRg sr7yJzlRcM a9aotIHLQP sr7yJzlRcM bzoLSMmOvF sr7yJzlRcM gkNBwl4RLu sr7yJzlRcM lAuS2zWAPn sr7yJzlRcM lMqHeAkrTb sr7yJzlRcM lgquwFNy5U sr7yJzlRcM md4xbbZN0U sr7yJzlRcM muHUYCloPL sr7yJzlRcM nx1sfwm4YL sr7yJzlRcM s4OrzaFL7g sr7yJzlRcM uj1BqLePr7 sr7yJzlRcM vV6kWC0oCn sr7yJzlRcM wEQA1br8ei sr7yJzlRcM wo1cGQsxG3 t1QFOCIFgP CT94OMR0el t1QFOCIFgP h5PPMDGh9k t1QFOCIFgP prO1QYA3Lk t1QFOCIFgP snOQIeEGLZ t1QFOCIFgP xfxVXqIVeu t6uwSRC0Ba AEKgzNBgLp t6uwSRC0Ba CL7InjadLY t6uwSRC0Ba DLCemPdUa2 t6uwSRC0Ba KSnU0hIItN t6uwSRC0Ba MZJO1jWrVr t6uwSRC0Ba OkDkTePqup t6uwSRC0Ba QBaeM75Es5 t6uwSRC0Ba STDKogvNpb t6uwSRC0Ba cih0AH9lyP t6uwSRC0Ba dyZoZYnzvi t6uwSRC0Ba gamVSiwqjr t6uwSRC0Ba glnwWdW8Ri t6uwSRC0Ba hzYEQ4SmM7 t6uwSRC0Ba i00FUjOQxx t6uwSRC0Ba i8YnD3sMqM t6uwSRC0Ba no0A0GfJ5P t6uwSRC0Ba nyAZwNUZqR t6uwSRC0Ba odIh2ap0Te t6uwSRC0Ba pvyeavNWzt tGYPXVsQvI PbOg86I31Y tGYPXVsQvI UOO1WKebx8 tGYPXVsQvI jLRLqiMVgB tGYPXVsQvI jreJYcvwJ4 tGYPXVsQvI mlRbIMj46E tGYPXVsQvI nf7dTNtSdc tGYPXVsQvI p09TiO84PT tGYPXVsQvI r4sCRIIjjE tGYPXVsQvI trzVFzFzfU tGYPXVsQvI vAjTAP480q tGYPXVsQvI yRlT8Jv7q3 tIXMeS0Ofn GDC2l4Qter tIXMeS0Ofn IYKO1wH0kv tIXMeS0Ofn IfhyOroLcw tIXMeS0Ofn JGSZw135s1 tIXMeS0Ofn QWqMOgywY3 tIXMeS0Ofn SJJP4L8Mgj tIXMeS0Ofn UdctjCRplG tIXMeS0Ofn XcXj23HQ38 tIXMeS0Ofn bRhTeE5ynI tIXMeS0Ofn bpymfnpvaz tIXMeS0Ofn dIppWC7kET tIXMeS0Ofn nSGQtlVZxb tIXMeS0Ofn oS2R0cZHY7 tIXMeS0Ofn qEmmw8pgBY tIXMeS0Ofn uTM8CnyXtM tfbkFiMXxB FxRrbPlLBc tfbkFiMXxB KdK442vKVQ tfbkFiMXxB MuvfKsKOds tfbkFiMXxB PJdPFUXV4p tfbkFiMXxB Z0W7FSu3Lv tfbkFiMXxB a95TSSt8eF tfbkFiMXxB dCUAioVIEU tfbkFiMXxB g51m2Ar66X tfbkFiMXxB hWzPVctQys trzVFzFzfU PbOg86I31Y trzVFzFzfU UOO1WKebx8 trzVFzFzfU jLRLqiMVgB trzVFzFzfU jreJYcvwJ4 trzVFzFzfU mlRbIMj46E trzVFzFzfU nf7dTNtSdc trzVFzFzfU p09TiO84PT trzVFzFzfU r4sCRIIjjE trzVFzFzfU tGYPXVsQvI trzVFzFzfU vAjTAP480q trzVFzFzfU yRlT8Jv7q3 u8bzLFKHM3 K7PU5OVuQg u8bzLFKHM3 KfwNNX0Wv2 u8bzLFKHM3 MRKNAnBkqw u8bzLFKHM3 N1p0MmEQt2 u8bzLFKHM3 PXldCpcCCr u8bzLFKHM3 RK222QTUpQ u8bzLFKHM3 WhtLWtpJ5C u8bzLFKHM3 WpWMwUAphV u8bzLFKHM3 lcNbrMOErd u8bzLFKHM3 seClx9Il7d uLBvOgMNp4 CLNKQwna5q uLBvOgMNp4 PG3HRvcYYE uLBvOgMNp4 QUVT7xx0GS uLBvOgMNp4 hfU4rnQr8C uLBvOgMNp4 lNnfKNbVWj uLBvOgMNp4 uMtUrHVik9 uMtUrHVik9 CLNKQwna5q uMtUrHVik9 PG3HRvcYYE uMtUrHVik9 QUVT7xx0GS uMtUrHVik9 hfU4rnQr8C uMtUrHVik9 jLRLqiMVgB uMtUrHVik9 lNnfKNbVWj uMtUrHVik9 uLBvOgMNp4 uTM8CnyXtM GDC2l4Qter uTM8CnyXtM IYKO1wH0kv uTM8CnyXtM IfhyOroLcw uTM8CnyXtM JGSZw135s1 uTM8CnyXtM QWqMOgywY3 uTM8CnyXtM SJJP4L8Mgj uTM8CnyXtM XcXj23HQ38 uTM8CnyXtM bRhTeE5ynI uTM8CnyXtM bpymfnpvaz uTM8CnyXtM dIppWC7kET uTM8CnyXtM nSGQtlVZxb uTM8CnyXtM oS2R0cZHY7 uTM8CnyXtM qEmmw8pgBY uTM8CnyXtM tIXMeS0Ofn uVqqmtdAGv BnPab1zOQN uVqqmtdAGv ZUpFZT5Wky uVqqmtdAGv a6ZJ2OpfvC uVqqmtdAGv gXEbqBTGW0 uVqqmtdAGv mDrSgV98vb uVqqmtdAGv zfmwbQwtk8 uf6s6hkKhz COW3QMJ85P uf6s6hkKhz KL6BLASGwb uf6s6hkKhz NrvLRILdiu uf6s6hkKhz TrOrQJuYRl uf6s6hkKhz auXTz19oRf uf6s6hkKhz bAKSD1xNgl uf6s6hkKhz bk1hOFMxpV uf6s6hkKhz j2XRj0AcEF uf6s6hkKhz l8efBc8fhD uf6s6hkKhz lzZNPnevUM uf6s6hkKhz oL6jG5HkJV uf6s6hkKhz q8XaJaEHpz uf6s6hkKhz rUpyqd6ACn uf6s6hkKhz xt1v639ujY uivgUSXTDP NTh5zY76FH uivgUSXTDP RWIbIcuvf4 uivgUSXTDP a6ZJ2OpfvC uivgUSXTDP qzSsPB6mfy uivgUSXTDP r5asrtuIGP uj1BqLePr7 ARitgxyo6H uj1BqLePr7 D3qsVMNHYJ uj1BqLePr7 InL4L3gP8B uj1BqLePr7 KKcQ5rtdSl uj1BqLePr7 NeQc8lW94W uj1BqLePr7 QaLVi9lXV2 uj1BqLePr7 WdgaReG7Rl uj1BqLePr7 Z3KCxzBZRg uj1BqLePr7 a9aotIHLQP uj1BqLePr7 bzoLSMmOvF uj1BqLePr7 gkNBwl4RLu uj1BqLePr7 lAuS2zWAPn uj1BqLePr7 lMqHeAkrTb uj1BqLePr7 lgquwFNy5U uj1BqLePr7 md4xbbZN0U uj1BqLePr7 muHUYCloPL uj1BqLePr7 nx1sfwm4YL uj1BqLePr7 s4OrzaFL7g uj1BqLePr7 sr7yJzlRcM uj1BqLePr7 vV6kWC0oCn uj1BqLePr7 wEQA1br8ei uj1BqLePr7 wo1cGQsxG3 v4Yk1jEqxy edPbsjTBsb v4Yk1jEqxy kRBtPgk5QZ v4Yk1jEqxy nuepnjXhMY v4Yk1jEqxy nxGyzU2KfB v4Yk1jEqxy plyHE0u0du v4Yk1jEqxy vgJtiHXQ6K v4Yk1jEqxy xPdsAb32ao v4Yk1jEqxy yj9yczPW9o v4Yk1jEqxy zhtbMk1QlU vAjTAP480q PbOg86I31Y vAjTAP480q UOO1WKebx8 vAjTAP480q jLRLqiMVgB vAjTAP480q jreJYcvwJ4 vAjTAP480q mlRbIMj46E vAjTAP480q nf7dTNtSdc vAjTAP480q p09TiO84PT vAjTAP480q r4sCRIIjjE vAjTAP480q tGYPXVsQvI vAjTAP480q trzVFzFzfU vAjTAP480q yRlT8Jv7q3 vJq2Lg7NqP DSEAKInMON vJq2Lg7NqP FgtPqv5u6X vJq2Lg7NqP TdhrBfhKA4 vJq2Lg7NqP VY0YXayFZy vJq2Lg7NqP WwQrPLos2H vJq2Lg7NqP dg9My3b63y vJq2Lg7NqP dozVsfXm9d vJq2Lg7NqP ileXvC9I0m vJq2Lg7NqP mIkGeVJqMH vJq2Lg7NqP qI1Uu600tE vJq2Lg7NqP qUu6MtWEti vJq2Lg7NqP vfjOHm8unN vJq2Lg7NqP xCwj0abqdX vV6kWC0oCn ARitgxyo6H vV6kWC0oCn D3qsVMNHYJ vV6kWC0oCn InL4L3gP8B vV6kWC0oCn KKcQ5rtdSl vV6kWC0oCn NeQc8lW94W vV6kWC0oCn QaLVi9lXV2 vV6kWC0oCn WdgaReG7Rl vV6kWC0oCn Z3KCxzBZRg vV6kWC0oCn a9aotIHLQP vV6kWC0oCn bzoLSMmOvF vV6kWC0oCn gkNBwl4RLu vV6kWC0oCn lAuS2zWAPn vV6kWC0oCn lMqHeAkrTb vV6kWC0oCn lgquwFNy5U vV6kWC0oCn md4xbbZN0U vV6kWC0oCn muHUYCloPL vV6kWC0oCn nx1sfwm4YL vV6kWC0oCn s4OrzaFL7g vV6kWC0oCn sr7yJzlRcM vV6kWC0oCn uj1BqLePr7 vV6kWC0oCn wEQA1br8ei vV6kWC0oCn wo1cGQsxG3 vZvVMwIHhe LV67y8AOJY vZvVMwIHhe SW1Py0cYFg vZvVMwIHhe VBeFh2joiE vZvVMwIHhe VzzvL9hhsp vZvVMwIHhe daSo9v7Y6S vZvVMwIHhe f9LXtwOwjX vZvVMwIHhe qrCt5OycXI vfjOHm8unN DSEAKInMON vfjOHm8unN FgtPqv5u6X vfjOHm8unN TdhrBfhKA4 vfjOHm8unN VY0YXayFZy vfjOHm8unN WwQrPLos2H vfjOHm8unN dg9My3b63y vfjOHm8unN dozVsfXm9d vfjOHm8unN ileXvC9I0m vfjOHm8unN mIkGeVJqMH vfjOHm8unN qI1Uu600tE vfjOHm8unN qUu6MtWEti vfjOHm8unN vJq2Lg7NqP vfjOHm8unN xCwj0abqdX vgJtiHXQ6K edPbsjTBsb vgJtiHXQ6K kRBtPgk5QZ vgJtiHXQ6K nuepnjXhMY vgJtiHXQ6K nxGyzU2KfB vgJtiHXQ6K plyHE0u0du vgJtiHXQ6K v4Yk1jEqxy vgJtiHXQ6K xPdsAb32ao vgJtiHXQ6K yj9yczPW9o vgJtiHXQ6K zhtbMk1QlU wA2rJOcWwU By3ifr7skR wA2rJOcWwU PWnohCTMMJ wA2rJOcWwU ShPTT2Mg66 wA2rJOcWwU jQk2isP8og wA2rJOcWwU ku3RU4kQrJ wA2rJOcWwU x9YzQ9iiwh wBowW79rK5 DYahKpmvmE wBowW79rK5 qxMdGfMei3 wBowW79rK5 zOsDATVDjk wEQA1br8ei ARitgxyo6H wEQA1br8ei D3qsVMNHYJ wEQA1br8ei InL4L3gP8B wEQA1br8ei KKcQ5rtdSl wEQA1br8ei NeQc8lW94W wEQA1br8ei QaLVi9lXV2 wEQA1br8ei WdgaReG7Rl wEQA1br8ei Z3KCxzBZRg wEQA1br8ei a9aotIHLQP wEQA1br8ei bzoLSMmOvF wEQA1br8ei gkNBwl4RLu wEQA1br8ei lAuS2zWAPn wEQA1br8ei lMqHeAkrTb wEQA1br8ei lgquwFNy5U wEQA1br8ei md4xbbZN0U wEQA1br8ei muHUYCloPL wEQA1br8ei nx1sfwm4YL wEQA1br8ei s4OrzaFL7g wEQA1br8ei sr7yJzlRcM wEQA1br8ei uj1BqLePr7 wEQA1br8ei vV6kWC0oCn wEQA1br8ei wo1cGQsxG3 wMkRLgAmqT DxIAA9ZJ3T wMkRLgAmqT Ga31WHOJqV wMkRLgAmqT JQk2aqVtGs wMkRLgAmqT KifgRzbVyt wMkRLgAmqT Q9eF9yQN0t wMkRLgAmqT S8193nrYCv wMkRLgAmqT UuPwyCgvCP wMkRLgAmqT nYAc8XCqer wMkRLgAmqT oQJaxdHG4N wMkRLgAmqT olBdH0jfPB wMkRLgAmqT xZtfO8qwEC wMkRLgAmqT xgdjqGL33t wMkRLgAmqT yPUgavnkvb wMkRLgAmqT z3HLDgXLGK wMkRLgAmqT z6bXLibpTn wf7yGa9VWa G6M6biLXNb wf7yGa9VWa MsdnkwG8qI wf7yGa9VWa NTh5zY76FH wf7yGa9VWa O2N8IPM9SD wf7yGa9VWa UECu5dEunb wf7yGa9VWa UdctjCRplG wf7yGa9VWa e0jC4Bo2N5 wf7yGa9VWa jAEMuG4nj6 wjZgToLdJm IxJ0H1khHP wjZgToLdJm JIAU1JIHpJ wjZgToLdJm Ohh0A7h9Lb wjZgToLdJm SwxZE8dapQ wjZgToLdJm TMEwmzXwHX wjZgToLdJm Vp94QEZInp wjZgToLdJm bHjtTXmzMn wjZgToLdJm dQ9g9Axvcf wjZgToLdJm fD6g3ZeOQ1 wjZgToLdJm g3MqlsjtAp wjZgToLdJm i1SqlxFZ39 wjZgToLdJm iXgFa9e2aY wjZgToLdJm itLVbe0zBv wjZgToLdJm jzYrwL8zIr wjZgToLdJm kzxPEcveyR wjZgToLdJm nLYXHof79N wjZgToLdJm pGo7sdz80r wjZgToLdJm rOtv3qBWnW wjZgToLdJm sLJoKUc1Fv wl1GfSteFH CspreEn61Q wl1GfSteFH DtnQIxAWuf wl1GfSteFH FxS3sBJA2Y wl1GfSteFH WtW8bhpTmG wl1GfSteFH Yk5M1fW87z wl1GfSteFH hbAACpTBfH wl1GfSteFH oq9uAm8HQ7 wl1GfSteFH orvvutiOIS wl1GfSteFH yCMMplAGar wl1GfSteFH yjLH0H4leh wo1cGQsxG3 ARitgxyo6H wo1cGQsxG3 D3qsVMNHYJ wo1cGQsxG3 InL4L3gP8B wo1cGQsxG3 KKcQ5rtdSl wo1cGQsxG3 NeQc8lW94W wo1cGQsxG3 QaLVi9lXV2 wo1cGQsxG3 WdgaReG7Rl wo1cGQsxG3 Z3KCxzBZRg wo1cGQsxG3 a9aotIHLQP wo1cGQsxG3 bzoLSMmOvF wo1cGQsxG3 gkNBwl4RLu wo1cGQsxG3 lAuS2zWAPn wo1cGQsxG3 lMqHeAkrTb wo1cGQsxG3 lgquwFNy5U wo1cGQsxG3 md4xbbZN0U wo1cGQsxG3 muHUYCloPL wo1cGQsxG3 nx1sfwm4YL wo1cGQsxG3 s4OrzaFL7g wo1cGQsxG3 sr7yJzlRcM wo1cGQsxG3 uj1BqLePr7 wo1cGQsxG3 vV6kWC0oCn wo1cGQsxG3 wEQA1br8ei wq1vjepQ9g KqpzATMLe1 wq1vjepQ9g RLDnkOU46i wq1vjepQ9g T4QHaY3mvu wq1vjepQ9g UGeImwK7UL wq1vjepQ9g cqiJYAyZ4N wq1vjepQ9g gjyJWmHrdc wq1vjepQ9g mLQdynHm1q wq1vjepQ9g ro4OmYo2Fl wq1vjepQ9g zO7KxMkWwX x9YzQ9iiwh By3ifr7skR x9YzQ9iiwh PWnohCTMMJ x9YzQ9iiwh Rf7Jske2mT x9YzQ9iiwh ShPTT2Mg66 x9YzQ9iiwh jQk2isP8og x9YzQ9iiwh wA2rJOcWwU xCwj0abqdX DSEAKInMON xCwj0abqdX FgtPqv5u6X xCwj0abqdX TdhrBfhKA4 xCwj0abqdX VY0YXayFZy xCwj0abqdX WwQrPLos2H xCwj0abqdX dg9My3b63y xCwj0abqdX dozVsfXm9d xCwj0abqdX ileXvC9I0m xCwj0abqdX mIkGeVJqMH xCwj0abqdX qI1Uu600tE xCwj0abqdX qUu6MtWEti xCwj0abqdX vJq2Lg7NqP xCwj0abqdX vfjOHm8unN xPdsAb32ao edPbsjTBsb xPdsAb32ao kRBtPgk5QZ xPdsAb32ao nuepnjXhMY xPdsAb32ao nxGyzU2KfB xPdsAb32ao plyHE0u0du xPdsAb32ao v4Yk1jEqxy xPdsAb32ao vgJtiHXQ6K xPdsAb32ao yj9yczPW9o xPdsAb32ao zhtbMk1QlU xXtkF1hfGE G9EvjeK9x3 xXtkF1hfGE Rf7Jske2mT xZtfO8qwEC DxIAA9ZJ3T xZtfO8qwEC Ga31WHOJqV xZtfO8qwEC JQk2aqVtGs xZtfO8qwEC KifgRzbVyt xZtfO8qwEC Q9eF9yQN0t xZtfO8qwEC S8193nrYCv xZtfO8qwEC UuPwyCgvCP xZtfO8qwEC nYAc8XCqer xZtfO8qwEC oQJaxdHG4N xZtfO8qwEC olBdH0jfPB xZtfO8qwEC wMkRLgAmqT xZtfO8qwEC xgdjqGL33t xZtfO8qwEC yPUgavnkvb xZtfO8qwEC z3HLDgXLGK xZtfO8qwEC z6bXLibpTn xfxVXqIVeu CT94OMR0el xfxVXqIVeu h5PPMDGh9k xfxVXqIVeu prO1QYA3Lk xfxVXqIVeu snOQIeEGLZ xfxVXqIVeu t1QFOCIFgP xgdjqGL33t DxIAA9ZJ3T xgdjqGL33t Ga31WHOJqV xgdjqGL33t JQk2aqVtGs xgdjqGL33t KifgRzbVyt xgdjqGL33t Q9eF9yQN0t xgdjqGL33t S8193nrYCv xgdjqGL33t UuPwyCgvCP xgdjqGL33t nYAc8XCqer xgdjqGL33t oQJaxdHG4N xgdjqGL33t olBdH0jfPB xgdjqGL33t wMkRLgAmqT xgdjqGL33t xZtfO8qwEC xgdjqGL33t yPUgavnkvb xgdjqGL33t z3HLDgXLGK xgdjqGL33t z6bXLibpTn xkzOvzedbB HCOpVDLF8S xkzOvzedbB QcttPAc76l xkzOvzedbB Rzz4iaLvJ7 xkzOvzedbB YMcnYwVdY5 xkzOvzedbB ZyjOEhlFQH xkzOvzedbB fjWTJ6hu4c xkzOvzedbB pMTjj8TguG xkzOvzedbB zO7KxMkWwX xt1v639ujY COW3QMJ85P xt1v639ujY KL6BLASGwb xt1v639ujY NrvLRILdiu xt1v639ujY TrOrQJuYRl xt1v639ujY auXTz19oRf xt1v639ujY bAKSD1xNgl xt1v639ujY bk1hOFMxpV xt1v639ujY j2XRj0AcEF xt1v639ujY l8efBc8fhD xt1v639ujY lzZNPnevUM xt1v639ujY oL6jG5HkJV xt1v639ujY q8XaJaEHpz xt1v639ujY rUpyqd6ACn xt1v639ujY uf6s6hkKhz y4069LW6DD CYewf1rn1r y4069LW6DD N646MBv0Yq y4069LW6DD aKuuvQuyeK y4069LW6DD cs2tTcKbyD y4069LW6DD dc2HNm7Ug1 y4069LW6DD iDifCMJxDo y4069LW6DD kfMZhQCnm7 y4069LW6DD nMfKZ3OhnK y4069LW6DD qz1mi8Zoju yCMMplAGar CspreEn61Q yCMMplAGar DtnQIxAWuf yCMMplAGar FxS3sBJA2Y yCMMplAGar WtW8bhpTmG yCMMplAGar Yk5M1fW87z yCMMplAGar hbAACpTBfH yCMMplAGar oq9uAm8HQ7 yCMMplAGar orvvutiOIS yCMMplAGar wl1GfSteFH yCMMplAGar yjLH0H4leh yEO7ylHAt9 A2bNee4LMs yEO7ylHAt9 BT3jNizL5n yEO7ylHAt9 FTI9Shcypv yEO7ylHAt9 U5BoY6tv9F yEO7ylHAt9 aWxJ1IXGt6 yEO7ylHAt9 os8C1a0ovg yEO7ylHAt9 yjDhgXRv3q yIzUYqLM7u BT3jNizL5n yIzUYqLM7u GiFzzqNblC yIzUYqLM7u OXNteCuFGm yIzUYqLM7u UYMtwhVMNr yIzUYqLM7u YeRS8002jm yIzUYqLM7u nrHzxDHavt yIzUYqLM7u pXvezpQ7XS yIzUYqLM7u qVIxalGY9L yPUgavnkvb DxIAA9ZJ3T yPUgavnkvb Ga31WHOJqV yPUgavnkvb JQk2aqVtGs yPUgavnkvb KifgRzbVyt yPUgavnkvb Q9eF9yQN0t yPUgavnkvb S8193nrYCv yPUgavnkvb UuPwyCgvCP yPUgavnkvb nYAc8XCqer yPUgavnkvb oQJaxdHG4N yPUgavnkvb olBdH0jfPB yPUgavnkvb wMkRLgAmqT yPUgavnkvb xZtfO8qwEC yPUgavnkvb xgdjqGL33t yPUgavnkvb z3HLDgXLGK yPUgavnkvb z6bXLibpTn yRlT8Jv7q3 PbOg86I31Y yRlT8Jv7q3 UOO1WKebx8 yRlT8Jv7q3 jLRLqiMVgB yRlT8Jv7q3 jreJYcvwJ4 yRlT8Jv7q3 mlRbIMj46E yRlT8Jv7q3 nf7dTNtSdc yRlT8Jv7q3 p09TiO84PT yRlT8Jv7q3 r4sCRIIjjE yRlT8Jv7q3 tGYPXVsQvI yRlT8Jv7q3 trzVFzFzfU yRlT8Jv7q3 vAjTAP480q yj9yczPW9o edPbsjTBsb yj9yczPW9o kRBtPgk5QZ yj9yczPW9o nuepnjXhMY yj9yczPW9o nxGyzU2KfB yj9yczPW9o plyHE0u0du yj9yczPW9o v4Yk1jEqxy yj9yczPW9o vgJtiHXQ6K yj9yczPW9o xPdsAb32ao yj9yczPW9o zhtbMk1QlU yjDhgXRv3q Q8HrJbjuvO yjDhgXRv3q QvBrDnbW5p yjDhgXRv3q SUQX13ah2K yjDhgXRv3q UZern2cwbM yjDhgXRv3q Uge5ENcjA2 yjDhgXRv3q bHtjkkHFmg yjDhgXRv3q eJkQkNe3fI yjDhgXRv3q f8QTHIXoZL yjDhgXRv3q g3iapDoSzG yjDhgXRv3q iBVBTY5BMg yjDhgXRv3q qQXEOrDWg5 yjDhgXRv3q yEO7ylHAt9 yjLH0H4leh CspreEn61Q yjLH0H4leh DtnQIxAWuf yjLH0H4leh FxS3sBJA2Y yjLH0H4leh WtW8bhpTmG yjLH0H4leh Yk5M1fW87z yjLH0H4leh hbAACpTBfH yjLH0H4leh oq9uAm8HQ7 yjLH0H4leh orvvutiOIS yjLH0H4leh wl1GfSteFH yjLH0H4leh yCMMplAGar yucEolJLVv ALlRYyU1EZ yucEolJLVv B6n7gHBJv4 yucEolJLVv HXULiM5bHi yucEolJLVv LKccFxxrwL yucEolJLVv Lgr2XHIkc5 yucEolJLVv Q5gCM7jKEq yucEolJLVv StISRXUIrc yucEolJLVv b5rNzRWDtz yucEolJLVv nUgZ8bkR9P yucEolJLVv qzSsPB6mfy yucEolJLVv r5asrtuIGP yucEolJLVv zAr9xJc4ZO z3HLDgXLGK DxIAA9ZJ3T z3HLDgXLGK Ga31WHOJqV z3HLDgXLGK JQk2aqVtGs z3HLDgXLGK KifgRzbVyt z3HLDgXLGK Q9eF9yQN0t z3HLDgXLGK S8193nrYCv z3HLDgXLGK UuPwyCgvCP z3HLDgXLGK nYAc8XCqer z3HLDgXLGK oQJaxdHG4N z3HLDgXLGK olBdH0jfPB z3HLDgXLGK wMkRLgAmqT z3HLDgXLGK xZtfO8qwEC z3HLDgXLGK xgdjqGL33t z3HLDgXLGK yPUgavnkvb z3HLDgXLGK z6bXLibpTn z6bXLibpTn DxIAA9ZJ3T z6bXLibpTn Ga31WHOJqV z6bXLibpTn JQk2aqVtGs z6bXLibpTn KifgRzbVyt z6bXLibpTn Q9eF9yQN0t z6bXLibpTn S8193nrYCv z6bXLibpTn UuPwyCgvCP z6bXLibpTn nYAc8XCqer z6bXLibpTn oQJaxdHG4N z6bXLibpTn olBdH0jfPB z6bXLibpTn wMkRLgAmqT z6bXLibpTn xZtfO8qwEC z6bXLibpTn xgdjqGL33t z6bXLibpTn yPUgavnkvb z6bXLibpTn z3HLDgXLGK zAr9xJc4ZO ALlRYyU1EZ zAr9xJc4ZO B6n7gHBJv4 zAr9xJc4ZO HXULiM5bHi zAr9xJc4ZO LKccFxxrwL zAr9xJc4ZO Lgr2XHIkc5 zAr9xJc4ZO Q5gCM7jKEq zAr9xJc4ZO StISRXUIrc zAr9xJc4ZO b5rNzRWDtz zAr9xJc4ZO nUgZ8bkR9P zAr9xJc4ZO r5asrtuIGP zAr9xJc4ZO yucEolJLVv zCqBU4Ta1g B1c3NQQs27 zCqBU4Ta1g CFrJfSankS zCqBU4Ta1g HFS3yWJkRY zCqBU4Ta1g Js1CmTFK4I zCqBU4Ta1g Kj20pAyTmb zCqBU4Ta1g LP5PM4fEPd zCqBU4Ta1g MC1i7asSAF zCqBU4Ta1g NfN7fKRqmr zCqBU4Ta1g OZ6YsCupAP zCqBU4Ta1g ajxV9jwt8z zCqBU4Ta1g avg1XJ13Zr zCqBU4Ta1g eZZxdI9HOD zCqBU4Ta1g j6TSAtVjgt zCqBU4Ta1g ku3RU4kQrJ zCqBU4Ta1g poreM4rgZB zO7KxMkWwX KqpzATMLe1 zO7KxMkWwX RLDnkOU46i zO7KxMkWwX T4QHaY3mvu zO7KxMkWwX UGeImwK7UL zO7KxMkWwX cqiJYAyZ4N zO7KxMkWwX gjyJWmHrdc zO7KxMkWwX mLQdynHm1q zO7KxMkWwX ro4OmYo2Fl zO7KxMkWwX wq1vjepQ9g zO7KxMkWwX xkzOvzedbB zOsDATVDjk DYahKpmvmE zOsDATVDjk qxMdGfMei3 zOsDATVDjk wBowW79rK5 zfmwbQwtk8 BnPab1zOQN zfmwbQwtk8 ZUpFZT5Wky zfmwbQwtk8 gXEbqBTGW0 zfmwbQwtk8 mDrSgV98vb zfmwbQwtk8 uVqqmtdAGv zhtbMk1QlU RLDnkOU46i zhtbMk1QlU edPbsjTBsb zhtbMk1QlU kRBtPgk5QZ zhtbMk1QlU nuepnjXhMY zhtbMk1QlU nxGyzU2KfB zhtbMk1QlU plyHE0u0du zhtbMk1QlU v4Yk1jEqxy zhtbMk1QlU vgJtiHXQ6K zhtbMk1QlU xPdsAb32ao zhtbMk1QlU yj9yczPW9o zlUF7FRkOG A89PdGxmGI zlUF7FRkOG BFvFM3RWTa zlUF7FRkOG KXxZfSdUoy zlUF7FRkOG PVpkYC8Vhk zlUF7FRkOG StlgRkbU88 zlUF7FRkOG T4vS2lsKkv zlUF7FRkOG TdbAAnj7IR zlUF7FRkOG VBeFh2joiE zlUF7FRkOG YfM5zyFoHF zlUF7FRkOG h9090xBRnh zlUF7FRkOG iPZX9ImURe zlUF7FRkOG kzMilPvDzg zlUF7FRkOG m8xQ57130w zlUF7FRkOG mNJUSI0TTF zlUF7FRkOG mXTIjIMUdT zlUF7FRkOG pbsWMSurgG zlUF7FRkOG qX3l34l9vg zlUF7FRkOG r6JciJbeWh zlUF7FRkOG rtVFNPW6Yb Graph-0.9735/t/01_isa.t0000644000175000017500000000022413774262024014321 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 2; use Graph; my $g = Graph->new; isa_ok($g, 'Graph'); my $h = $g->new; isa_ok($g, 'Graph'); Graph-0.9735/t/76_attribute_hash.t0000644000175000017500000000071713774262024016576 0ustar osboxesosboxesuse Test::More tests => 9; package Hash; use Graph::Attribute hash => _A; sub new { bless {}, shift } package main; use strict; use warnings; my $o = Hash->new(); ok(!$o->_g_has_attributes()); is(my $a = $o->_g_get_attributes(), undef); ok($o->_g_set_attributes({foo => 42})); ok($o->_g_has_attributes()); ok($a = $o->_g_get_attributes()); is($a->{foo}, 42); ok($o->_g_delete_attributes()); ok(!$o->_g_has_attributes()); is($a = $o->_g_get_attributes(), undef); Graph-0.9735/t/75_attribute_array.t0000644000175000017500000000072013774262024016762 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 9; package Array; use Graph::Attribute array => 0; sub new { bless [], shift } package main; my $o = Array->new(); ok(!$o->_g_has_attributes()); is(my $a = $o->_g_get_attributes(), undef); ok($o->_g_set_attributes({foo => 42})); ok($o->_g_has_attributes()); ok($a = $o->_g_get_attributes()); is($a->{foo}, 42); ok($o->_g_delete_attributes()); ok(!$o->_g_has_attributes()); is($a = $o->_g_get_attributes(), undef); Graph-0.9735/t/87_planar.t0000644000175000017500000000175714741033474015054 0ustar osboxesosboxesuse strict; use warnings; use Graph::Undirected; use Test::More; my $g0 = Graph::Undirected->new; $g0->add_path('A'..'Z'); ok($g0->is_planar); my $g1 = Graph::Undirected->new; $g1->add_cycle('A'..'C'); ok($g1->is_planar); my $g2 = Graph::Undirected->new; $g2->add_cycle('A'..'D'); ok($g2->is_planar); my $g3 = Graph::Undirected->new; for my $A ('A'..'Z') { for my $B ('1'..'9') { $g3->add_edge($A, $B); } } ok(!$g3->is_planar); my $g4 = Graph::Undirected->new; $g4->add_cycle('A'..'C'); $g4->add_cycle('1'..'6'); ok($g4->is_planar); my $g5 = Graph::Undirected->new; $g5->add_cycle('A'..'D'); $g5->add_cycle('1'..'6'); ok($g5->is_planar); my $g6 = Graph::Undirected->new; # K5 for my $A ('A'..'E') { for my $B ('A'..'E') { next if $A eq $B; $g6->add_edge( $A, $B ); } } ok(!$g6->is_planar); my $g7 = Graph::Undirected->new; # K3,3 for my $A ('A'..'C') { for my $B ('1'..'3') { $g7->add_edge( $A, $B ); } } ok(!$g7->is_planar); done_testing; Graph-0.9735/t/u_rb_cc.t0000644000175000017500000000044413774262024014645 0ustar osboxesosboxesuse Graph::Undirected; use strict; use warnings; use Test::More tests => 2; my $mg = new Graph::Undirected; $mg->add_edges(qw(C6 H6A S1 C6)); is($mg->connected_component_by_vertex("S1"), 0); my @cc0 = $mg->connected_component_by_index(0); my %cc0; @cc0{ @cc0 } = (); ok(exists $cc0{ S1 }); Graph-0.9735/t/u_mn_va.t0000644000175000017500000000060613774262024014675 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 3; use Graph::Undirected; my $G = Graph::Undirected->new; $G->add_vertex('a'); $G->set_vertex_attribute('a', 'sim', 0); $G->add_vertex('b'); $G->set_vertex_attribute('b', 'sim', 1); $G->add_edge('a', 'b'); $G->delete_vertex('a'); ok(! $G->has_vertex('a') ); my @V = $G->vertices; is(scalar @V, 1); my %V; @V{ @V } = (); ok(exists $V{b}); Graph-0.9735/t/11_vertices.t0000644000175000017500000000060313774262024015373 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 7; use Graph; my $g = Graph->new; ok( !$g->has_vertices() ); my $v = $g->vertices; is( $v, 0 ); my @v = $g->vertices; is( "@v", "" ); $g->add_vertex("a"); $v = $g->vertices; is( $v, 1 ); @v = $g->vertices; is( "@v", "a" ); $g->add_vertex("b"); $v = $g->vertices; is( $v, 2 ); @v = sort $g->vertices; is( "@v", "a b" ); Graph-0.9735/t/22_refvertexed.t0000644000175000017500000000115513774262024016077 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 6; use Graph; my $g1 = Graph->new; ok ( !$g1->refvertexed ); my $g2 = Graph->new( refvertexed => 1 ); ok ( $g2->refvertexed ); { # rt.cpan.org 78465 find_a_cycle and has_cycle are broken my $v1 = \1; my $v2 = \2; my $graph = Graph->new( directed => 1, refvertexed => 1, edges => [[$v1, $v2], [$v2, $v1]] ); my @v = $graph->vertices(); ok($graph->has_a_cycle); my @c = $graph->find_a_cycle; is(@c, 2); if ($c[0] == $v1) { is_deeply($c[0], $v1); is_deeply($c[1], $v2); } else { is_deeply($c[0], $v2); is_deeply($c[1], $v1); } } Graph-0.9735/t/33_hyperedge.t0000644000175000017500000000401614741033474015531 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph; my $g = Graph->new(hyperedged => 1, directed => 0); $g->add_edge(); $g->add_edge("a"); $g->add_edge("b", "c"); $g->add_edge("d", "e", "f"); is( $g->edges, 4 ); my @e = $g->edges; is ( "@{[ sort map { qq'[@$_]' } @e ]}", "[] [a] [b c] [d e f]" ); ok( $g->has_edge() ); ok( $g->has_edge("a") ); ok( $g->has_edge("b", "c") ); ok( $g->has_edge("d", "e", "f") ); ok( $g->any_edge("d", "e", "f") ); ok( $g->any_edge("e", "f") ); ok( $g->any_edge("f", "e") ); ok( !$g->any_edge("a", "e") ); ok( ! $g->has_edge("b") ); ok( ! $g->has_edge("c") ); ok( ! $g->has_edge("d", "e") ); $g->add_edge("d", "e", "g"); is $g, ",a,b=c,d=e=f,d=e=g"; is( $g->delete_edge("d", "e", "f"), $g ); is $g, ",a,b=c,d=e=g,f"; ok( ! $g->has_edge("d", "e", "f") ); ok( $g->has_edge("d", "e", "g") ); is( $g->delete_edge("d", "e", "f"), $g ); is ( "@{[ sort map { qq'[@$_]' } $g->edges ]}", "[] [a] [b c] [d e g]" ); $g = Graph->new(hyperedged => 1, directed => 1); $g->set_edge_attributes([qw(a b c)], [qw(f g)], { color => 'pearl', weight => 'heavy' }); $g->add_weighted_edge([qw(a b c)], [qw(f h)], 123); $g->add_weighted_path(["c"], 45, ["d"], 46, ["e"]); ok !$g->has_edge([qw(a c)], [qw(f g)]); ok( $g->any_edge("c", "g") ); ok( $g->any_edge("c", "d") ); ok( !$g->any_edge("b", "d") ); ok $g->has_edge([qw(a b c)], [qw(f g)]) or diag explain $g; ok $g->has_edge([qw(b a c)], [qw(f g)]); ok $g->has_edge([qw(a b c)], [qw(f h)]) or diag explain $g; ok !$g->has_edge([qw(f h)], [qw(a b c)]) or diag explain $g; ok $g->has_edge([qw(c)], [qw(d)]) or diag explain $g; ok $g->has_edge([qw(d)], [qw(e)]) or diag explain $g; is_deeply [ $g->edges_to('e') ], [ [['d'], ['e']] ]; is_deeply [ $g->edges_from('d') ], [ [[qw(d)], [qw(e)]] ]; is_deeply [ $g->edges_at('e') ], [ [[qw(d)], [qw(e)]] ] or diag explain [ $g->edges_at('e') ]; is_deeply [ sort $g->successors('c') ], [qw(d f g h)]; is_deeply [ $g->predecessors('e') ], [qw(d)]; is $g, "[a,b,c]-[f,g],[a,b,c]-[f,h],[c]-[d],[d]-[e]"; done_testing; Graph-0.9735/t/40_edges_from.t0000644000175000017500000000222013774262024015660 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 16; use Graph; my $g = Graph->new; $g->add_edge("a", "b"); $g->add_edge("b", "c"); $g->add_edge("c", "d"); $g->add_edge("d", "d"); $g->add_edge("e", "b"); $g->add_edge("c", "f"); $g->add_edge("c", "g"); $g->add_edge("g", "h"); $g->add_edge("h", "g"); sub from { join(" ", sort map { "[" . join(" ", map { ref $_ ? "[@$_]" : $_ } @$_) . "]" } $g->edges_from(@_)); } is( from("a"), "[a b]"); is( from("b"), "[b c]"); is( from("c"), "[c d] [c f] [c g]"); is( from("d"), "[d d]"); is( from("e"), "[e b]"); is( from("f"), ""); is( from("g"), "[g h]"); is( from("h"), "[h g]"); is( from("x"), ""); { use Graph::Directed; my $g1 = new Graph::Directed(); $g1->add_edge(0,0); my @e = $g1->edges_from(0); is(@e, 1); is("@{ $e[0] }", "0 0"); } { my $g2 = new Graph::Directed(); $g2->add_edge(1,1); $g2->add_edge(1,2); my @e1 = $g2->edges_from(1); is(@e1, 2); @e1[1, 0] = @e1[0, 1] if $e1[0]->[1] > $e1[1]->[1]; is("@{ $e1[0] }", "1 1"); is("@{ $e1[1] }", "1 2"); my @e2 = $g2->edges_from(2); is(@e2, 0); my @e3 = $g2->edges_from(0); is(@e3, 0); } Graph-0.9735/t/89_connected_subgraphs.t0000644000175000017500000000156614760717055017624 0ustar osboxesosboxesuse strict; use warnings; use Graph::Undirected; use Test::More; my $g; my @subgraphs; # Caffeine molecule, heavy atoms only $g = Graph::Undirected->new; $g->add_path('A'..'K'); $g->add_edge('C', 'L'); $g->add_edge('E', 'M'); $g->add_edge('I', 'N'); $g->add_edge('B', 'J'); $g->add_edge('D', 'H'); @subgraphs = $g->connected_subgraphs; is(@subgraphs, 1153); my @by_size; for (@subgraphs) { $by_size[scalar $_->vertices]++; } my @result0 = qw( 0 14 15 23 40 68 112 165 206 208 162 93 37 9 1 ); for (1..$#result0) { is($by_size[$_], $result0[$_]); } # K5 $g = Graph::Undirected->new; for my $i (1..5) { for my $j ($i+1..5) { $g->add_edge($i, $j); } } @subgraphs = $g->connected_subgraphs; is(@subgraphs, 31); # Line of 5 vertices $g = Graph::Undirected->new; $g->add_path(1..5); @subgraphs = $g->connected_subgraphs; is(@subgraphs, 15); done_testing; Graph-0.9735/t/u_bb_rv.t0000644000175000017500000000141713743337306014672 0ustar osboxesosboxesuse Test::More tests => 11; use strict; use warnings; use Graph; my ($u, $v); my $g1 = Graph->new(refvertexed => 1); use Math::Complex qw(cplx); $g1->add_edge($u = cplx(1,2), $v = cplx(3,4)); is($g1, "$u-$v"); $g1->delete_vertex($u); is($g1, $v); $g1->delete_vertex($v); is($g1, ""); my $g2 = Graph->new(refvertexed => 1); use Math::Complex qw(cplx); $g2->add_vertex($u = cplx(1,2)); is($g2, $u); $g2->add_vertex($v = cplx(3,4)); is($g2, "$u,$v"); $g2->delete_vertex($u); is($g2, $v); $g2->delete_vertex($v); is($g2, ""); my $g3 = Graph->new(refvertexed => 1); use Math::Complex qw(cplx); $g3->add_edge($u = cplx(1,2), $v = cplx(3,4)); is($g3, "$u-$v"); $g3->delete_edge($u, $v); is($g3, "$u,$v"); $g3->delete_vertex($u); is($g3, $v); $g3->delete_vertex($v); is($g3, ""); Graph-0.9735/t/simple.pl0000644000175000017500000000136513774262024014715 0ustar osboxesosboxes# Simple classes for testing. use strict; use warnings; sub Foo::new { bless { foo => $_[1] }, $_[0]; } sub Foo::xyz { 1; } sub Bar::new { bless { bar => $_[1] }, $_[0]; } sub Bar::xyz { 1; } { package Bar; use Scalar::Util qw(refaddr); use overload '""' => \&str, eq => \&eq, ne => \≠ sub str { refaddr $_[0] } sub eq { my $d0 = defined $_[0]->{bar}; my $d1 = defined $_[1]->{bar}; $d0 && $d1 ? $_[0]->{bar} eq $_[1]->{bar} : $d0 || $d0 ? 0 : 1; } sub ne { my $d0 = defined $_[0]->{bar}; my $d1 = defined $_[1]->{bar}; $d0 && $d1 ? $_[0]->{bar} ne $_[1]->{bar} : $d0 || $d0 ? 1 : 0; } } 1; Graph-0.9735/t/u_te_me.t0000644000175000017500000000125713743337306014673 0ustar osboxesosboxesuse Graph; use strict; use Test::More tests => 18; my $g0 = Graph->new (multiedged => 1); for my $i (0..2) { print "# Adding 'A' - 'B'\n"; my $id = $g0->add_edge_get_id('A', 'B'); is($id, $i, "id is $i"); my @ids = sort { $a <=> $b } $g0->get_multiedge_ids('A', 'B'); print "# ids = @ids\n"; for my $j (0..$i) { is($ids[$j], $j, "id[$j] is $j"); } } my $g1 = Graph->new (multivertexed => 1); for my $i (0..2) { print "# Adding 'C'\n"; my $id = $g1->add_vertex_get_id('C'); is($id, $i, "id is $i"); my @ids = sort { $a <=> $b } $g1->get_multivertex_ids('C'); print "# ids = @ids\n"; for my $j (0..$i) { is($ids[$j], $j, "id[$j] is $j"); } } Graph-0.9735/t/u_ng_scc.t0000644000175000017500000003506614741033474015041 0ustar osboxesosboxesuse strict; use warnings; use Carp; use Getopt::Long; use Text::Abbrev; use Graph::Directed; use Test::More qw(no_plan); # random graphs my $cmd_line="$0 @ARGV"; my %OPTIONS; GetOptions (\%OPTIONS, qw(help verbose echo|X seed=i input cycle cone grid random circumference=i height=i width=i nodes=i edges=i density=f )); $OPTIONS{help} and die <] [] Test StrongComponents algorithm. The test-graphs can be any of the following: 1. Provided as input in a file or STDIN Expressed as pairs (node1 node2) one pair per line 2. Cycle. Size is specified by --circumference Density of cross edges specified by --density 3. Cone (layered cycles of increasing size) --circumference is size of largest cycle 4. Grid. Size is specified by --height and --width options. If just 1 number is specified, the grid is square. If neither is specified defaults are used. 5. Random graph. Size is specifed by any two of the following: number of nodes (--nodes option) number of edges (--edges) density (edges/nodes, --density option) If 0 or 1 are specified, defaults are used for rest. The size is a target. The actual graph may be a little smaller due to duplicate edges. Default is --grid and --random Options ------- --help Print this message --verbose (for testing) -X or --echo Echo command line (for testing and use in scripts) --seed Seed for random number generator --input Graph is provided as input --cycle Generate cycle graph --cone Generate cone graph --circumference Numbre of nodes in cycle graph --grid Generate grid (implied by --height or --width) --height Grid height --width Grid width --random Generate random graph (implied by --nodes, --edges) --nodes Number of nodes in random graph --edges Number of edges in random graph --density Average number of cross edges per node in cycle graph Average number of edges per node in random graph Options and values may be abbreviated. Values are case insensitive. USAGE ; print OUT "$cmd_line\n" if $OPTIONS{echo}; srand $OPTIONS{seed} if defined $OPTIONS{seed}; my %DEFAULTS= (circumference=>10, height=>3, width=>3, nodes=>10, edges=>11, density=>1.1, ); my $INPUT=$OPTIONS{input} || @ARGV>=2; my $CYCLE=$OPTIONS{cycle}; my $CONE=$OPTIONS{cone}; $CYCLE || $CONE or ($OPTIONS{circumference} and $CYCLE=1); my $GRID=$OPTIONS{grid} || $OPTIONS{height} || $OPTIONS{width}; my $RANDOM=$OPTIONS{random} || $OPTIONS{nodes} || $OPTIONS{edges}; $CYCLE=$CONE=$GRID=$RANDOM=1 unless $INPUT || $CYCLE || $CONE || $GRID || $RANDOM; my $CIRCUMFERENCE= $CYCLE ? get_param('circumference') : undef; my $CONE_SIZE= $CONE ? get_param('circumference') : undef; my($HEIGHT,$WIDTH)= $GRID ? grid_size() : undef; my($NODES,$EDGES)= $RANDOM ? random_size() : undef; my $DENSITY=get_param('density'); if ($INPUT && @ARGV) { my $input_file=shift @ARGV; open(IN,$input_file) || confess "Cannot open input file $input_file: $!"; } else { *IN=*STDIN; } if (@ARGV) { my $output_file=shift @ARGV; open(OUT,"> $output_file") || die "Cannot create output file $output_file: $!"; } else { *OUT=*STDOUT; } if ($INPUT) { print"# Test input graph\n"; my $graph=input_graph(); test_sc($graph); } if ($CYCLE) { print"# Test cycle graph\n"; my $graph=cycle_graph(); my $components=test_sc($graph); is(@$components, 1, "number of components"); } if ($CONE) { print"# Test cone graph\n"; my $graph=cone_graph(); my $components=test_sc($graph); test_cone_sc($graph,$components); print"# Test cone graph with intermediate nodes\n"; my $graph2=cone_graph('intermediate'); my $components2=test_sc($graph2); test_coneint_sc($graph2,$components2); } if ($GRID) { print"# Test grid graph (acyclic)\n"; my $graph=grid_graph(); my $nodes=$graph->vertices; my $components=test_sc($graph); is(@$components, $nodes); print"# grid graph (cyclic)\n"; my $graph2=grid_graph('cyclic'); my $components2=test_sc($graph2); is(@$components2, 1, "number of components"); } if ($RANDOM) { print"# Test random graph (acyclic)\n"; my $graph=random_graph(); my $components=test_sc($graph); my $nodes=$graph->vertices; is(@$components, $nodes); print"# Test random graph (cyclic)\n"; my $graph2=random_graph('cyclic'); my $components2=test_sc($graph2); } sub input_graph { my $graph=new Graph::Directed; while () { my($node0,$node1)=split; next unless length($node0) && length($node1); $graph->add_weighted_edge($node0,$node1,1); } $graph; } sub cycle_graph { my($circumference,$density)=@_; defined $circumference or $circumference=$CIRCUMFERENCE; defined $density or $density=$DENSITY; my $graph=new Graph::Directed; # make simple cycle for (my $i=1; $i<$circumference; $i++) { $graph->add_weighted_edge($i-1,$i,1); } $graph->add_weighted_edge($circumference-1,0,1); # add random cross edges my $cross_edges=int $density*$circumference; for (my $i=0;$i<$cross_edges; $i++) { my($node1,$node2)=(int rand $circumference,int rand $circumference); $graph->add_weighted_edge($node1,$node2,1); } $graph; } sub cone_graph { my($intermediate)=@_; my $graph=new Graph::Directed; print"# Constructing cone graph...\n"; # make $CONE_SIZE simple cycles of sizes 1..$CONE_SIZE for (my $i=0; $i<$CONE_SIZE; $i++) { my $circumference=$i+1; # make simple cycle for (my $j=1; $j<$circumference; $j++) { $graph->add_weighted_edge($i.'/'.($j-1),"$i/$j",1); } $graph->add_weighted_edge($i.'/'.($circumference-1),"$i/0",1); # add random cross edges my $cross_edges=int $DENSITY*$i; for (my $j=0;$j<$cross_edges; $j++) { my($j1,$j2)=(int rand $circumference,int rand $circumference); $graph->add_weighted_edge("$i/$j1","$i/$j2",1); } } # add random edges between cycles my $nodes=0+$graph->vertices; my $edges=int $DENSITY*$nodes; for (my $i=0;$i<$edges; $i++) { my($i1,$i2)=(int rand $CONE_SIZE,int rand $CONE_SIZE); ($i1,$i2)=sort2($i1,$i2); my($j1,$j2)=(int rand $i1+1,int rand $i2+1); if ($intermediate && $i1 != $i2) { $graph->add_weighted_edge("$i1/$j1","i$i",1); $graph->add_weighted_edge("i$i","$i2/$j2",1); } else { $graph->add_weighted_edge("$i1/$j1","$i2/$j2",1); } } $graph; } sub grid_graph { my($cyclic)=@_; my $graph=new Graph::Directed; for (my $i=0; $i<$HEIGHT; $i++) { for (my $j=0; $j<$WIDTH; $j++) { my $node=grid_node($i,$j); $graph->add_vertex($node); $graph->add_weighted_edge(grid_node($i-1,$j),$node,1) if $i>0; # down $graph->add_weighted_edge(grid_node($i,$j-1),$node,1) if $j>0; # right $graph->add_weighted_edge(grid_node($i-1,$j-1),$node,1) # down-right diagonal if !$cyclic && $i>0 && $j>0; # $graph->add_weighted_edge(grid_node($i-1,$j+1),$node,1) # down-left diagonal # if $cyclic && $i>0 && $j<$WIDTH-1; } } if ($cyclic) { # add wrapround edges, making grid a torus if ($WIDTH>1) { for (my $i=0; $i<$HEIGHT; $i++) { $graph->add_weighted_edge(grid_node($i,$WIDTH-1),grid_node($i,0),1); } } if ($HEIGHT>1) { for (my $j=0; $j<$WIDTH; $j++) { $graph->add_weighted_edge(grid_node($HEIGHT-1,$j),grid_node(0,$j),1); } } } $graph; } sub random_graph { my($cyclic)=@_; my $graph=new Graph::Directed; for (my $i=0;$i<$EDGES; $i++) { my($node1,$node2)=(int rand $NODES,int rand $NODES); ($node1,$node2)=sort2($node1,$node2) unless $cyclic; $graph->add_weighted_edge($node1,$node2,1) unless $node1==$node2; } $graph; } sub get_param { my($keyword)=@_; $keyword=lc $keyword; my $param=$OPTIONS{$keyword}; defined $param or $param=$DEFAULTS{$keyword}; $param; } sub grid_size { my $height=$OPTIONS{height}; my $width=$OPTIONS{width}; # Cases # 1. no height && no width => use defaults # 2. height && no width => set width=height # 3. no height && width => set height=width # 4. height && width => use as given if (!defined $height && !defined $width) { $height=$DEFAULTS{height}; $width=$DEFAULTS{width}; } elsif (defined $height && !defined $width) { $width=$height; } elsif (!defined $height && defined $width) { $height=$width; } ($height,$width); } sub grid_node { my($i,$j)=@_; $j=$i unless defined $j; "$i/$j"; } #distances in directed grid with either diagonal edges or wraparound edges sub grid_dist { my($node1,$node2,$cyclic)=@_; my($i1,$j1)=split('/',$node1); my($i2,$j2)=split('/',$node2); if ($cyclic) { # wraparound edges return ($i2-$i1)%$HEIGHT + ($j2-$j1)%$WIDTH; } else { return undef unless $i1<=$i2 && $j1<=$j2; return max($i2-$i1,$j2-$j1); } } sub random_size { my $nodes=$OPTIONS{nodes}; my $edges=$OPTIONS{edges}; my $density=$OPTIONS{density}; # Cases # nodes edges density action # no no no use default nodes, edges # no no yes use default nodes # no yes no use default density # no yes yes okay # yes no no use default density # yes no yes okay # yes yes no okay # yes yes yes overspecified -- use nodes, edges if (!defined $nodes) { if (!defined $edges && !defined $density) { ($nodes,$edges)=@DEFAULTS{qw(nodes edges)}; } elsif (!defined $edges && defined $density) { $nodes=$DEFAULTS{nodes}; $edges=int $nodes*$density; } elsif (defined $edges && !defined $density) { $nodes=int ($edges/$DEFAULTS{density}); } else { $nodes=int ($edges/$density); } } else { if (!defined $edges && !defined $density) { $edges=int $nodes*$DEFAULTS{density}; } elsif (!defined $edges && defined $density) { $edges=int $nodes*$density; } # remaining cases have $edges set, so nothing to do } ($nodes,$edges); } # check strong components sub test_sc { my($graph)=@_; print OUT "+++ test_sc\n" if $OPTIONS{verbose}; my @components=$graph->strongly_connected_components; require Graph::TransitiveClosure; my $tc=new Graph::TransitiveClosure($graph); check_partition($graph,@components); # check that components partition nodes check_reach($graph,$tc,@components); # check reachability of components \@components; } sub check_partition { my($graph,@components)=@_; my @nodes=$graph->vertices; my %hits; for my $component (@components) { map {$hits{$_}++} @$component; } for my $node (@nodes) { is($hits{$node}, 1, "node $node"); } } sub dump_graph { my ($g) = @_; # these reveal the order that vertices and edges were added, helping repro my @v = map @$_, $g->[2]->paths; my $v = join(',', @v); my $e = join(',', map join('-', @v[@$_]), $g->[3]->paths); [ $v, $e ]; } sub check_reach { my($graph,$tc,@components)=@_; # for each (ordered) pair of nodes in each component, make sure 1st node can reach 2nd for my $component (@components) { for (my $i=0; $i<@$component; $i++) { my $nodei=$component->[$i]; for (my $j=0; $j<@$component; $j++) { my $nodej=$component->[$j]; ok($tc->is_reachable($nodei,$nodej), "nodes $nodei, $nodej in same component and reachable") or diag "for graph $graph ", explain \@components, dump_graph($graph); } } } # for each pair of mutually reachable nodes, make sure they're in same component my %components; for my $component (@components) { # put each node in its component @components{@$component}=($component)x@$component; } my @nodes=$graph->vertices; for (my $i=0; $i<@nodes; $i++) { my $nodei=$nodes[$i]; for (my $j=$i; $j<@nodes; $j++) { my $nodej=$nodes[$j]; next unless $tc->is_reachable($nodei,$nodej) && $tc->is_reachable($nodej,$nodei); is($components{$nodei}, $components{$nodej}, "nodes $nodei, $nodej reachable and in same component"); } } } # check strong components of cone graph # exploits the special structure of cycle, namely, cycles are in layers sub test_cone_sc { my($graph,$components)=@_; is(@$components, $CONE_SIZE); # each component should be different size my %size2component; for my $component (@$components) { my $size=@$component; ok(!$size2component{$size}, "two components have same size ($size)"); $size2component{$size}=$component; } # check that each component corresponds to a layer for (my $i=0; $i<$CONE_SIZE; $i++) { my $size=$i+1; my $component=$size2component{$size}; # ecah node should start with "$i/" my @bad=grep {$_!~/^$i\//} @$component; is(@bad, 0, "component $size does not contain wrong nodes (@bad)"); } } # check strong components of cone graph with intermediate nodes # exploits the special structure of cycle, namely, cycles are in layers sub test_coneint_sc { my($graph,$components)=@_; my @nodes=$graph->vertices; my @intermediates=grep {$_=~/^i/} @nodes; is(@$components, $CONE_SIZE+@intermediates); # each intermediate node should be in own component for my $component (@$components) { my @ints=grep {$_=~/^i/} @$component; ok(@ints <= 1, "component contains multiple intermediate nodes @ints"); ok(!(@ints==1 && @$component>1), "component contains cycle and intermediate nodes"); } # each component should be different size unless it contains just an intermediate node my %size2component; my %intermediates; for my $component (@$components) { my $size=@$component; next if grep {$_=~/^i/} @$component; ok(!($size2component{$size}), "two components have same size ($size)"); $size2component{$size}=$component; } # check that each non-intermediate node component corresponds to a layer for (my $i=0; $i<$CONE_SIZE; $i++) { my $size=$i+1; my $component=$size2component{$size}; # each node should start with "$i/" my @bad=grep {$_!~/^$i\//} @$component; is(@bad, 0, "component $size contains wrong nodes @bad"); } } sub min { if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];} return undef unless @_; if ($#_==1) {my($x,$y)=@_; return ($x<=$y?$x:$y);} my $min=shift @_; map {$min=$_ if $_<$min} @_; $min; } sub max { if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];} return undef unless @_; if ($#_==1) {my($x,$y)=@_; return ($x>=$y?$x:$y);} my $max=shift @_; map {$max=$_ if $_>$max} @_; $max; } sub mindef {min(grep {defined $_} @_);} sub maxdef {max(grep {defined $_} @_);} sub sort2 {$_[0]<=$_[1]? ($_[0],$_[1]): ($_[1],$_[0]);} Graph-0.9735/t/72_transitive.t0000644000175000017500000003741314741033474015757 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::Directed; use Graph::Undirected; my $g0 = Graph::Directed->new; $g0->add_edge(qw(a b)); $g0->add_edge(qw(a c)); $g0->add_edge(qw(c d)); ok(!$g0->is_transitive); my $t0 = Graph::TransitiveClosure->new($g0->deep_copy); is $t0, "a-a,a-b,a-c,a-d,b-b,c-c,c-d,d-d"; ok( $t0->is_transitive); my $r0 = Graph::TransitiveClosure->new($g0->deep_copy, reflexive => 0); is $r0, "a-b,a-c,a-d,c-d"; ok !$r0->transitive_closure_matrix->is_transitive(qw(a a)), 'r0 !is_transitive a a'; ok $r0->transitive_closure_matrix->is_transitive(qw(a c)), 'r0 is_transitive a c'; ok !$r0->transitive_closure_matrix->is_transitive(qw(d a)), 'r0 !is_transitive d a'; ok( $r0->is_transitive); my $r1 = Graph::TransitiveClosure->new($g0->deep_copy, reflexive => 1); is $r1, "a-a,a-b,a-c,a-d,b-b,c-c,c-d,d-d"; ok $r1->transitive_closure_matrix->is_transitive(qw(a a)), 'r1 is_transitive a a'; ok $r1->transitive_closure_matrix->is_transitive(qw(a c)), 'r1 is_transitive a c'; ok !$r1->transitive_closure_matrix->is_transitive(qw(d a)), 'r1 !is_transitive d a'; ok( $r1->is_transitive); my $g1 = Graph::Undirected->new; $g1->add_edge(qw(a b)); $g1->add_edge(qw(a c)); $g1->add_edge(qw(c d)); ok(!$g1->is_transitive); my $t1 = Graph::TransitiveClosure->new($g1->deep_copy); is $t1, "a=a,a=b,a=c,a=d,b=b,b=c,b=d,c=c,c=d,d=d"; is("@{[$t1->path_vertices(qw(a d))]}", "a c d"); is($t1->path_length(qw(a b)), 1); ok( $t1->is_transitive); my $g2 = Graph->new; $g2->add_weighted_edge(qw(a b 3)); $g2->add_weighted_edge(qw(b c 1)); ok(!$g2->is_transitive); my $t2 = Graph::TransitiveClosure->new($g2->deep_copy, path => 1); is($t2->path_length(qw(a a)), 0); is($t2->path_length(qw(a b)), 3); is($t2->path_length(qw(a c)), 4); is($t2->path_length(qw(b a)), undef); is($t2->path_length(qw(b b)), 0); is($t2->path_length(qw(b c)), 1); is($t2->path_length(qw(c a)), undef); is($t2->path_length(qw(c b)), undef); is($t2->path_length(qw(c c)), 0); is("@{[$t2->path_vertices(qw(a a))]}", ""); is("@{[$t2->path_vertices(qw(a b))]}", "a b"); is("@{[$t2->path_vertices(qw(a c))]}", "a b c"); is("@{[$t2->path_vertices(qw(b a))]}", ""); is("@{[$t2->path_vertices(qw(b b))]}", ""); is("@{[$t2->path_vertices(qw(b c))]}", "b c"); is("@{[$t2->path_vertices(qw(c a))]}", ""); is("@{[$t2->path_vertices(qw(c b))]}", ""); is("@{[$t2->path_vertices(qw(c c))]}", ""); ok( $t2->is_transitive); my $g3 = Graph->new; $g3->add_edge(qw(a b)); $g3->add_edge(qw(b c)); ok(!$g3->deep_copy->is_transitive); my $t3 = Graph::TransitiveClosure->new($g3->deep_copy, path => 1); is($t3->path_length(qw(a a)), 0); is($t3->path_length(qw(a b)), 1); is($t3->path_length(qw(a c)), 2); is($t3->path_length(qw(b a)), undef); is($t3->path_length(qw(b b)), 0); is($t3->path_length(qw(b c)), 1); is($t3->path_length(qw(c a)), undef); is($t3->path_length(qw(c b)), undef); is($t3->path_length(qw(c c)), 0); is("@{[$t3->path_vertices(qw(a a))]}", ""); is("@{[$t3->path_vertices(qw(a b))]}", "a b"); is("@{[$t3->path_vertices(qw(a c))]}", "a b c"); is("@{[$t3->path_vertices(qw(b a))]}", ""); is("@{[$t3->path_vertices(qw(b b))]}", ""); is("@{[$t3->path_vertices(qw(b c))]}", "b c"); is("@{[$t3->path_vertices(qw(c a))]}", ""); is("@{[$t3->path_vertices(qw(c b))]}", ""); is("@{[$t3->path_vertices(qw(c c))]}", ""); is($t3->path_successor(qw(a a)), undef); is($t3->path_successor(qw(a b)), "b"); is($t3->path_successor(qw(a c)), "b"); is($t3->path_successor(qw(b a)), undef); is($t3->path_successor(qw(b b)), undef); is($t3->path_successor(qw(b c)), "c"); is($t3->path_successor(qw(c a)), undef); is($t3->path_successor(qw(c b)), undef); is($t3->path_successor(qw(c c)), undef); ok( $t3->is_transitive); is($g3->path_length(qw(a a)), 0); is($g3->path_length(qw(a b)), 1); is($g3->path_length(qw(a c)), 2); is($g3->path_length(qw(b a)), undef); is($g3->path_length(qw(b b)), 0); is($g3->path_length(qw(b c)), 1); is($g3->path_length(qw(c a)), undef); is($g3->path_length(qw(c b)), undef); is($g3->path_length(qw(c c)), 0); is("@{[$g3->path_vertices(qw(a a))]}", ""); is("@{[$g3->path_vertices(qw(a b))]}", "a b"); is("@{[$g3->path_vertices(qw(a c))]}", "a b c"); is("@{[$g3->path_vertices(qw(b a))]}", ""); is("@{[$g3->path_vertices(qw(b b))]}", ""); is("@{[$g3->path_vertices(qw(b c))]}", "b c"); is("@{[$g3->path_vertices(qw(c a))]}", ""); is("@{[$g3->path_vertices(qw(c b))]}", ""); is("@{[$g3->path_vertices(qw(c c))]}", ""); is($g3->path_successor(qw(a a)), undef); is($g3->path_successor(qw(a b)), "b"); is($g3->path_successor(qw(a c)), "b"); is($g3->path_successor(qw(b a)), undef); is($g3->path_successor(qw(b b)), undef); is($g3->path_successor(qw(b c)), "c"); is($g3->path_successor(qw(c a)), undef); is($g3->path_successor(qw(c b)), undef); is($g3->path_successor(qw(c c)), undef); { # Found by Nathan Goodman. is($t3->path_vertices("a", "b"), 2); is($t3->path_vertices("a", "b"), 2); # Crashed or hung, depending. } { my $g4 = Graph::Directed->new; $g4->set_edge_attribute("a", "b", "distance", 2); $g4->set_edge_attribute("b", "c", "distance", 3); my $t4 = Graph::TransitiveClosure->new($g4, attribute_name => 'distance', path_length => 1); is($t4->path_length("a", "c"), 5); } { # Found by Nathan Goodman. use Graph::Directed; my $graph = new Graph::Directed; $graph->add_weighted_edge('a', 'b', 1); $graph->add_weighted_edge('b', 'a', 1); my $tc = new Graph::TransitiveClosure($graph, path_length => 1, path_vertices => 1); is($tc->path_length('a','a'), 0); is($tc->path_vertices('a','a'), 0); is($tc->path_length('b','b'), 0); is($tc->path_vertices('b','b'), 0); # Some extra ones. is($tc->path_length('a','b'), 1); is($tc->path_vertices('a','b'), 2); is($tc->path_length('b','a'), 1); is($tc->path_vertices('b','a'), 2); ok($tc->is_reachable('a', 'a')); ok($tc->is_reachable('a', 'b')); ok($tc->is_reachable('b', 'a')); ok($tc->is_reachable('b', 'b')); } { use Graph::Directed; my $graph = new Graph::Directed; $graph->add_edge('a', 'b'); $graph->add_edge('b', 'a'); my $tc = new Graph::TransitiveClosure($graph, path_length => 1, path_vertices => 1); is($tc->path_length('a','a'), 0); is($tc->path_vertices('a','a'), 0); is($tc->path_length('b','b'), 0); is($tc->path_vertices('b','b'), 0); is($tc->path_length('a','b'), 1); is($tc->path_vertices('a','b'), 2); is($tc->path_length('b', 'a'), 1); is($tc->path_vertices('b','a'), 2); ok($tc->is_reachable('a', 'a')); ok($tc->is_reachable('a', 'b')); ok($tc->is_reachable('b', 'a')); ok($tc->is_reachable('b', 'b')); } { # More Nathan Goodman. use Graph::Directed; my $graph = new Graph::Directed; $graph->add_weighted_edge('a', 'a', 1); my $tc = new Graph::TransitiveClosure($graph, path_length => 1, path_vertices => 1); ok($tc->is_reachable('a', 'a')); is($tc->path_length('a', 'a'), 0); is($tc->path_vertices('a', 'a'), 0); # More extra. is($tc->path_length('b','b'), undef); is($tc->path_vertices('b','b'), undef); is($tc->path_length('a','b'), undef); is($tc->path_vertices('a','b'), undef); is($tc->path_length('b', 'a'), undef); is($tc->path_vertices('b','a'), undef); is($tc->is_reachable('a', 'b'), undef); is($tc->is_reachable('b', 'a'), undef); is($tc->is_reachable('b', 'b'), undef); } # TransitiveClosure_Floyd_Warshall is just an alias for TransitiveClosure. my $t0tcfw = $g0->deep_copy->TransitiveClosure_Floyd_Warshall; is($t0, $t0tcfw); my $t3apspfw = $g3->deep_copy->APSP_Floyd_Warshall; is($t3, $t3apspfw); is($t3apspfw->path_length(qw(a a)), 0); is($t3apspfw->path_length(qw(a b)), 1); is($t3apspfw->path_length(qw(a c)), 2); is($t3apspfw->path_length(qw(b a)), undef); is($t3apspfw->path_length(qw(b b)), 0); is($t3apspfw->path_length(qw(b c)), 1); is($t3apspfw->path_length(qw(c a)), undef); is($t3apspfw->path_length(qw(c b)), undef); is($t3apspfw->path_length(qw(c c)), 0); is("@{[$t3apspfw->path_vertices(qw(a a))]}", ""); is("@{[$t3apspfw->path_vertices(qw(a b))]}", "a b"); is("@{[$t3apspfw->path_vertices(qw(a c))]}", "a b c"); is("@{[$t3apspfw->path_vertices(qw(b a))]}", ""); is("@{[$t3apspfw->path_vertices(qw(b b))]}", ""); is("@{[$t3apspfw->path_vertices(qw(b c))]}", "b c"); is("@{[$t3apspfw->path_vertices(qw(c a))]}", ""); is("@{[$t3apspfw->path_vertices(qw(c b))]}", ""); is("@{[$t3apspfw->path_vertices(qw(c c))]}", ""); is($t3apspfw->path_successor(qw(a a)), undef); is($t3apspfw->path_successor(qw(a b)), "b"); is($t3apspfw->path_successor(qw(a c)), "b"); is($t3apspfw->path_successor(qw(b a)), undef); is($t3apspfw->path_successor(qw(b b)), undef); is($t3apspfw->path_successor(qw(b c)), "c"); is($t3apspfw->path_successor(qw(c a)), undef); is($t3apspfw->path_successor(qw(c b)), undef); is($t3apspfw->path_successor(qw(c c)), undef); { # From Andras Salamon use Graph; my $g = Graph->new; $g->add_edges(qw(a b b c a d d e b f)); my $t = $g->TransitiveClosure_Floyd_Warshall; # the calling convention ok( $t->is_reachable('a', 'f')); ok(!$t->is_reachable('c', 'f')); } { # From Andras Salamon my $g = Graph->new; $g->add_edges( qw( a b b c ) ); $g->add_vertex( 'd' ); my $t0 = $g->deep_copy->TransitiveClosure_Floyd_Warshall(reflexive => 0); ok( $t0->has_vertex( 'a' ) ); ok(!$t0->has_vertex( 'd' ) ); my $t1 = $g->deep_copy->TransitiveClosure_Floyd_Warshall(reflexive => 1); ok( $t1->has_vertex( 'a' ) ); ok( $t1->has_vertex( 'd' ) ); # test caching of TCM my $t0c = $g->TransitiveClosure_Floyd_Warshall(reflexive => 0); ok( $t0c->has_vertex( 'a' ) ); ok(!$t0c->has_vertex( 'd' ) ); my $t1c = $g->TransitiveClosure_Floyd_Warshall(reflexive => 1); ok( $t1c->has_vertex( 'a' ) ); ok( $t1c->has_vertex( 'd' ) ); } { # From Andras Salamon use Graph::Directed; my $g = new Graph::Directed; $g->add_edges( qw(a b b c) ); is($g->APSP_Floyd_Warshall, 'a-a,a-b,a-c,b-b,b-c,c-c'); } { # From Nathan Goodman. my $graph=new Graph::Directed; $graph->add_weighted_edge(0,1,1); $graph->add_weighted_edge(1,2,1); my $tc1=new Graph::TransitiveClosure($graph->deep_copy); is ("@{[sort $tc1->path_vertices(0,1)]}", "0 1"); is ("@{[sort $tc1->path_vertices(0,2)]}", "0 1 2"); is ("@{[sort $tc1->path_vertices(1,2)]}", "1 2"); my $tc2=new Graph::TransitiveClosure($graph->deep_copy,path_length=>1,path_vertices=>1); is ("@{[sort $tc2->path_vertices(0,1)]}", "0 1"); is ("@{[sort $tc2->path_vertices(0,2)]}", "0 1 2"); is ("@{[sort $tc2->path_vertices(1,2)]}", "1 2"); } { # From Jon Freeman. my @example = ( [ 1, 3, -2 ], [ 3, 4, 2 ], [ 4, 2, -1 ], [ 2, 1, 4 ], [ 2, 3, 3 ] ); my $g = Graph::Directed->new; $g->add_weighted_edge(@$_) for @example; my $apsp = $g->APSP_Floyd_Warshall(); # The output from APSP_Floyd_Warshall was non-deterministically # incorrect for two of the possible vertex pairs due to an "|| 1" # instead of defined-or across the 1-3-4 path which had "distance" 2+-2=0 my @bad_edges = ( [1, 2, -1, [1,3,4,2]], [2, 4, 4, [2,1,3,4]] ); foreach my $e (@bad_edges) { my ($u, $v, $length, $path) = @$e; my @spvs = $apsp->path_vertices($u, $v); is_deeply \@spvs, $path, "APSP $u $v" or diag explain \@spvs; is $apsp->path_length($u, $v), $length, "length $u $v"; } } { my @example = ( [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], # direct link to two away [ 3, 4 ] ); my $g = Graph::Directed->new; $g->add_edge(@$_) for @example; my $path_counts = $g->APSP_Floyd_Warshall(path_count => 1); my @counts = ( [ 1, 2, 1 ], [ 1, 3, 1 ], [ 1, 4, 2 ], [ 2, 1, 0 ], [ 2, 3, 0 ], [ 2, 4, 0 ], [ 3, 1, 0 ], [ 3, 2, 0 ], [ 3, 4, 1 ], [ 4, 1, 0 ], [ 4, 2, 0 ], [ 4, 3, 0 ], ); foreach my $e (@counts) { my ($u, $v, $count) = @$e; is $path_counts->path_length($u, $v), $count, "count $u $v"; } } { my @example = ( [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], # direct link to two away [ 1, 1 ], # self-loop [ 3, 4 ] ); my $g = Graph::Directed->new; $g->add_edge(@$_) for @example; my $tcg = $g->transitive_closure; is $tcg->transitive_closure_matrix->[1]->stringify, <<'EOF'; to: 1 2 3 4 1 0 1 1 1 2 0 3 0 1 4 0 EOF my @paths = ( [ 1, 2, [[1,2]] ], [ 1, 3, [[1,3]] ], [ 1, 4, [[1,3,4], [1,4]] ], [ 2, 1, [] ], [ 2, 3, [] ], [ 2, 4, [] ], [ 3, 1, [] ], [ 3, 2, [] ], [ 3, 4, [[3,4]] ], [ 4, 1, [] ], [ 4, 2, [] ], [ 4, 3, [] ], ); foreach my $t (@paths) { my ($u, $v, $paths) = @$t; my $got = [ sort { $a->[1] <=> $b->[1] } $g->all_paths($u, $v) ]; is_deeply $got, $paths, "paths $u $v" or diag explain $got; } } { my @example = ( [ 1, 2, [[qw(a weight 1)], [qw(b weight 2)]] ], [ 1, 3, [[qw(c other 1)], [qw(b weight 2)]] ], [ 1, 4, [[qw(d weight 4)], [qw(b weight 5)]] ], # direct link to two away [ 3, 4, [[qw(d weight 3)], [qw(3 weight 2)]] ] ); my $g = Graph::Directed->new(multiedged => 1); for my $t (@example) { my ($u, $v, $e) = @$t; $g->set_edge_attribute_by_id($u, $v, @$_) for @$e; } my $tcg = $g->transitive_closure; my @paths = ( [ 1, 2, 1, [[1,2]] ], [ 1, 3, 2, [[1,3]] ], [ 1, 4, 4, [[1,3,4], [1,4]] ], [ 2, 1, undef, [] ], [ 2, 3, undef, [] ], [ 2, 4, undef, [] ], [ 3, 1, undef, [] ], [ 3, 2, undef, [] ], [ 3, 4, 2, [[3,4]] ], [ 4, 1, undef, [] ], [ 4, 2, undef, [] ], [ 4, 3, undef, [] ], ); foreach my $t (@paths) { my ($u, $v, $dist, $paths) = @$t; my $got = [ sort { $a->[1] <=> $b->[1] } $g->all_paths($u, $v) ]; is_deeply $got, $paths, "paths $u $v" or diag explain $got; is $tcg->path_length($u, $v), $dist, "dist $u $v"; } } { my $g = Graph::Undirected->new; $g->add_path(qw(A B C A)); my $tcg = $g->transitive_closure; my @paths = sort { @$a <=> @$b } $tcg->all_paths("A", "C"); is_deeply \@paths, [ [qw(A C)], [qw(A B C)] ], "no infinite loop"; } { # 9 4 8 are a cycle, plus longer cycle 9 4 8 7 # other paths: 1-7, 6-2-3 my @example = ( [9, 4], [2, 3], [7, 9], [8, 7], [6, 2], [4, 8], [8, 9], [1, 7], ); my $g = Graph::Directed->new; $g->add_weighted_edge(@$_, 1) for @example; my $tcg = $g->transitive_closure; is $tcg->transitive_closure_matrix->[0]->stringify, <<'EOF', 'adjacency'; to: 1 2 3 4 6 7 8 9 1 1 0 0 1 0 1 1 1 2 0 1 1 0 0 0 0 0 3 0 0 1 0 0 0 0 0 4 0 0 0 1 0 1 1 1 6 0 1 1 0 1 0 0 0 7 0 0 0 1 0 1 1 1 8 0 0 0 1 0 1 1 1 9 0 0 0 1 0 1 1 1 EOF is $tcg->transitive_closure_matrix->[1]->stringify, <<'EOF', 'distances'; to: 1 2 3 4 6 7 8 9 1 0 3 1 4 2 2 0 1 3 0 4 0 2 1 2 6 1 2 0 7 2 0 3 1 8 2 1 0 1 9 1 3 2 0 EOF ok $tcg->is_reachable(7, 8), '7-8 reachable when on cycle'; } { my $g = Graph::Directed->new(edges => [ [qw(A C)], [qw(A NOTA)], [qw(B A)], [qw(B C)], [qw(B NOTA)], ]); $g->delete_vertex('C'); my $tc = $g->transitive_closure; is $tc, 'A-A,A-NOTA,B-A,B-B,B-NOTA,NOTA-NOTA'; $tc->delete_edge($_,$_) for qw(A B C N); is $tc, 'A-NOTA,B-A,B-NOTA,NOTA-NOTA'; } done_testing; Graph-0.9735/t/58_connections.t0000644000175000017500000000300713774262024016105 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 24; use Graph; my $g0 = Graph->new; my $g1 = Graph->new(undirected => 1); my @E = ([1=>1], [1=>2], [1=>3], [2=>4], [5=>4]); $g0->add_edge(@$_) for @E; $g1->add_edge(@$_) for @E; $g0->add_vertex(6); $g1->add_vertex(6); test_graphs($g0, $g1, { sink_vertices => [ [ [], "3 4", "" ] ], source_vertices => [ [ [], "5", "" ] ], isolated_vertices => [ [ [], "6", "6" ] ], interior_vertices => [ [ [], "2", "1 2 3 4 5" ] ], exterior_vertices => [ [ [], "3 4 5 6", "6" ] ], self_loop_vertices => [ [ [], "1", "1" ] ], }); sub test_graphs { my ($g0, $g1, $methods) = @_; for my $m (sort keys %$methods) { for my $t ( @{ $methods->{$m} } ) { my ($args, $expected0, $expected1) = @$t; is( "@{[sort $g0->$m(@$args)]}", $expected0, "directed $m (@$args)" ); is( "@{[sort $g1->$m(@$args)]}", $expected1, "undirected $m (@$args)" ); } } } use Graph::Directed; use Graph::Undirected; $g0 = Graph::Directed->new; $g1 = Graph::Undirected->new; my @P = ([qw(a b d)], [qw(b e)], [qw(a c f f)], [qw(g h)], [qw(i i)], [qw(k k l)]); $g0->add_path(@$_) for @P; $g1->add_path(@$_) for @P; $_->add_vertex(qw(j)) for $g0, $g1; test_graphs($g0, $g1, { sink_vertices => [ [ [], "d e h l", "" ] ], source_vertices => [ [ [], "a g", "" ] ], isolated_vertices => [ [ [], "j", "j" ] ], interior_vertices => [ [ [], "b c", "a b c d e f g h k l" ] ], exterior_vertices => [ [ [], "a d e g h j l", "j" ] ], self_loop_vertices => [ [ [], "f i k", "f i k" ] ], }); Graph-0.9735/t/30_mixedged.t0000644000175000017500000000140214102052753015324 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 36; use Graph; my %EXPECT = ( "0 0" => "a=b", "0 1" => "a=b,c=d=e", "1 0" => "a=b", "1 1" => "a=b,c=d=e", ); for my $m (0, 1) { for my $h (0, 1) { my $g = Graph->new(countedged => $m, hyperedged => $h, directed => 0); note "c = $m, h = $h\n"; $g->add_edge("a", "b"); $g->add_edge("a", "b"); if ($g->hyperedged) { $g->add_edge("c", "d", "e"); $g->add_edge("c", "d", "e"); } for (1, 2) { ok( $g->has_vertices( ) ); is $g, $EXPECT{"$m $h"}; } for (1, 2) { is( $g->get_edge_count("a", "b"), $m ? 2 : 1 ); if ($g->hyperedged) { is( $g->get_edge_count("c", "d", "e"), $m ? 2 : 1 ); } is( $g->get_edge_count("e", "f"), 0 ); } } } Graph-0.9735/t/88_max_cliq.t0000644000175000017500000000275614741033474015375 0ustar osboxesosboxesuse strict; use warnings; use Graph::Undirected; use Test::More; my $g0 = Graph::Undirected->new; $g0->add_vertices('A', 'B'); is_deeply [map [sort @$_], @{ $g0->max_cliques }], [['A'], ['B']], 'no edges'; my $g1 = Graph::Undirected->new; $g1->add_edges(map split(/-/), qw( a-b b-c a-c x-y y-z u-v )); $g1->add_vertex('w'); my $mc = $g1->max_cliques; # Scalar context. is_deeply [sort { $a->[0] cmp $b->[0] } map [sort @$_], @$mc], [['a', 'b', 'c'], ['u', 'v'], ['w'], ['x', 'y'], ['y', 'z']], 'cliques of sizes 1..3'; my $g2 = Graph::Undirected->new; $g2->add_edges(map split(/-/), qw( kh-tc qp-kh de-cg ka-co yn-aq qp-ub cg-tb vc-aq tb-ka wh-tc yn-cg kh-ub ta-co de-co tc-td tb-wq wh-td ta-ka td-qp aq-cg wq-ub ub-vc de-ta wq-aq wq-vc wh-yn ka-de kh-ta co-tc wh-qp tb-vc td-yn )); my @mc = $g2->max_cliques; # List context. is_deeply [sort { "@$a" cmp "@$b" } map [sort @$_], @mc], [ [qw[ aq cg yn ]], [qw[ aq vc wq ]], [qw[ cg de ]], [qw[ cg tb ]], [qw[ co de ka ta ]], [qw[ co tc ]], [qw[ ka tb ]], [qw[ kh qp ub ]], [qw[ kh ta ]], [qw[ kh tc ]], [qw[ qp td wh ]], [qw[ tb vc wq ]], [qw[ tc td wh ]], [qw[ td wh yn ]], [qw[ ub vc wq ]] ], 'Advent of Code 2024 Day 23 Part 2'; done_testing; Graph-0.9735/t/73_diameter.t0000644000175000017500000001222713774262024015356 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 84; use Graph; use Graph::Directed; use Graph::Undirected; my $g = Graph->new(undirected => 1); is($g->diameter, undef); is($g->radius, Graph::Infinity); is($g->shortest_path, undef); is_deeply([$g->shortest_path], []); is_deeply([sort $g->center_vertices], []); is($g->vertex_eccentricity('a'), Graph::Infinity); $g->add_vertex('a'); is($g->diameter, undef); is($g->radius, Graph::Infinity); is($g->shortest_path, undef); is_deeply([$g->shortest_path], []); is_deeply([sort $g->center_vertices], []); is($g->vertex_eccentricity('a'), Graph::Infinity); $g->add_vertex('b'); is($g->diameter, undef); is($g->radius, Graph::Infinity); is($g->shortest_path, undef); is_deeply([$g->shortest_path], []); is_deeply([sort $g->center_vertices], []); is($g->vertex_eccentricity('b'), Graph::Infinity); $g->add_edge(qw(e a)); $g->add_edge(qw(a r)); $g->add_edge(qw(r t)); $g->add_edge(qw(t h)); $g->add_edge(qw(h f)); $g->add_edge(qw(f r)); $g->add_edge(qw(r o)); $g->add_edge(qw(o m)); $g->add_edge(qw(m a)); $g->add_edge(qw(a b)); $g->add_edge(qw(b o)); $g->add_edge(qw(o v)); $g->add_edge(qw(v e)); is($g->diameter, 4); is($g->longest_path, 4); is($g->shortest_path, 1); is($g->longest_path(qw(a h)), 3); is($g->shortest_path(qw(a h)), 3); is($g->longest_path('a', undef), 3); is($g->shortest_path('a', undef), 1); is($g->longest_path(undef, 'h'), 4); is($g->shortest_path(undef, 'h'), 1); is($g->radius, 2); { my @c = sort $g->center_vertices; is(@c, 1); is("@c", "r"); } is($g->average_path_length(), 19 / 9); # Note that the below are just some of the possible paths, # for example other possible paths of length four are # a-r-t-h-e, a-m-o-r-t, b-o-v-e-a, ... # a-b: a-b : 1 # a-e: a-r-o-v-e : 4 # a-f: a-r-t-h-f : 4 # a-h: a-r-t-h : 3 # a-m: a-r-o-m : 3 # a-o: a-r-o : 2 # a-r: a-r : 1 # a-t: a-r-t : 2 # a-v: a-r-o-v : 3 # 23 / 9 = 2.56 is($g->average_path_length('a'), 15 / 9); is($g->average_path_length('b'), 20 / 9); is($g->average_path_length('c'), undef ); is($g->average_path_length('a', undef), 15 / 9); is($g->average_path_length('b', undef), 20 / 9); is($g->average_path_length(undef, 'a'), 15 / 9); is($g->average_path_length(undef, 'b'), 20 / 9); is($g->vertex_eccentricity('a'), 3); is($g->vertex_eccentricity('b'), 4); is($g->vertex_eccentricity('e'), 4); is($g->diameter, 4); is($g->radius, 2); { my @c; @c = sort $g->center_vertices; is(@c, 1); is("@c", "r"); @c = sort $g->center_vertices(1); is(@c, 5); is("@c", "a f o r t"); } sub gino { my $gi = $_[0]; my $m = (sort @$gi)[0]; for (my $i = 0; $i < @$gi && $gi->[0] ne $m; $i++) { push @$gi, shift @$gi; } return @$gi; } my $h = Graph->new(undirected => 1); $h->add_weighted_edge(qw(a b 2.3)); $h->add_weighted_edge(qw(a c 1.7)); is($h->longest_path, 4.0); is($h->shortest_path, 1.7); is($h->diameter, 4.0); is($h->radius, 2.3); my $i = Graph::Directed->new(undirected => 1); $i->add_edge(qw(k a)); $i->add_edge(qw(a l)); $i->add_edge(qw(l e)); $i->add_edge(qw(e v)); $i->add_edge(qw(v a)); $i->add_edge(qw(a l)); $i->add_edge(qw(l a)); $i->add_edge(qw(a n)); is($i->vertex_eccentricity('k'), 3); is($i->vertex_eccentricity('a'), 2); is($i->vertex_eccentricity('l'), 2); is($i->vertex_eccentricity('e'), 3); is($i->vertex_eccentricity('v'), 2); is($i->vertex_eccentricity('n'), 3); { my @c = sort $i->center_vertices; is(@c, 3); is("@c", "a l v"); } my $j = Graph::Undirected->new(undirected => 1); $j->add_edge(qw(k a)); $j->add_edge(qw(a l)); $j->add_edge(qw(l e)); $j->add_edge(qw(e v)); $j->add_edge(qw(v a)); $j->add_edge(qw(a l)); $j->add_edge(qw(l a)); $j->add_edge(qw(a n)); is($j->vertex_eccentricity('k'), 3); is($j->vertex_eccentricity('a'), 2); is($j->vertex_eccentricity('l'), 2); is($j->vertex_eccentricity('e'), 3); is($j->vertex_eccentricity('v'), 2); is($j->vertex_eccentricity('n'), 3); { my @c = sort $j->center_vertices; is(@c, 3); is("@c", "a l v"); } my $k = Graph::Undirected->new(undirected => 1); $k->add_edge(qw(s t)); $k->add_edge(qw(s a)); $k->add_edge(qw(s r)); is($k->vertex_eccentricity('s'), 1); is($k->vertex_eccentricity('t'), 2); is($k->vertex_eccentricity('a'), 2); is($k->vertex_eccentricity('r'), 2); { my @c = sort $k->center_vertices; is(@c, 1); is($c[0], 's'); } { # These tests inspired by Xiaoli Zheng. my $g = Graph::Directed->new(undirected => 1); is($g->diameter, undef); $g->add_edge('a', 'b'); is($g->diameter, 1); $g->add_edge('b', 'c'); is($g->diameter, 2); $g->add_edge('c', 'd'); is($g->diameter, 3); $g->add_edge('e', 'f'); is($g->diameter, 3); $g->add_edge('d', 'e'); is($g->diameter, 5); $g->add_edge('g', 'f'); is($g->diameter, 6); $g->delete_edge('c', 'b'); is($g->diameter, 4); $g->delete_edge('b', 'c'); is($g->diameter, 4); } { my $g = Graph->new(undirected => 1); $g->add_edge(qw(a b)); $g->add_edge(qw(c d)); is($g->vertex_eccentricity('a'), Graph::Infinity); } { my $g = Graph->new(undirected => 1); $g->add_path(1,2,3); is($g->average_path_length(1, 2), 1); } Graph-0.9735/t/u_at3.t0000644000175000017500000000117313774262024014264 0ustar osboxesosboxesuse Test::More tests => 44; use strict; use warnings; use Graph::Undirected; my $g = Graph::Undirected->new; $g->add_edge("a", "b"); $g->add_edge("c", "d"); for (1..10) { my @v1 = $g->SP_Dijkstra("a", "c"); is(@v1, 0); my @v2 = $g->SP_Dijkstra("a", "d"); is(@v2, 0); my @v3 = $g->SP_Dijkstra("b", "c"); is(@v3, 0); my @v4 = $g->SP_Dijkstra("b", "d"); is(@v4, 0); } $g->add_edge("c", "b"); my @v1 = $g->SP_Dijkstra("a", "c"); is("@v1", "a b c"); my @v2 = $g->SP_Dijkstra("a", "d"); is("@v2", "a b c d"); my @v3 = $g->SP_Dijkstra("b", "c"); is("@v3", "b c"); my @v4 = $g->SP_Dijkstra("b", "d"); is("@v4", "b c d"); Graph-0.9735/t/u_ng_mst.t0000644000175000017500000000712213774262024015064 0ustar osboxesosboxesuse strict; use warnings; use Test::More qw/no_plan/; =head1 NAME Test program for Graph. =head2 SYNOPSIS perl u_ng_mst.t [ A [ D [ N ] ] ] =head2 DESCRIPTION This program constructs various trees, embeds them in general graphs, and tests various minimum spanning tree methods: MST_Kruskal, MST_Prim, MST_Dijkstra. A is arity and it defaults to 4. D is depth and it defaults to 3. N is chain/star size and it defaults to 40. (The minimum is 10.) (To use a default, specify '-'.) =head1 AUTHOR Nathan Goodman =cut my ($A, $D, $N) = @ARGV; $A = 3 if ($A || 0) < 1; $D = 4 if ($D || 0) < 1; $N = 40 if ($N || 0) < 1; use strict; use Graph; use Graph::Directed; use Graph::Undirected; for my $arity (1..$A) { for my $depth (1..$D) { print "# depth=$depth, arity=$arity\n"; # $g=construct(new Graph::Directed,$depth,$arity); my $h=construct(new Graph::Undirected,$depth,$arity); my $t=regular_tree(new Graph::Undirected,$depth,$arity); my $mst1=$h->MST_Kruskal; is($mst1,$t,"Kruskal"); my $mst2=$h->MST_Prim; is($mst2,$t,"Prim"); my $mst3=$h->MST_Dijkstra; is($mst3,$t,"Dijkstra"); # ok(1,"end of tests for depth=$depth, arity=$arity"); } } # do some long chains my $arity=1; for(my $depth=10;$depth<=$N;$depth+=10) { print "# depth=$depth, arity=$arity\n"; # $g=construct(new Graph::Directed,$depth,$arity); my $h=construct(new Graph::Undirected,$depth,$arity); my $t=regular_tree(new Graph::Undirected,$depth,$arity); my $mst1=$h->MST_Kruskal; is($mst1,$t,"Kruskal"); my $mst2=$h->MST_Prim; is($mst2,$t,"Prim"); my $mst3=$h->MST_Dijkstra; is($mst3,$t,"Dijkstra"); # ok(1,"end of tests for depth=$depth, arity=$arity"); } # do some wide stars my $depth=1; for(my $arity=10;$arity<=$N;$arity+=10) { print "# depth=$depth, arity=$arity\n"; # $g=construct(new Graph::Directed,$depth,$arity); my $h=construct(new Graph::Undirected,$depth,$arity); my $t=regular_tree(new Graph::Undirected,$depth,$arity); my $mst1=$h->MST_Kruskal; is($mst1,$t,"Kruskal"); my $mst2=$h->MST_Prim; is($mst2,$t,"Prim"); my $mst3=$h->MST_Dijkstra; is($mst3,$t,"Dijkstra"); # ok(1,"end of tests for depth=$depth, arity=$arity"); } exit; sub construct { my($g, $depth, $arity, $density)=@_; $density or $density=3; # make a tree with edge weights of1 $g=regular_tree($g,$depth,$arity); # add heavier edges my @nodes=$g->vertices; my $new_edges=int $density*@nodes; for (1..$new_edges) { my $i=int rand $#nodes; my $j=int rand $#nodes; next if $g->has_edge($nodes[$i],$nodes[$j]); $g->add_weighted_edge($nodes[$i],$nodes[$j],2); } print "# V = ", scalar $g->vertices, ", E = ", scalar $g->edges, "\n"; return $g; } sub regular_tree { my($tree,$depth,$arity,$root)=@_; defined $root or do { $root=0; $tree->add_vertex($root); }; if ($depth>0) { for (my $i=0; $i<$arity; $i++) { my $child="$root/$i"; $tree->add_vertex($child); $tree->add_weighted_edge($root,$child,1); regular_tree($tree,$depth-1,$arity,$child); } } $tree; } sub is_quiet { my($a,$b,$tag)=@_; return if $a eq $b; is($a,$b,$tag); } sub ok_quiet { my($bool,$tag)=@_; return if $bool; ok($bool,$tag); } sub min { if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];} return undef unless @_; if ($#_==1) {my($x,$y)=@_; return ($x<=$y?$x:$y);} my $min=shift @_; map {$min=$_ if $_<$min} @_; $min; } sub max { if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];} return undef unless @_; if ($#_==1) {my($x,$y)=@_; return ($x>=$y?$x:$y);} my $max=shift @_; map {$max=$_ if $_>$max} @_; $max; } Graph-0.9735/t/00-report-prereqs.t0000644000175000017500000001350613774262024016463 0ustar osboxesosboxes#!perl use strict; use warnings; # This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.020 # THEN modified with more info by Ed J for PDL project use Test::More tests => 1; use ExtUtils::MakeMaker; use File::Spec; # from $version::LAX my $lax_version_re = qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )? | (?:\.[0-9]+) (?:_[0-9]+)? ) | (?: v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )? | (?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)? ) )/x; # hide optional CPAN::Meta modules from prereq scanner # and check if they are available my $cpan_meta = "CPAN::Meta"; my $cpan_meta_pre = "CPAN::Meta::Prereqs"; my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic # Verify requirements? my $DO_VERIFY_PREREQS = 1; sub _max { my $max = shift; $max = ( $_ > $max ) ? $_ : $max for @_; return $max; } sub _merge_prereqs { my ($collector, $prereqs) = @_; # CPAN::Meta::Prereqs object if (ref $collector eq $cpan_meta_pre) { return $collector->with_merged_prereqs( CPAN::Meta::Prereqs->new( $prereqs ) ); } # Raw hashrefs for my $phase ( keys %$prereqs ) { for my $type ( keys %{ $prereqs->{$phase} } ) { for my $module ( keys %{ $prereqs->{$phase}{$type} } ) { $collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module}; } } } return $collector; } my @include = qw( ); my @exclude = qw( ); # Add static prereqs to the included modules list my $static_prereqs = do 't/00-report-prereqs.dd'; # Merge all prereqs (either with ::Prereqs or a hashref) my $full_prereqs = _merge_prereqs( ( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ), $static_prereqs ); # Add dynamic prereqs to the included modules list (if we can) my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml'; if ( $source && $HAS_CPAN_META ) { if ( my $meta = eval { CPAN::Meta->load_file($source) } ) { $full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs); } } else { $source = 'static metadata'; } my @full_reports; my @dep_errors; my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs; # Add static includes into a fake section for my $mod (@include) { $req_hash->{other}{modules}{$mod} = 0; } for my $phase ( qw(configure build test runtime develop other) ) { next unless $req_hash->{$phase}; next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING}); for my $type ( qw(requires recommends suggests conflicts modules) ) { next unless $req_hash->{$phase}{$type}; my $title = ucfirst($phase).' '.ucfirst($type); my @reports = [qw/Module Want Have Where Howbig/]; for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) { next if $mod eq 'perl'; next if grep { $_ eq $mod } @exclude; my $file = $mod; $file =~ s{::}{/}g; $file .= ".pm"; my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC; my $want = $req_hash->{$phase}{$type}{$mod}; $want = "undef" unless defined $want; $want = "any" if !$want && $want == 0; my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required"; if ($prefix) { my $filename = File::Spec->catfile($prefix, $file); my $have = MM->parse_version( $filename ); $have = "undef" unless defined $have; push @reports, [$mod, $want, $have, $prefix, (-s $filename)]; if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) { if ( $have !~ /\A$lax_version_re\z/ ) { push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)"; } elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) { push @dep_errors, "$mod version '$have' is not in required range '$want'"; } } } else { push @reports, [$mod, $want, "missing", '', 0]; if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) { push @dep_errors, "$mod is not installed ($req_string)"; } } } if ( @reports ) { push @full_reports, "=== $title ===\n\n"; my $ml = _max( map { length $_->[0] } @reports ); my $wl = _max( map { length $_->[1] } @reports ); my $hl = _max( map { length $_->[2] } @reports ); my $ll = _max( map { length $_->[3] } @reports ); # location my $sl = _max( map { length $_->[4] } @reports ); # size if ($type eq 'modules') { splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl, "-" x $ll, "-" x $sl]; push @full_reports, map { sprintf(" %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports; } else { splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl, "-" x $ll, "-" x $sl]; push @full_reports, map { sprintf(" %*s %*s %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2], -$ll, $_->[3], $sl, $_->[4]) } @reports; } push @full_reports, "\n"; } } } if ( @full_reports ) { diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports; } if ( @dep_errors ) { diag join("\n", "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n", "The following REQUIRED prerequisites were not satisfied:\n", @dep_errors, "\n" ); } pass; # vim: ts=4 sts=4 sw=4 et: Graph-0.9735/t/00_use.t0000644000175000017500000000010713774262024014340 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 1; use_ok('Graph'); Graph-0.9735/t/08_stringify.t0000644000175000017500000001415514741033474015602 0ustar osboxesosboxesuse strict; use warnings; use Test::More; use Graph::Undirected; use Graph::Directed; my $g0 = Graph::Undirected->new; my $g1 = Graph::Directed->new; is $_, "" for $g0, $g1; ok !$_->has_edge('a') for $g0, $g1; ok !$_->has_edge('a', 'a') for $g0, $g1; my @EDGES = ( [qw(a b)], [qw(a c)], [qw(a d)], [qw(a e)], [qw(a f)], [qw(b c)], [qw(c b)], [qw(b d)], [qw(d b)], [qw(b e)], [qw(e b)], [qw(b f)], [qw(f b)], [qw(d c)], [qw(e c)], [qw(f c)], [qw(d e)], [qw(d f)], [qw(e f)], ); $g0->add_edge(@$_) for @EDGES; $g1->add_edge(@$_) for @EDGES; is(Graph::Undirected->new->add_edges(@EDGES), $g0, 'add_edges equivalence'); is(Graph::Directed->new->add_edges(@EDGES), $g1, 'add_edges equivalence'); is(Graph::Undirected->new(edges=>\@EDGES), $g0, 'new(edges) equivalence'); is(Graph::Directed->new(edges=>\@EDGES), $g1, 'new(edges) equivalence'); ok !$_->has_edge('a') for $g0, $g1; ok !$_->has_edge('a', 'a') for $g0, $g1; ok !$_->has_edge('a', 'x') for $g0, $g1; # undirected, directed my %SUCCESSORS = ( a => [ [qw(b c d e f)], [qw(b c d e f)] ], b => [ [qw(a c d e f)], [qw(c d e f)] ], c => [ [qw(a b d e f)], [qw(b)] ], d => [ [qw(a b c e f)], [qw(b c e f)] ], e => [ [qw(a b c d f)], [qw(b c f)] ], f => [ [qw(a b c d e)], [qw(b c)] ], ); for my $v (sort keys %SUCCESSORS) { my ($u, $d) = @{ $SUCCESSORS{$v} }; for my $t ([$g0, $u], [$g1, $d]) { my ($g, $r) = @$t; is("@{[sort $g->successors($v)]}", "@$r", "successors u $v"); my %expected_edges; @expected_edges{ @$r } = (); my %not_edges; @not_edges{ grep $_ ne $v, $g->vertices } = (); delete @not_edges{ keys %expected_edges }; ok $g->has_edge($v, $_) for keys %expected_edges; ok !$g->has_edge($v, $_) for keys %not_edges; } } my %PREDECESSORS = ( a => [ [qw(b c d e f)], [qw()] ], b => [ [qw(a c d e f)], [qw(a c d e f)] ], c => [ [qw(a b d e f)], [qw(a b d e f)] ], d => [ [qw(a b c e f)], [qw(a b)] ], e => [ [qw(a b c d f)], [qw(a b d)] ], f => [ [qw(a b c d e)], [qw(a b d e)] ], ); for my $v (sort keys %PREDECESSORS) { my ($u, $d) = @{ $PREDECESSORS{$v} }; for my $t ([$g0, $u], [$g1, $d]) { my ($g, $r) = @$t; is("@{[sort $g->predecessors($v)]}", "@$r", "predecessors u $v"); my %expected_edges; @expected_edges{ @$r } = (); my %not_edges; @not_edges{ grep $_ ne $v, $g->vertices } = (); delete @not_edges{ keys %expected_edges }; ok $g->has_edge($_, $v) for keys %expected_edges; ok !$g->has_edge($_, $v) for keys %not_edges; } } is_deeply [ $g0->as_hashes ], [ { map +($_ => {}), keys %SUCCESSORS }, { map +($_ => { map +($_ => {}), @{ $SUCCESSORS{$_}[0] } }), keys %SUCCESSORS }, ], "undirected as_hashes" or diag explain [ $g0->as_hashes ]; is_deeply [ $g1->as_hashes ], [ { map +($_ => {}), keys %SUCCESSORS }, { map +($_ => { map +($_ => {}), @{ $SUCCESSORS{$_}[1] } }), keys %SUCCESSORS }, ], "directed as_hashes" or diag explain [ $g1->as_hashes ]; is($g0, 'a=b,a=c,a=d,a=e,a=f,b=c,b=d,b=e,b=f,c=d,c=e,c=f,d=e,d=f,e=f') for 1..10; is($g1, 'a-b,a-c,a-d,a-e,a-f,b-c,b-d,b-e,b-f,c-b,d-b,d-c,d-e,d-f,e-b,e-c,e-f,f-b,f-c') for 1..10; is $g0->[ Graph::_V ]->stringify, <<'EOF'; Graph::AdjacencyMap::Light arity=1 flags: _LIGHT a 0 b 1 c 2 d 3 e 4 f 5 EOF is $g0->[ Graph::_E ]->stringify, <<'EOF'; Graph::AdjacencyMap::Light arity=2 flags: _UNORD|_LIGHT to: 1 2 3 4 5 0 1 1 1 1 1 1 1 1 1 1 2 1 1 1 3 1 1 4 1 EOF is $g1->[ Graph::_V ]->stringify, <<'EOF'; Graph::AdjacencyMap::Light arity=1 flags: _LIGHT a 0 b 1 c 2 d 3 e 4 f 5 EOF is $g1->[ Graph::_E ]->stringify, <<'EOF'; Graph::AdjacencyMap::Light arity=2 flags: _LIGHT to: 1 2 3 4 5 0 1 1 1 1 1 1 1 1 1 1 2 1 3 1 1 1 1 4 1 1 1 5 1 1 EOF $g1->set_edge_attribute(qw(a b weight 2)); $g1->set_vertex_attribute(qw(a size 2)); is $g1->[ Graph::_V ]->stringify, <<'EOF'; Graph::AdjacencyMap::Light arity=1 flags: _LIGHT a 0,{'size' => '2'} b 1 c 2 d 3 e 4 f 5 EOF is $g1->[ Graph::_E ]->stringify, <<'EOF'; Graph::AdjacencyMap::Light arity=2 flags: _LIGHT to: 1 2 3 4 5 0 {'weight' => '2'} 1 1 1 1 1 1 1 1 1 2 1 3 1 1 1 1 4 1 1 1 5 1 1 EOF my $g2 = Graph::Directed->new(multivertexed => 1, multiedged => 1); $g2->add_edge(qw(a c)); $g2->set_edge_attribute_by_id(qw(a b x weight 2)); $g2->set_vertex_attribute_by_id(qw(a z other 5)); $g2->set_vertex_attribute_by_id(qw(a 0 other2 6)); is $g2->[ Graph::_V ]->stringify, <<'EOF'; Graph::AdjacencyMap arity=1 flags: _MULTI a 0,{'0' => {'other2' => '6'},'z' => {'other' => '5'}} b 2,{'0' => {}} c 1,{'0' => {}} EOF is $g2->[ Graph::_E ]->stringify, <<'EOF'; Graph::AdjacencyMap arity=2 flags: _MULTI to: 1 2 0 {'0' => {}} {'x' => {'weight' => '2'}} EOF for my $cv (0, 1) { my $g3 = Graph::Directed->new(hyperedged => 1, countvertexed => $cv); $g3->add_edge([qw(a c)], [qw(d e f)]); $g3->set_edge_attribute([qw(a c)], [qw(e g)], qw(weight 2)); is $g3->[ Graph::_E ]->stringify, <<'EOF'; Graph::AdjacencyMap arity=0 flags: 0 [[0,1],[2,3,4]] 0 [[0,1],[3,5]] 1,{'weight' => '2'} EOF my $got = [ $g3->as_hashes ]; is_deeply $got->[1], [ { predecessors => [qw(a c)], successors => [qw(d e f)], attributes => {} }, { predecessors => [qw(a c)], successors => [qw(e g)], attributes => { weight => 2 } }, ] or diag explain $got; } { my $null = Graph->new; ok($null, "boolify wins over stringify for empty graph"); ok($g0, "boolify"); } { # Inspired by # rt.cpan.org 93278: SPT_Dijkstra sometimes returns a wrong answer use Graph::Directed; my $null = Graph::Directed->new; for ( 1..5 ) { # Adds _NOTHING_ -- but dies. eval { $null->add_vertex }; like($@, qr/Graph::add_vertex: use add_vertices for more than one vertex/); } is($null, ""); } done_testing; Graph-0.9735/t/41_edges_to.t0000644000175000017500000000207313774262024015346 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 16; use Graph; my $g = Graph->new; $g->add_edge("a", "b"); $g->add_edge("b", "c"); $g->add_edge("c", "d"); $g->add_edge("d", "d"); $g->add_edge("e", "b"); $g->add_edge("c", "f"); $g->add_edge("c", "g"); $g->add_edge("g", "h"); $g->add_edge("h", "g"); sub to { join(" ", sort map { "[" . join(" ", map { ref $_ ? "[@$_]" : $_ } @$_) . "]" } $g->edges_to(@_)); } is( to("a"), ""); is( to("b"), "[a b] [e b]"); is( to("c"), "[b c]"); is( to("d"), "[c d] [d d]"); is( to("e"), ""); is( to("f"), "[c f]"); is( to("g"), "[c g] [h g]"); is( to("h"), "[g h]"); is( to("x"), ""); { use Graph::Directed; my $g1 = new Graph::Directed(); $g1->add_edge(0,0); my @e = $g1->edges_to(0); is(@e, 1); is("@{ $e[0] }", "0 0"); } { my $g2 = new Graph::Directed(); $g2->add_edge(1,1); $g2->add_edge(1,2); my @e1 = $g2->edges_to(1); is(@e1, 1); is("@{ $e1[0] }", "1 1"); my @e2 = $g2->edges_to(2); is(@e2, 1); is("@{ $e2[0] }", "1 2"); my @e3 = $g2->edges_to(3); is(@e3, 0); } Graph-0.9735/t/u_jh_va.t0000644000175000017500000000121513774262024014661 0ustar osboxesosboxesuse Test::More tests => 18; use strict; use warnings; use Graph; my $g = Graph->new; $g->add_path('a'..'d'); ok( $g->has_vertex('a')); ok( $g->has_vertex('b')); ok( $g->has_vertex('c')); ok( $g->has_vertex('d')); $g->delete_vertex('d'); ok( $g->has_vertex('a')); ok( $g->has_vertex('b')); ok( $g->has_vertex('c')); ok(!$g->has_vertex('d')); $g->set_vertex_attribute('b','a',1); ok( $g->delete_vertex('b')); ok( $g->has_vertex('a')); ok(!$g->has_vertex('b')); ok( $g->has_vertex('c')); ok(!$g->has_vertex('d')); ok( $g->delete_vertex('a')); ok(!$g->has_vertex('a')); ok(!$g->has_vertex('b')); ok( $g->has_vertex('c')); ok(!$g->has_vertex('d')); Graph-0.9735/t/26_multiedged.t0000644000175000017500000000703414102052753015675 0ustar osboxesosboxesuse strict; use warnings; use Test::More tests => 61; use Graph; my $g = Graph->new(multiedged => 1); is( $g->get_edge_count('a', 'b'), 0 ); ok( $g->add_edge_by_id('a', 'b', 'red') ); is( $g->get_edge_count('a', 'b'), 1 ); is $g, "a-b"; ok( $g->has_edge_by_id('a', 'b', 'red') ); ok(!$g->has_edge_by_id('a', 'b', 'blue') ); ok( $g->has_edge_by_id('a', 'b', 'red') ); ok(!$g->has_edge_by_id('a', 'b', 'blue') ); $g->add_edge_by_id('a', 'b', 'blue'); is( $g->get_edge_count('a', 'b'), 2 ); ok( $g->has_edge_by_id('a', 'b', 'blue') ); ok( $g->has_edge_by_id('a', 'b', 'red') ); $g->add_edge('a', 'b'); is $g, "a-b"; is( $g->get_edge_count('a', 'b'), 3 ); is( $g->add_edge_get_id('a', 'b'), 1); is( $g->add_edge_get_id('a', 'b'), 2); is( $g->add_edge_get_id('a', 'b'), 3); is( $g->get_edge_count('a', 'b'), 6 ); ok( $g->delete_edge_by_id('a', 'b', 'blue') ); ok(!$g->has_edge_by_id('a', 'b', 'blue') ); ok( $g->has_edge_by_id('a', 'b', 'red') ); ok(!$g->delete_edge_by_id('a', 'b', 'green') ); ok(!$g->has_edge_by_id('a', 'b', 'blue') ); ok( $g->has_edge_by_id('a', 'b', 'red') ); ok(!$g->has_edge_by_id('a', 'b', 'green') ); ok( $g->delete_edge_by_id('a', 'b', 'red') ); my @i = sort $g->get_multiedge_ids('a', 'b'); is("@i", "0 1 2 3"); ok( $g->has_edge_by_id('a', 'b', '0') ); ok( $g->has_edge_by_id('a', 'b', '1') ); ok( $g->has_edge_by_id('a', 'b', '2') ); ok( $g->has_edge_by_id('a', 'b', '3') ); is( $g->get_edge_count('a', 'b'), 4 ); is( $g->delete_edge('a', 'b'), 'a,b' ); ok(!$g->has_edge_by_id('a', 'b', '0') ); ok(!$g->has_edge_by_id('a', 'b', '1') ); ok(!$g->has_edge_by_id('a', 'b', '2') ); ok(!$g->has_edge_by_id('a', 'b', '3') ); is( $g->get_multiedge_ids('a', 'b'), undef ); is( $g->add_edge_get_id('a', 'b'), 0); ok( $g->delete_edge_by_id('a', 'b', 0) ); # exercise deleting last one my $h = Graph->new; eval { $h->add_edge_by_id("b", "c", "black") }; like($@, qr/add_edge_by_id: expected multiedged/); eval { $h->has_edge_by_id("b", "c", "black") }; like($@, qr/has_edge_by_id: expected multiedged/); eval { $h->get_multiedge_ids() }; like($@, qr/get_multiedge_ids: expected multiedged/); eval { $h->delete_edge_by_id("b", "c", "black") }; like($@, qr/delete_edge_by_id: expected multiedged/); $h = Graph->new(multiedged => 1, hyperedged => 1, directed => 0); ok( $h->add_edge_by_id('u', 'v', 'w', 'genghis') ); ok( $h->add_edge_by_id('u', 'khan') ); ok( $h->has_edge('u' ,'v', 'w') ); ok(!$h->has_edge('u' ,'v') ); ok( $h->any_edge('u' ,'v') ); ok( $h->any_edge('u' ,'w') ); ok( $h->any_edge('v' ,'w') ); ok( $h->has_edge('u') ); ok(!$h->has_edge('v') ); ok(!$h->has_edge() ); ok( $h->has_edge_by_id('u', 'v', 'w', 'genghis') ); ok( $h->has_edge_by_id('u', 'khan') ); eval { Graph->new( multiedged => 1, countedged => 1 ) }; like ( $@, qr/both countedged and multiedged/ ); { my $g4a = Graph->new(undirected => 1, multiedged => 1); $g4a->add_edge_get_id("a1","s1"); $g4a->add_edge_get_id("a2","s2"); $g4a->add_edge_get_id("a2","a1"); # Last. my @e = $g4a->edges; is(@e, 3); my $g4b = Graph->new(undirected => 1, multiedged => 1); $g4b->add_edge_get_id("a2","a1"); # First. $g4b->add_edge_get_id("a1","s1"); $g4b->add_edge_get_id("a2","s2"); @e = $g4b->edges; is(@e, 3); } { # rt.cpan.org 107567 edges() missing on undirected multiedged graph my $graph = Graph->new(undirected => 1, multiedged => 1); $graph->add_vertex(0); $graph->add_vertex(1); $graph->add_edge(1,0); is($graph, "0=1"); my @edges = $graph->edges; is_deeply(@edges, [0, 1]) or diag explain \@edges; is($graph->edges, 1); } Graph-0.9735/TODO0000644000175000017500000000252414760400762013311 0ustar osboxesosboxesNote that these are possibilities, not plans nor promises. - graph periphery: the subgraph of graph center vertices - finding the shortest cycle: easy for directed (*), but how about undirected? (*) http://www.cs.auckland.ac.nz/~ute/220ft/graphalg/node14.html do a BFS, when sees an already seen vertex, a cycle of length of at most twice the current depth of the BFS has been found; no cycles => V + 1 (Inf) - bipartite aka 2-colourable: again, BFS: http://www.cs.auckland.ac.nz/~ute/220ft/graphalg/node15.html - Eulerian circuit: Fleury's algorithm: http://planetmath.org/encyclopedia/FleurysAlgorithm.html - could_be_isomorphic() - go second degree? 1. separate external and internal attributes? 2. biconn: add next_root 3. DAG SSSP 4. flow: Ford-Fulkerson/Edmonds-Karp/preflow-push? Floyd-Warshall variant? -- Classics -- Undirected graphs - connectivity - given two vertices are they on a cycle - find_all_paths($u, $v)? - find_all_cycles()? NP-complete; equivalent to finding all Hamiltonians - Euler tour Digraphs - disallow_selfloops => 1? - odd-length cycle - shortest paths (bfs) - squaring a graph: for (u,v)(v,w) add (u,w) unless already there - graph union, graph difference? Various specialized graph constructors? - n-dim grid (with wraps and connectors -> wheels (webs), cones), circle, star, platonic solids, trees, etc. Graph-0.9735/META.yml0000644000175000017500000000144114771263365014077 0ustar osboxesosboxes--- abstract: unknown author: - 'Jarkko Hietaniemi ' build_requires: ExtUtils::MakeMaker: '0' Math::Complex: '0' Test::More: '0.82' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.72, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Graph no_index: directory: - t - inc requires: Heap: '0.80' List::Util: '1.45' Safe: '0' Scalar::Util: '0' Set::Object: '1.40' Storable: '2.05' perl: '5.006' resources: bugtracker: https://github.com/graphviz-perl/Graph/issues repository: git://github.com/graphviz-perl/Graph.git version: '0.9735' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Graph-0.9735/Changes0000644000175000017500000007717014771263257014135 0ustar osboxesosboxes0.9735 2025-03-27 - better connectedness docs (#36 #37) - thanks @gwselke 0.9734 2025-03-01 - add connected_subgraphs (#35) - thanks @merkys 0.9733 2025-01-12 - added max_cliques (#33,#34) - thanks @choroba - restore 0.9716 behaviour of random_graph (#32) - thanks @kester-habermann for report 0.9732 2024-09-02 - delete_vertex_by_id now deletes edges if vertex multiness to 0 - add filter_{vertic,edg}es - {un,}directed_copy no longer use caching mechanism so can mutate copies 0.9731 2024-08-24 - add add_{edges,path}_by_id - fix calling "new" on Graph::Undirected object - make {,{un,}directed_}copy preserve multi{edg,vertex}ed - add {un,}directed_copy_attributes 0.9730 2024-08-22 - add get_edge_attribute_all - make SP_Dijkstra and SP_Bellman_Ford work with multiedged 0.9729 2024-06-28 - add is_planar (#31) - thanks @merkys 0.9728 2024-06-25 - add is_bipartite (#30) - thanks @merkys 0.9727 2023-06-25 - fix biconnectivity to work with refvertexed (#29) - thanks @merkys for report 0.9726 2023-02-11 - fix subgraph of undirected (#28) - thanks @merkys for report 0.9725 2021-10-10 - fix refvertexed which was stringifying not using ref address - thanks @merkys for report 0.9724 2021-09-13 - make deep_copy not interfere with $. - thanks @merkys for report 0.9723 2021-09-01 - doc fixes - thanks @xsawyerx - fix problem with deep_copy with vertices that are refs - thanks @merkys for report 0.9722 2021-07-04 - fix neighbours et al not returning count in scalar context - thanks @merkys for report 0.9721 2021-04-18 - fix BitMatrix and AdjacencyMatrix problems - thanks @dod38fr for report 0.9720 2021-03-25 - better fix - no mutate inputs 0.9719 2021-03-25 - fix all_paths infinite loop on cycle - thanks @tobez for report 0.9718 2021-03-13 - remove doc of deleted average_degree method - thanks @lindleyw for report 0.9717 2021-01-27 - bulk APIs for UnionFind - add unionfind config option for util/grand.pl (benchmark-ish script) - GRAPH_ALLOW_RECURSION env var to turn off recursion protection - "Light" edge-map now uses bit-vectors -> smaller storage - directed hypergraphs - fix same_biconnected_components logic when given >2 vertices 0.9716 2021-01-01 - use Set::Object - {neighbours,successors,predecessors,reachable}_by_radius 0.9715 2020-12-31 - fix AdjacencyMap::Light attributes so delete when path deleted - fix as_hashes undirected edges: now both directions - subgraph_by_radius take multiple vertices 0.9714 2020-12-25 - remove "omni*" - hypergraphs are simply directed or undirected - as_hashes works with undirected hypergraphs - add_edge with != 2 vertices only for undirected hypergraph - any_edge - delete_*_attributes_by_id (and deleting last attribute) now don't destroy that entity - AdjacencyMap::Light can have attributes, so no slowdown if use (eg APSP) 0.9713 2020-12-19 - fix edges_at on self-edges in scalar context - fix refvertexed_stringified predicate - remove "hypervertices": a collection of n vertices is a hyperedge - AdjacencyMap.get_paths_by_ids - transitive_closure et al no longer re-bless objects to Graph - AdjacencyMap.get_ids_by_paths - no more uniqedged configurability - BitMatrix transpose option - Transitive closure records path successor, not predecessor. Method name and docs updated. 0.9712 2020-12-05 - bug-fix: set_edge_attribute_by_id add_edge_by_id if not exist - connected_component_by_index behaves same with/without unionfind - AdjacencyMatrix handle multiedged - reduce redundant sorting for _UNORD, fix AdjacencyMap::Vertex with ID 0 - AdjacencyMap.stringify - allow constructor args to override "prototype" object - fix docs for TransitiveClosure to correctly say path_vertices default true - AdjacencyMatrix now always creates adjacency matrix (clue in name) - remove compat02 features - drop untested scalar-context Traversal.postorder mutation behaviour - much more lazy-loading of modules - set_vertex_attribute_by_id now works on hypervertexed - internal AdjacencyMap uses array not hash for mapping index to path - successors/predecessors/rename_path work right with multivertex - AdjacencyMap array -> stable vertices ordering, TCM performance benefit - TransitiveClosure etc handle multiedged - all_paths ignore self-loops 0.9711 2020-11-27 - ingest handle multivertexed, multiedged right 0.9710 2020-11-27 - all_paths method - as_hashes handle multivertexed, multiedged right 0.9709 2020-11-22 - add path_count option to TransitiveClosure - get_{edge,vertex}_attributes undef if no such entity, in list context - as_hashes method - ingest method 0.9708 2020-11-06 - update metadata for Test::More version dep - stringify hypervertices right - add rename_vertex, rename_vertices 0.9707 2020-10-31 - can't use Safe, ergo Storable, on 5.8 0.9706 2020-10-20 - metadata list test-deps as not runtime 0.9705 2020-10-20 - document clustering_coefficient return empty list on no vertices - RT#114094 - apply lazy-load patch from Stephen Loyd - RT#123236 - depend on Heap 0.80 instead of local fork. Heap::Elem is really an interface not superclass. - fix uninitialised value warning in SP_Dijkstra - RT#118539 - fix complement losing vertices on unconnected graphs - RT#115366 - fix average_path_length when two vertices given - RT#120611 - switch to GitHub issues rather than RT - fix all_successors with non-truthy names - fix #5 - add Graph::Matrix->stringify to help debug - fix APSP_Floyd_Warshall logic error when subpaths totalled 0 - fix #3 - typo fix - thanks @jkeenan (#6) - Added "use strict; use warnings;", etc - thanks @manwar (closes #2) 0.9704 2015-10-07 Jarkko Hietaniemi - rt.cpan.org 107567: edges() missing on undirected multiedged graph: was broken in 0.96, had been fixed somewhere there and here, added the test case - rt.cpan.org 107600: no modify Storable $VERSION 0.9703 2015-09-29 Jarkko Hietaniemi - document (at user level) the openbsd random problem - using the 5.22+ Inf was done the wrong way: https://github.com/neilbowers/Graph/issues/1 0.9702 2015-09-28 Jarkko Hietaniemi - rt.cpan.org 107394 $Storable::VERSION may contain underscores - follow-up to rt.cpan.org 104687: more docs, fixes, and tests for diameter/radius/shortest_path/center_vertices/vertex_eccentricity for corner cases like empty graph, single-vertex graphs, and in general unconnected graphs - for perl 5.22 or later one should be able to use Inf for Infinity - openbsd before perl 5.20 had nondeterministic rand() 0.97 2015-09-22 Jarkko Hietaniemi - rt.cpan.org 104687 diameter and centre of a one vertex graph - rt.cpan.org 107195 [PATCH] fix POD: add missing NAME header - rt.cpan.org 107194 [PATCH] fix a spelling mistake - rt.cpan.org #94046 Loading graph produces a warning with Perl 5.16.3 - rt.cpan.org 62626 Graph::TransitiveClosure::Matrix contradictory wrt reflexive - rt.cpan.org 71793 Problem with APSP and default weight 1 - rt.cpan.org 79143 Graph scalar context override causes problems - rt.cpan.org 92427 Graph::delete_vertex should not use _edges_at (in all cases) - rt.cpan.org 85238 bug in edges() method? - rt.cpan.org 93278 SPT_Dijkstra sometimes returns a wrong answer - rt.cpan.org 78465 find_a_cycle and has_cycle are broken - rt.cpan.org 92204 (longest path is not calculated correctly in this case) - rt.cpan.org 65497 induced subgraph method - plus various doc and code nits found while looking at the above 0.96_01 2014-03-09 @NEILB - Taken over maintenance from JHI - Specified min perl version 5.6.0 - Tweaked COPYRIGHT and LICENSE in pod to match usual form - Added "use warnings", but that results in loads of warnings about functions redefined. So added "no warnings 'redefine';". Have to come back and work that one out! - Set all VERSION's to 0.96_01. I suspect a switch to Dist::Zilla might be coming soon... - Updated README to acknowledge change in maintainer - Reformatted as per CPAN::Changes::Spec 0.96 2013-05-24 Jarkko Hietaniemi - Mop-up release for 0.95. Still is and will be unsupported. 0.95 2013-05-23 Jarkko Hietaniemi - Address rt.cpan.org #85449: "Graph-0.94 tests fail under perl 5.18.0" - Address rt.cpan.org #82324: "Test failures due to hash randomisation in perl 5.17.6" The two above fixes were the same: the biconnectedness code was rewritten from scratch. The new code behaves differently (but I believe more correctly) on certain edge cases, in general it will generate more biconnected components and bridges, for example for "a=b=c" it will now return the same two biconnected components and bridges (cut edges), namely "a=b" and "b=c", the "b" of course being the articulation point (cut vertex). - Address rt.cpan.org #67213: "[PATCH] pod fixes" - Remove the t/u_bo.t and t/u_bo1.t since they die in 5.18 due to some strange failure, looks unrelated to Graph as such, probably some fix/change made by newer Perls. 0.94 2010-03-13 Jarkko Hietaniemi - Address rt.cpan.org #43580: "Reversed logic on overload::StrVal() in AdjacencyMap::Vertex::__set_path" Had to add a new option, refvertexed_stringified. - Address rt.cpan.org #50210: "Graph-0.91: bug in unionfind parameter" One cannot delete from a unionfind graph: now enforcing that. - Address rt.cpan.org #48090: "all_reachable on directed $g->add_edges(['a','b'],['b','a'])" Now if there are loops, all_reachable() will include the initial vertices themselves. Also all_neighbors() had some problems in certain cases, fixed those too. - Address rt.cpan.org #50210: "Graph-0.91: bug in unionfind parameter" One cannot delete edges or vertices from a unionfind graph: now enforce that in code. - Address rt.pcan.org #42549: "stron" Document that strongly connected components will include isolated and sink and source vertices. 0.93 2010-03-07 Jarkko Hietaniemi - Revert the SPTHeapElem.pm change made in Graph 0.92, installing Heap 0.80 broke Graph. Better be conservative. 0.92 2010-03-03 Jarkko Hietaniemi - Address rt.cpan.org #55912 "Broken links in the documentation" - Address rt.cpan.org #55196 "Heap 0.80 compatibility fix" - Add copyright and clearer license statement. 0.91 2009-01-16 Jarkko Hietaniemi - Minor documentation cleanups. - Add 'use strict;' to lib/Graph/TransitiveClosure.pm. - Modernize the META.yml. 0.90 2008-12-29 Jarkko Hietaniemi - Storable deparse of coderefs for deep_copy() does not work at all with 5.6.2: if modern enough Storable and B::Deparse are not available, fall back to the previous version which used Data::Dumper. 0.89 2008-12-27 Jarkko Hietaniemi - Some PAUSE upload problem with 0.88, retrying. 0.88 2008-12-26 Jarkko Hietaniemi - The 0.87 forgot to specify the Storable (and Safe, used in the deserialization step of deep_copy) prerequirement(s) in Makefile.PL. 0.87 2008-12-26 Jarkko Hietaniemi - Addressed a performance problem in successors() and predecessors(), reported by Jonathan Moore. - Reimplement deep_copy() by using Storable freeze() and thaw() instead of Data::Dumper, inspired by Jonathan Moore. Probably now safer and faster, but Storable is now a prerequirement. 0.86 2008-11-27 Jarkko Hietaniemi - Addressed a performance problem in connected_components() for 1000+ vertex graphs, reported by David Grobe. Should in general speed up graph traversal. 0.85 2008-11-27 Jarkko Hietaniemi - Address rt.cpan.org #31608 "Graph::Undirected, unionfind and connected_component" - Address rt.cpan.org #34377 "recursive successors and predecessors" (added all_successors/all_predecessors/all_neighbours/all_reachable) - Address rt.cpan.org #39444 "inconsistent return value" (make add_edges and add_vertices to always return the graph) - Address rt.cpan.org #39614 "copy should retain more attributes" (now copies also refvertexed/hypervertexed/countvertexed/ multivertexed/hyperedged/countedged/multiedged/omniedged) - Address rt.cpan.org #39805 "UnionFind: Repeated adds clobbers graph component information" - Address rt.cpan.org #41190 "add_edge_by_id on multigraph malfunctioning" - Added betweenness(), clustering_coefficient(), and subgraph_by_radius(), contributed by Matt Spear. 0.84 2007-08-13 Jarkko Hietaniemi - Tels found one more attributed edge problem. 0.83 2007-08-12 Jarkko Hietaniemi - One test in 73_diameter.t had too many possible answers, dependent on the hash ordering, removed the test. 0.82 2007-08-11 Jarkko Hietaniemi - Since Heap 0.80 broke Graph, as a stop-gap measure I will include the Heap::Elem and Heap::Fibonacci of Heap 0.71, renamed as 'Heap071', addresses rt.cpan.org #26943: "Heap 0.80 breaks Graph", and numerous bug reports by email - Address rt.cpan.org #27840: "add-edge_attributes() on undirected graph wrongly depends on node order", from Tels - Address rt.cpan.org #27959: "radius method incorrect", code and test case from ROSULEK. 0.81 2007-01-21 Jarkko Hietaniemi - Address rt.cpan.org #24417: "next_successor unavailable in Traversal (PATCH)", from Ted Carnahan. - Small pod tweaks. - Minor internal cleanup for the caching code. 0.80 2006-09-10 Jarkko Hietaniemi - SP_Bellman_Ford() used actually SPT_Dijkstra(), not SPT_Bellman_Ford(), noticed by "aramos". This changed some regression test results a bit. - The NAME line of Graph::Undirected said "directed graphs", noticed by "Ruslan", the first one to notice this in two years, including the author yours truly... - Add Scalar::Util to the prereqs listed in Makefile.PL since one of the tests uses refaddr, shouldn't be a problem because List::Util already is a prereq. - Fix few broken intra-pod links. 0.79 2006-08-06 Jarkko Hietaniemi - The u_bo_ap1.t wasn't really testing the same for 20 times, which meant that one bug was waiting for Koen van der Drift. (If one start vertex of biconnectivity search was a self-loop, an empty list of articulation points was returned.) - Add a new API family: $g->..._clear_cache(), which allows one to forget a cached value such as biconnectivity(). Without clearing the cache once the result has been computed it stays the same (until the graph is modified, of course). If the cache is cleared, (pseudo)randomness is applied again, and the algorithm results may become different. 0.78 2006-07-16 Jarkko Hietaniemi - Address rt.cpan.org #20476: "SPT_Bellman_Ford does not respect refvertexed" - now fixed, and SPT_Dijkstra() had the same problem. 0.77 2006-07-08 Jarkko Hietaniemi - Address rt.cpan.org #20185: "problem with SPT_Bellman_Ford", SPT_Bellman_Ford() was broken for undirected graphs (they were handled as directed ones, therefore missing vertices). - weakly_connected_component_by_vertex() usage example was wrong, was using weakly_connected_component(), noted by 'yanick' in annocpan.org. - Implement and document the saving of the SPT_Dijkstra() and SPT_Bellman_Ford() start vertices. - Document add_edges() alternative API. - Aerate the Changes by adding empty lines between the * items. 0.76 2006-06-28 Jarkko Hietaniemi - Problem found by Xiaoli Zheng in diameter(): adding vertices did not change diameter, this was due to a deeper bug where the transitive closure matrix was being cached wrong: sometimes saved too long, sometimes recomputed too often. Enhanced 73_diameter.t to detect this. - Problem found by Andree Toonk and Ronald van der Pol: SP_Dijkstra() tried too hard to find a path between vertices even if there was none - and returned rubbish. Added t/u_at3.t to detect this. - Address rt.cpan.org #20021: "bridges() sometimes returns empty list when isolated vertices present". biconnectivity() did not work right if isolated vertices were picked as roots, it either hung or returned empty. Added t/u_bill.t to detect this. - Document that add_vertex() is often unnecessary. - Directed.pm and Undirected.pm were missing "use strict". 0.75 2006-06-09 Jarkko Hietaniemi - Had accidentally removed Digest::MD5 from the Makefile.PL prereq list, found by Anton Berezin (using Perl 5.6.2, which doesn't include that), solved by removing the dependency to Digest::MD5. - Speeded up repeated longest/shortest paths computations by using a cached (Floyd-Warshall) transitive closure. - Implemented: - graph radius - graph center (vertices) - vertex eccentricity 0.74 2006-05-31 Jarkko Hietaniemi - Bug in SP_Dijkstra() found by Andree Toonk and Ronald van der Pol. The edge weights of the Dijkstra shortest paths graph were not cumulative (if the whole graph is a-b = 1 and b-c = 2, a-c should be 3), which caused the SP_Dijsktra() results sometimes to be nonsense. Two test cases, one of them rather large (about 5000 edges). - Bugs when using references as vertices in bridges() and (Traversal) seen() found by Brian Osborne. - Added another articulation_points() test by Brian Obsorne. - Explicitly disallow adding undef as a vertex. 0.73 2006-05-27 Jarkko Hietaniemi - Still one bug hiding in articulation points: if the (randomly chosen) first vertex was a self-loop, an empty list was returned for articulation points. t/u_bo_ap.t now tests the test case from Brian Osborne 20 times to stress test more cases, and extra five tests testing self-loops and articulation points. 0.72 2006-05-27 Jarkko Hietaniemi - Brian Osborne found a graph where articulation_points() ended up in an infinite loop. Resolved and the graph test case added as t/u_bo_ap.t. 0.71 2006-05-22 Jarkko Hietaniemi - Tweak the pod-coverage.t so that it looks more like Test::Pod::Coverage documentation suggests in this case. - Fix the u_bo.t not to have a test class with a broken stringification method to avoid spurious warnings and failure (also do away with the use of Math::Complex to avoid problems because of different Math::Complex releases), and more even importantly fix the "next_root" logic in connected components not to advance to the next component if there is nothing to advance to. This seems to be prone to failure in 5.6.2, for some reason 5.8.8 works fine. - Test under Perl 5.6.2. - Force has_cycle() to return true/false, not the list of edges, reported by Casey Bergman. 0.70 2006-05-21 Jarkko Hietaniemi - delete_vertex() from a refvertexed graph left an unnecessary reference to the referenced vertex hanging around in the graph, reported by Christoph Lamprecht. - Implement new 'super_component' option for connected_graph(), biconnected_graph(), and strongly_connected_graph(), to allow more complex ways of forming 'supercomponents' (and more customized ways of naming them). - Address rt.cpan.org #17159: "Nodes appear to unblessed after using articulation_points() - 2" (elaboration of rt.cpan.org #17108: "Nodes appear to unblessed after using articulation_points())" - Address rt.cpan.org #17160: "Nodes appear to unblessed after using connected_components()" - Address rt.cpan.org #17161: "Nodes appear to unblessed after using bridges()" - Address rt.cpan.org #17162: "Nodes appear to unblessed after using connected_graph()" - Address rt.cpan.org #17163, "SP_Dijkstra() is complaining" - Address rt.cpan.org #17164, "SP_Bellman_Ford() is complaining" - Address rt.cpan.org #17165, documentation error in SP_Bellman_Ford(). - Address rt.cpan.org #17405: "has_cycle with empty args should return FALSE" - Address rt.cpan.org: #17592: "articulation_points doesn't find all vertices" (didn't find all the vertices of non-connected graphs, only the vertices of the first (randomly chosen) connected subgraph) The rt.cpan.org cases 17159-17592 reported by Brian Obsorne. - Add Test::Pod and Test::Pod::Coverage tests. 0.69 2005-12-06 Jarkko Hietaniemi - Add SP_Dijkstra() and SP_Bellman_Ford() to find the shortest path between any two vertices, the result is returned as the list of the vertices in the path. - In addition to the SPT per vertex result weight, also add a predecessor ('p') vertex attribute (the SP_Dijkstra() and SP_Bellman_Ford() unsurprisingly use this.) - Cache the SPT results for better speed. - Document that the SPT also allow a single argument as the starting (root) vertex. - Fix a bug in SPT_Dijkstra() which would ignore an "untrue" vertex (such as '0') if it was any other vertex than the root vertex (boolean context is dangerous, when you really mean "exists"). - For "components" (strongly, biconnected, and connected) graphs store the list of the original vertices as a vertex attribute 'subvertices' (so there is no need to do split(/\+/, ...) tricks), the list is stored as a array reference. 0.68 2005-11-23 Jarkko Hietaniemi - SPT_Dijkstra() wasn't setting the vertex attributes of the result graph, noticed by Susan Tang, only the edge attributes were being set. SPT_Bellman_Ford() was doing neither! - There was an actual typo in the SPT test case from Sedgewick, a weight of 0.32 was mistyped as 0.22, this luckily didn't affect the result graph but it of course affected the resulting vertex 'weight' attributes. - Add tests to t/70_spt.t for the vertex and edge attributes of the SPT_Dijkstra() and SPT_Bellman_Ford() results. - Minor documentation tweaks, most importantly clarify the return value of the SPT_Dijkstra() and SPT_Bellman_Ford(). - Document that Perl 5.6.0 is the minimum (because of weak references) and also make Graph.pm require that (Makefile.PL was already doing the probing using Scalar::Util qw(weaken)). - Add an early test (02_trap.t) for catching the development-time-only setting of __DIE__ and __WARN__ handlers (as a result of this almost all the numbered tests were renumbered, so the diff is falsely gigantic). (If the handlers were mistakenly left turned on, a lot of later tests that checked the $@ got confusing failures.) 0.67 2005-08-03 Jarkko Hietaniemi - The 0.66 add_edge_get_id() fix was not yet quite right, Tels found another problem with it. Now with another fix, and another test case (t/u_te_ae.t) - Documentation fixes from John P. Linderman. 0.66 2005-07-20 Jarkko Hietaniemi - Fix [rt.cpan.org #13193] "Documentation error in set_edge_attributes" and [rt.cpan.org #13194] "Documentation error in set_edge_attributes" (duplicate report) - Fixes for problems listed in [rt.cpan.org #13195] "add_vertex_get_id/add_edge_get_id() return wrong result on first call" - add_edge_get_id() was returning an array reference instead of the id with the first call (the array reference was the ids of the vertices of the edge) - add_vertex_get_id() was even more broken (a multivertexed graph was using Graph::AdjacencyMap::Vertex for the vertex map, not Graph::AdjacencyMap::Heavy) - Added test t/u_te_me.t for the two above issues. - document in which order multiedge ids are returned (random) - require Data::Dumper only for deep_copy() and _dump() (not changes for two listed items, "check directly multiedged via a flag" and "remove returns for speed" because I have issues with speed hacks without actual measurements, and even if so would fear reduced maintainability) - Fix [rt.cpan.org #13352] "Dijkstra heap logic" Dijkstra was fine, the SPTHealElem cmp() routine was wrong in having no tie breakers in case the weights compared equal. Added test t/u_re_sd.t. 0.65 2005-05-15 Jarkko Hietaniemi - Tests added to 64_ref.t to verify that using different kinds of blessed references as vertices works okay. Few bugs found by these tests squashed. 0.64 2005-05-14 Jarkko Hietaniemi - Fix for [rt.cpan.org #12509] "Errors using objects as nodes", patch from the reporter of the bug, add t/u/bb_rv.t. - Fix for refvertexed isolated vertices not having overloaded cmp and graph string presentation failing because of that. - The s needed to be Bs. 0.63 2005-04-16 Jarkko Hietaniemi - After setting a vertex attribute one could not delete non-attributed vertices, reported by Joseph Hamilton. - Inlining to speed up path_vertices() slightly. 0.62 2005-04-10 Jarkko Hietaniemi - The documentation of add_weighted_vertices was wrong: the arguments are (v1, w1, v2, w2, ...) instead of (v1, v2, ..., w). - Made calling interfaces with an "options hash" like new() and random_graph() more robust, now bails out earlier instead of dieing mysteriously later with an "odd number of arguments" - Allow running under -d:DProf even when using random shuffling: workaround for List::Util::shuffle and -d:DProf not working together ([perl #32383]) by falling back to Fisher-Yates shuffle if (any use of) the -d: is detected. - Allow calling random_graph() also as a class method: Graph::random_graph(...) (the resulting graph will be a 'Graph'). - in_degree() and out_degree() (and therefore vertex_degree()) were one too low for self-loop vertices in undirected graphs (the self-loop edge was not counted). 0.61 2005-03-27 Jarkko Hietaniemi - [rt.cpan.org #12023] from Macha Nikolski: deleting an attributed vertex left the graph in a state where has_vertex() returned correctly false but vertices() still wrongly returned the freshly deleted vertex. - A few missing "See":s added to the pod. 0.60 2005-03-25 Jarkko Hietaniemi - Bug reported by Richard Ball: connected_component_by_index() and connected_component_by_vertex() were starting their indexing from one, not zero. - t/27_hyperedged.t was really testing for turning on hypervertexedness (the actual functionality was being tested correctly in t/32_hyperedge.t). 0.59 2005-03-03 Jarkko Hietaniemi - deep_copy_graph() could not handle code references since Data::Dumper by default doesn't handle those. Now uses the Deparse option for 5.8.x and later. - The removed interfaces add_graph() and delete_graph() still had their documentation hanging around. 0.58 2005-02-19 Jarkko Hietaniemi - Document that using attributes does have a slowing down effect on other graph operations [rt.cpan.org #11498] "Performance problem: edge attributes slow source_vertices" This is unlikely to get fixed any time soon, I am afraid, this is one of those working-as-designed-and-correctly-but- unfortunately-slow things. - Document that Graph 0.2xxx edges($v) is now edges_at($v) [rt.cpan.org #11494] - [rt.cpan.org #11543]: self-edges reported twice by edges_at(). - Declare/document that any attributes beginning with an underscore are reserved for the internal use of Graph. - Various inlining optimizations: should run 5-10% faster than the 0.57. 0.57 2005-02-12 Jarkko Hietaniemi - Further 10% speedup on 'make test' on top of 0.56 by inlining various code paths related to finding edges, now 'make test' is cumulatively about 15% faster than the 0.55 release. The test case of [rt.cpan.org #11465] is about 10 times faster. 0.56 2005-02-12 Jarkko Hietaniemi - Rewrite edges finding code (like edges_at()) to avoid a quadratic algorithm. Shame on me. Luckily this extremely slow path was not touched that often, but [rt.cpan.org #11465] shows one known bad case, source_vertices() for compat02 graphs. The removal of the slow path sped up 'make test' by about 5-10%. - Remove a voodoo keys() from vertices_at(). - Document stubs for Graph::Directed and Graph::Undirected. - Tiny documentation tweaks. 0.55 2005-01-22 Jarkko Hietaniemi - Add unset_row(), get_row(), set_row(), and unset_row(), methods to Graph::BitMatrix and make it public (remove the "internal use only" warning from it). Add t/82_bitmatrix.t. - Add vertex_degree() as an alias for degree(). - One more alternative solution for spt.t from Koen. - I seem to have this drive to misspell people's names. Sorry, Koen. 0.54 2005-01-16 Jarkko Hietaniemi - More bugs found in set_vertex_attribute(), fixed and tests added. (Basically the same failure pattern as with the [rt.cpan.org #9461]: after setting vertex attributes many of the 'structural' methods such as predecessors() often returned wrong results.) - More alternative solutions to spt.t, diameter.t, and dump.t, found by the PRNG of Koen van der Drift in Mac OS X 10.3.7, Perl 5.8.1. 0.53 2005-01-14 Jarkko Hietaniemi - The #9461 was still there. But now we have a simple test case from Sebastian Nagel. The real culprit seemed to be a misapplied optimisation. 0.52 2005-01-12 Jarkko Hietaniemi - Fix set_graph_attribute() documentation not to talk about $u, $v (noticed by Kurt Jaeger). - A mysterious failure fixed by a mysterious fix: under some circumstances it seems that an each() doesn't walk through all the key-value pairs, the workaround is to reset the each() iterator by a keys() call. Not simple test code, sadly, since the existing test code (see the case) is 13 kB and non-trivial. [rt.cpan.org #9461] - Add a safety guard against a missing Scalar::Util::weaken [rt.cpan.org #9481] 0.51 2005-01-09 Jarkko Hietaniemi - Allow calling Makefile.PL with arguments other than --renum (which is for internal use only, and therefore undocumented). [rt.cpan.org #9481] - Remove the add_graph() and delete_graph() interfaces, sorry if you were already using them, but the current interface was very poor and the concept ill-planned. If you want to merge or remove edges and vertices between your graph, you can probably yourself implement the exactly right things to do. [rt.cpan.org #9493] - Document that one cannot assume Graphs are blessed hash references (and the likely error message one will get if one so assumes). [rt.cpan.org #9505] - Fix Andras' last name (sorry). - Merge duplicate documentation of find_a_cycle(). - Graph::AdjacencyMap::Base does not exist, fix Graph/AdjacencyMap.pm pod to comply. 0.50 2005-01-01 Jarkko Hietaniemi - Unknown contents 0.001 1998-05-04 - First release to CPAN Graph-0.9735/lib/0000755000175000017500000000000014771263365013374 5ustar osboxesosboxesGraph-0.9735/lib/Graph/0000755000175000017500000000000014771263365014435 5ustar osboxesosboxesGraph-0.9735/lib/Graph/TransitiveClosure/0000755000175000017500000000000014771263365020122 5ustar osboxesosboxesGraph-0.9735/lib/Graph/TransitiveClosure/Matrix.pm0000644000175000017500000002516714741033474021730 0ustar osboxesosboxespackage Graph::TransitiveClosure::Matrix; use strict; use warnings; use Graph::AdjacencyMatrix; use Graph::Matrix; use Scalar::Util qw(weaken); use List::Util qw(min); sub _A() { 0 } # adjacency sub _D() { 1 } # distance sub _S() { 2 } # successors sub _V() { 3 } # vertices sub _G() { 4 } # the original graph (OG) sub _new { my ($g, $class, $am_opt, $want_transitive, $want_reflexive, $want_path, $want_path_vertices, $want_path_count) = @_; my $m = Graph::AdjacencyMatrix->new($g, %$am_opt); my @V = $g->vertices; my %v2i; @v2i{ @V } = 0..$#V; # paths are in array -> stable ordering my $am = $m->adjacency_matrix; $am->[1] = \%v2i; my ($dm, @di); # The distance matrix. my ($sm, @si); # The successor matrix. # directly use (not via API) arrays of bit-vectors etc for speed. # the API is so low-level it adds no clarity anyway my @ai = @{ $am->[0] }; my $multi = $g->multiedged; unless ($want_transitive) { $dm = $m->distance_matrix || Graph::Matrix->new($g); # if no distance_matrix in AM, we make our own if ($want_path_count) { # force defined @di = map [ (0) x @V ], 0..$#V; } else { @di = @{ $dm->[0] }; } $sm = Graph::Matrix->new($g); $dm->[1] = $sm->[1] = \%v2i; @si = @{ $sm->[0] }; for (my $iu = $#V; $iu >= 0; $iu--) { vec($ai[$iu], $iu, 1) = 1 if $want_reflexive; for (my $iv = $#V; $iv >= 0; $iv--) { next unless vec($ai[$iu], $iv, 1); if ($want_path_count or !defined $di[$iu][$iv]) { $di[$iu][$iv] = $iu == $iv ? 0 : 1; } elsif ($multi and ref($di[$iu][$iv]) eq 'HASH') { $di[$iu][$iv] = min values %{ $di[$iu][$iv] }; } $si[$iu]->[$iv] = $V[$iv] unless $iu == $iv; } } } # naming here is u = start, v = midpoint, w = endpoint for (my $iv = $#V; $iv >= 0; $iv--) { my $div = $di[$iv]; my $aiv = $ai[$iv]; for (my $iu = $#V; $iu >= 0; $iu--) { my $aiu = $ai[$iu]; next if !vec($aiu, $iv, 1); if ($want_transitive) { for (my $iw = $#V; $iw >= 0; $iw--) { return 0 if $iw != $iv && vec($aiv, $iw, 1) && !vec($aiu, $iw, 1); } next; } my $aiuo = $aiu; $aiu |= $aiv; if ($aiu ne $aiuo) { $ai[$iu] = $aiu; $aiv = $aiu if $iv == $iu; } next if !$want_path; my $diu = $di[$iu]; my $d1a = $diu->[$iv]; for (my $iw = $#V; $iw >= 0; $iw--) { next unless vec($aiv, $iw, 1); if ($want_path_count) { $diu->[$iw]++ if $iu != $iv and $iv != $iw and $iw != $iu; next; } my $d0 = $diu->[$iw]; my $d1b = $div->[$iw]; my $d1 = $d1a + $d1b; if (!defined $d0 || ($d1 < $d0)) { # print "d1 = $d1a ($V[$iu], $V[$iv]) + $d1b ($V[$iv], $V[$iw]) = $d1 ($V[$iu], $V[$iw]) (".(defined$d0?$d0:"-").") (propagate=".($aiu ne $aiuo?1:0).")\n"; $diu->[$iw] = $d1; $si[$iu]->[$iw] = $si[$iu]->[$iv] if $want_path_vertices; } } } } return 1 if $want_transitive; my %V; @V{ @V } = @V; $am->[0] = \@ai; $dm->[0] = \@di if defined $dm; $sm->[0] = \@si if defined $sm; weaken(my $og = $g); bless [ $am, $dm, $sm, \%V, $og ], $class; } sub new { my ($class, $g, %opt) = @_; my %am_opt = (distance_matrix => 1); $am_opt{attribute_name} = delete $opt{attribute_name} if exists $opt{attribute_name}; $am_opt{distance_matrix} = delete $opt{distance_matrix} if $opt{distance_matrix}; $opt{path_length} = $opt{path_vertices} = delete $opt{path} if exists $opt{path}; my $want_path_length = delete $opt{path_length}; my $want_path_count = delete $opt{path_count}; my $want_path_vertices = delete $opt{path_vertices}; my $want_reflexive = delete $opt{reflexive}; $am_opt{is_transitive} = my $want_transitive = delete $opt{is_transitive} if exists $opt{is_transitive}; Graph::_opt_unknown(\%opt); $want_reflexive = 1 unless defined $want_reflexive; my $want_path = $want_path_length || $want_path_vertices || $want_path_count; # $g->expect_dag if $want_path; $am_opt{distance_matrix} = 0 if $want_path_count; _new($g, $class, \%am_opt, $want_transitive, $want_reflexive, $want_path, $want_path_vertices, $want_path_count); } sub has_vertices { my $tc = shift; for my $v (@_) { return 0 unless exists $tc->[ _V ]->{ $v }; } return 1; } sub is_reachable { my ($tc, $u, $v) = @_; return undef unless $tc->has_vertices($u, $v); return 1 if $u eq $v; $tc->[ _A ]->get($u, $v); } sub is_transitive { return __PACKAGE__->new($_[0], is_transitive => 1) if @_ == 1; # Any graph # A TC graph my ($tc, $u, $v) = @_; return undef unless $tc->has_vertices($u, $v); $tc->[ _A ]->get($u, $v); } sub vertices { my $tc = shift; values %{ $tc->[3] }; } sub path_length { my ($tc, $u, $v) = @_; return undef unless $tc->has_vertices($u, $v); return 0 if $u eq $v; $tc->[ _D ]->get($u, $v); } sub path_successor { my ($tc, $u, $v) = @_; return undef if $u eq $v; return undef unless $tc->has_vertices($u, $v); $tc->[ _S ]->get($u, $v); } sub path_vertices { my ($tc, $u, $v) = @_; return unless $tc->is_reachable($u, $v); return wantarray ? () : 0 if $u eq $v; my @v = ( $u ); while ($u ne $v) { last unless defined($u = $tc->path_successor($u, $v)); push @v, $u; } $tc->[ _S ]->set($u, $v, [ @v ]) if @v; return @v; } sub all_paths { my ($tc, $u, $v, $seen) = @_; return if $u eq $v; $seen ||= {}; return if exists $seen->{$u}; $seen = { %$seen, $u => undef }; # accumulate, but don't mutate my @found; push @found, [$u, $v] if $tc->[ _G ]->has_edge($u, $v); push @found, map [$u, @$_], map $tc->all_paths($_, $v, $seen), grep $tc->is_reachable($_, $v), grep $_ ne $v && $_ ne $u, $tc->[ _G ]->successors($u); @found; } 1; __END__ =pod =head1 NAME Graph::TransitiveClosure::Matrix - create and query transitive closure of graph =head1 SYNOPSIS use Graph::TransitiveClosure::Matrix; use Graph::Directed; # or Undirected my $g = Graph::Directed->new; $g->add_...(); # build $g # Compute the transitive closure matrix. my $tcm = Graph::TransitiveClosure::Matrix->new($g); # Being reflexive is the default, # meaning that null transitions are included. my $tcm = Graph::TransitiveClosure::Matrix->new($g, reflexive => 1); $tcm->is_reachable($u, $v) # is_reachable(u, v) is always reflexive. $tcm->is_reachable($u, $v) # The reflexivity of is_transitive(u, v) depends on the reflexivity # of the transitive closure. $tcg->is_transitive($u, $v) my $tcm = Graph::TransitiveClosure::Matrix->new($g, path_length => 1); my $n = $tcm->path_length($u, $v) my $tcm = Graph::TransitiveClosure::Matrix->new($g, path_vertices => 1); my @v = $tcm->path_vertices($u, $v) my $tcm = Graph::TransitiveClosure::Matrix->new($g, attribute_name => 'length'); my $n = $tcm->path_length($u, $v) my @v = $tcm->vertices =head1 DESCRIPTION You can use C to compute the transitive closure matrix of a graph and optionally also the minimum paths (lengths and vertices) between vertices, and after that query the transitiveness between vertices by using the C and C methods, and the paths by using the C and C methods. If you modify the graph after computing its transitive closure, the transitive closure and minimum paths may become invalid. =head1 Methods =head2 Class Methods =over 4 =item new($g) Construct the transitive closure matrix of the graph $g. =item new($g, options) Construct the transitive closure matrix of the graph $g with options as a hash. The known options are =over 8 =item C => I By default the edge attribute used for distance is C. You can change that by giving another attribute name with the C attribute to the new() constructor. =item reflexive => boolean By default the transitive closure matrix is not reflexive: that is, the adjacency matrix has zeroes on the diagonal. To have ones on the diagonal, use true for the C option. =item path => boolean If set true, sets C and C. If either of those are true (and C is by default), then both are calculated. =item path_length => boolean By default "false", but see above as overridden by default C being true. If calculated, they can be retrieved using the path_length() method. =item path_vertices => boolean By default the paths are computed, with the boolean transitivity, they can be retrieved using the path_vertices() method. =item path_count => boolean As an alternative to setting C, if this is true then the matrix will store the quantity of paths between the two vertices. This is still retrieved using the path_length() method. The path vertices will not be available. You should probably only use this on a DAG, and not with C. =back =back =head2 Object Methods =over 4 =item is_reachable($u, $v) Return true if the vertex $v is reachable from the vertex $u, or false if not. =item path_length($u, $v) Return the minimum path length from the vertex $u to the vertex $v, or undef if there is no such path. =item path_vertices($u, $v) Return the minimum path (as a list of vertices) from the vertex $u to the vertex $v, or an empty list if there is no such path, OR also return an empty list if $u equals $v. =item has_vertices($u, $v, ...) Return true if the transitive closure matrix has all the listed vertices, false if not. =item is_transitive($u, $v) Return true if the vertex $v is transitively reachable from the vertex $u, false if not. =item vertices Return the list of vertices in the transitive closure matrix. =item path_successor($u, $v) Return the successor of vertex $u in the transitive closure path towards vertex $v. =item all_paths($u, $v) Return list of array-refs with all the paths from $u to $v. Will ignore self-loops. =back =head1 RETURN VALUES For path_length() the return value will be the sum of the appropriate attributes on the edges of the path, C by default. If no attribute has been set, one (1) will be assumed. If you try to ask about vertices not in the graph, undefs and empty lists will be returned. =head1 ALGORITHM The transitive closure algorithm used is Warshall and Floyd-Warshall for the minimum paths, which is O(V**3) in time, and the returned matrices are O(V**2) in space. =head1 SEE ALSO L =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut Graph-0.9735/lib/Graph/Traversal.pm0000644000175000017500000003251114741033474016731 0ustar osboxesosboxespackage Graph::Traversal; use strict; use warnings; # $SIG{__DIE__ } = \&Graph::__carp_confess; # $SIG{__WARN__} = \&Graph::__carp_confess; sub reset { my $self = shift; require Set::Object; $self->{ unseen } = Set::Object->new($self->{ graph }->vertices); $self->{ seen } = Set::Object->new; $self->{ order } = [ ]; $self->{ preorder } = [ ]; $self->{ postorder } = [ ]; $self->{ roots } = [ ]; $self->{ tree } = Graph->new(directed => $self->{ graph }->directed); delete $self->{ terminate }; } sub _see { my $self = shift; $self->see; } sub has_a_cycle { my ($u, $v, $t, $s) = @_; $s->{ has_a_cycle } = 1; $t->terminate; } sub find_a_cycle { my ($u, $v, $t, $s) = @_; my @cycle = ( $u ); push @cycle, $v unless $u eq $v; my $path = $t->{ order }; if (@$path) { my $i = $#$path; while ($i >= 0 && $path->[ $i ] ne $v) { $i-- } if ($i >= 0) { unshift @cycle, @{ $path }[ $i+1 .. $#$path ]; } } $s->{ a_cycle } = \@cycle; $t->terminate; } my @KNOWN_CONFIG = qw( tree_edge seen_edge next_alphabetic next_numeric next_random has_a_cycle find_a_cycle ); my @EXTRACT_CONFIG = qw( pre post pre_vertex post_vertex pre_edge post_edge back_edge down_edge cross_edge non_tree_edge first_root next_root next_successor ); sub new { my ($class, $g, %attr) = @_; Graph::__carp_confess("Graph::Traversal: first argument is not a Graph") unless ref $g && $g->isa('Graph'); my $self = bless { graph => $g, state => { } }, $class; $self->reset; if (exists $attr{ start }) { $attr{ first_root } = delete $attr{ start }; $attr{ next_root } = undef; } my @found_known = grep exists $attr{$_}, @EXTRACT_CONFIG; @$self{@found_known} = delete @attr{@found_known}; $self->{ seen_edge } = $attr{ seen_edge } if exists $attr{ seen_edge } and ($g->multiedged || $g->countedged); $self->{ pre_edge } = $attr{ tree_edge } if exists $attr{ tree_edge }; my $default_next = $attr{ next_alphabetic } ? \&Graph::_next_alphabetic : $attr{ next_numeric } ? \&Graph::_next_numeric : \&Graph::_next_random; $self->{ next_root } = $default_next if !exists $self->{ next_root }; $self->{ first_root } = $self->{ next_root } if !exists $self->{ first_root }; $self->{ next_successor } = $default_next if !exists $self->{ next_successor }; if (exists $attr{ has_a_cycle }) { $self->{ back_edge } = my $has_a_cycle = ref $attr{ has_a_cycle } eq 'CODE' ? $attr{ has_a_cycle } : \&has_a_cycle; $self->{ down_edge } = $has_a_cycle if $g->is_undirected; } if (exists $attr{ find_a_cycle }) { $self->{ back_edge } = my $find_a_cycle = ref $attr{ find_a_cycle } eq 'CODE' ? $attr{ find_a_cycle } : \&find_a_cycle; $self->{ down_edge } = $find_a_cycle if $g->is_undirected; } $self->{ add } = \&add_order; $self->{ see } = \&_see; delete @attr{@KNOWN_CONFIG}; Graph::_opt_unknown(\%attr); return $self; } sub terminate { my $self = shift; $self->{ terminate } = 1; } sub add_order { my ($self, @next) = @_; push @{ $self->{ order } }, @next; } sub visit { my ($self, @next) = @_; $self->{ unseen }->remove(@next); $self->{ seen }->insert(@next); $self->{ add }->( $self, @next ); return unless my $p = $self->{ pre }; $p->( $_, $self ) for @next; } sub visit_preorder { my ($self, @next) = @_; push @{ $self->{ preorder } }, @next; $self->{ preordern }->{ $_ } = $self->{ preorderi }++ for @next; $self->visit( @next ); } sub visit_postorder { my ($self) = @_; my @post = reverse $self->{ see }->( $self ); push @{ $self->{ postorder } }, @post; $self->{ postordern }->{ $_ } = $self->{ postorderi }++ for @post; if (my $p = $self->{ post }) { $p->( $_, $self ) for @post; } return unless (my $p = $self->{ post_edge }) and defined(my $u = $self->current); $p->( $u, $_, $self, $self->{ state }) for @post; } sub _callbacks { my ($self, $current, @all) = @_; return unless @all; my $nontree = $self->{ non_tree_edge }; my $back = $self->{ back_edge }; my $down = $self->{ down_edge }; my $cross = $self->{ cross_edge }; my $seen = $self->{ seen_edge }; my $bdc = defined $back || defined $down || defined $cross; return unless (defined $nontree || $bdc || defined $seen); my $u = $current; my $preu = $self->{ preordern }->{ $u }; my $postu = $self->{ postordern }->{ $u }; for my $v ( @all ) { if (!$self->{tree}->has_edge($u, $v) && (defined $nontree || $bdc) && exists $self->{ seen }->{ $v }) { $nontree->( $u, $v, $self, $self->{ state }) if $nontree; if ($bdc) { my $postv = $self->{ postordern }->{ $v }; if ($back && (!defined $postv || $postv >= $postu)) { $back ->( $u, $v, $self, $self->{ state }); } else { my $prev = $self->{ preordern }->{ $v }; if ($down && $prev > $preu) { $down ->( $u, $v, $self, $self->{ state }); } elsif ($cross && $prev < $preu) { $cross->( $u, $v, $self, $self->{ state }); } } } } next if !$seen; my $c = $self->graph->get_edge_count($u, $v); $seen->( $u, $v, $self, $self->{ state } ) while $c-- > 1; } } sub next { my $self = shift; return undef if $self->{ terminate }; my @next; while ($self->seeing) { my $current = $self->current; my $next = Set::Object->new($self->{ graph }->successors($current)); my @all = $next->members; $next = $next->difference($self->{seen}); if ($next->size) { @next = $self->{ next_successor }->( $self, { map +($_=>$_), $next->members } ); $self->{ tree }->add_edges(map [$current, $_], @next); last unless my $p = $self->{ pre_edge }; $p->($current, $_, $self, $self->{ state }) for @next; last; } else { $self->visit_postorder; } return undef if $self->{ terminate }; $self->_callbacks($current, @all); } unless (@next) { if (!@{ $self->{ roots } } and defined(my $first = $self->{ first_root })) { return unless @next = ref $first eq 'CODE' ? $first->( $self, { map +($_=>$_), $self->unseen } ) : $first; } return if !@next and !$self->{ next_root }; return if !@next and !(@next = $self->{ next_root }->( $self, { map +($_=>$_), $self->unseen } )); return if !defined $next[0] or $self->{ seen }->contains($next[0]); # Sanity check. push @{ $self->{ roots } }, $next[0]; } $self->visit_preorder( @next ) if @next; return $next[0]; } sub _order { my ($self, $order) = @_; 1 while defined $self->next; @{ $self->{ $order } }; } sub preorder { my $self = shift; $self->_order( 'preorder' ); } sub postorder { my $self = shift; $self->_order( 'postorder' ); } sub unseen { my $self = shift; $self->{ unseen }->${ wantarray ? \'members' : \'size' }; } sub seen { my $self = shift; $self->{ seen }->${ wantarray ? \'members' : \'size' }; } sub seeing { my $self = shift; @{ $self->{ order } }; } sub roots { my $self = shift; @{ $self->{ roots } }; } sub is_root { my ($self, $v) = @_; for my $u (@{ $self->{ roots } }) { return 1 if $u eq $v; } return 0; } sub tree { my $self = shift; $self->{ tree }; } sub graph { my $self = shift; $self->{ graph }; } sub vertex_by_postorder { my ($self, $i) = @_; exists $self->{ postorder } && $self->{ postorder }->[ $i ]; } sub postorder_by_vertex { my ($self, $v) = @_; exists $self->{ postordern } && $self->{ postordern }->{ $v }; } sub postorder_vertices { my ($self, $v) = @_; exists $self->{ postordern } ? %{ $self->{ postordern } } : (); } sub vertex_by_preorder { my ($self, $i) = @_; exists $self->{ preorder } && $self->{ preorder }->[ $i ]; } sub preorder_by_vertex { my ($self, $v) = @_; exists $self->{ preordern } && $self->{ preordern }->{ $v }; } sub preorder_vertices { my ($self, $v) = @_; exists $self->{ preordern } ? %{ $self->{ preordern } } : (); } sub has_state { my ($self, $var) = @_; exists $self->{ state } && exists $self->{ state }->{ $var }; } sub get_state { my ($self, $var) = @_; exists $self->{ state } ? $self->{ state }->{ $var } : undef; } sub set_state { my ($self, $var, $val) = @_; $self->{ state }->{ $var } = $val; return 1; } sub delete_state { my ($self, $var) = @_; delete $self->{ state }->{ $var }; delete $self->{ state } unless keys %{ $self->{ state } }; return 1; } 1; __END__ =pod =head1 NAME Graph::Traversal - traverse graphs =head1 SYNOPSIS Don't use Graph::Traversal directly, use Graph::Traversal::DFS or Graph::Traversal::BFS instead. use Graph; my $g = Graph->new; $g->add_edge(...); use Graph::Traversal::...; my $t = Graph::Traversal::...->new($g, %opt); $t->... =head1 DESCRIPTION You can control how the graph is traversed by the various callback parameters in the C<%opt>. In the parameters descriptions below the $u and $v are vertices, and the $self is the traversal object itself. =head2 Callback parameters The following callback parameters are available: =over 4 =item tree_edge Called when traversing an edge that belongs to the traversal tree. Called with arguments ($u, $v, $self). =item non_tree_edge Called when an edge is met which either leads back to the traversal tree (either a C, a C, or a C). Called with arguments ($u, $v, $self). =item pre_edge Called for edges in preorder. Called with arguments ($u, $v, $self). =item post_edge Called for edges in postorder. Called with arguments ($u, $v, $self). =item back_edge Called for back edges. Called with arguments ($u, $v, $self). =item down_edge Called for down edges. Called with arguments ($u, $v, $self). =item cross_edge Called for cross edges. Called with arguments ($u, $v, $self). =item pre =item pre_vertex Called for vertices in preorder. Called with arguments ($v, $self). =item post =item post_vertex Called for vertices in postorder. Called with arguments ($v, $self). =item first_root Called when choosing the first root (start) vertex for traversal. Called with arguments ($self, $unseen) where $unseen is a hash reference with the unseen vertices as keys. If not supplied, defaults to the same as C. =item next_root Called when choosing the next root (after the first one) vertex for traversal (useful when the graph is not connected). Called with arguments ($self, $unseen) where $unseen is a hash reference with the unseen vertices as keys. If you want only the first reachable subgraph to be processed, set the next_root to C. =item start Identical to defining C and undefining C. =item next_alphabetic Set this to true if you want the vertices to be processed in alphabetic order (and leave first_root/next_root undefined). =item next_numeric Set this to true if you want the vertices to be processed in numeric order (and leave first_root/next_root undefined). =item next_successor Called when choosing the next vertex to visit. Called with arguments ($self, $next) where $next is a hash reference with the possible next vertices as keys. Use this to provide a custom ordering for choosing vertices, as opposed to C or C. =back The parameters C and C have a 'hierarchy' of how they are determined: if they have been explicitly defined, use that value. If not, use the value of C, if that has been defined. If not, use the value of C, if that has been defined. If not, the next vertex to be visited is chosen randomly. =head2 Methods The following methods are available: =over 4 =item unseen Return the unseen vertices in random order. =item seen Return the seen vertices in random order. =item seeing Return the active fringe vertices in random order. =item preorder Return the vertices in preorder traversal order. =item postorder Return the vertices in postorder traversal order. =item vertex_by_preorder $v = $t->vertex_by_preorder($i) Return the ith (0..$V-1) vertex by preorder. =item preorder_by_vertex $i = $t->preorder_by_vertex($v) Return the preorder index (0..$V-1) by vertex. =item vertex_by_postorder $v = $t->vertex_by_postorder($i) Return the ith (0..$V-1) vertex by postorder. =item postorder_by_vertex $i = $t->postorder_by_vertex($v) Return the postorder index (0..$V-1) by vertex. =item preorder_vertices Return a hash with the vertices as the keys and their preorder indices as the values. =item postorder_vertices Return a hash with the vertices as the keys and their postorder indices as the values. =item tree Return the traversal tree as a graph. =item has_state $t->has_state('s') Test whether the traversal has state 's' attached to it. =item get_state $t->get_state('s') Get the state 's' attached to the traversal (C if none). =item set_state $t->set_state('s', $s) Set the state 's' attached to the traversal. =item delete_state $t->delete_state('s') Delete the state 's' from the traversal. =back =head2 Special callbacks If in a callback you call the special C method, the traversal is terminated, no more vertices are traversed. =head1 SEE ALSO L, L =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut Graph-0.9735/lib/Graph/AdjacencyMatrix.pm0000644000175000017500000001157014741033474020036 0ustar osboxesosboxespackage Graph::AdjacencyMatrix; use strict; use warnings; use Graph::BitMatrix; use Graph::Matrix; use base 'Graph::BitMatrix'; use Graph::AdjacencyMap qw(:flags :fields); sub _AM () { 0 } sub _DM () { 1 } sub _V () { 2 } # Graph::_V sub _E () { 3 } # Graph::_E sub new { my ($class, $g, %opt) = @_; my @V = $g->vertices; my $want_distance = delete $opt{distance_matrix}; my $d = Graph::_defattr(); if (exists $opt{attribute_name}) { $d = delete $opt{attribute_name}; $want_distance++; } my $want_transitive = delete $opt{is_transitive}; Graph::_opt_unknown(\%opt); my $m = Graph::BitMatrix->new($g); my $self = bless [ $m, undef, \@V ], $class; return $self if !$want_distance; my $n = $self->[ _DM ] = Graph::Matrix->new($g); $n->set($_, $_, 0) for @V; my $n0 = $n->[0]; my $n1 = $n->[1]; my $undirected = $g->is_undirected; my $multiedged = $g->multiedged; for my $e ($g->edges) { my ($u, $v) = @$e; $n->set($u, $v, $multiedged ? _multiedged_distances($g, $u, $v, $d) : $g->get_edge_attribute($u, $v, $d) ); $n->set($v, $u, $multiedged ? _multiedged_distances($g, $v, $u, $d) : $g->get_edge_attribute($v, $u, $d) ) if $undirected; } $self; } sub _multiedged_distances { my ($g, $u, $v, $attr) = @_; my %r; for my $id ($g->get_multiedge_ids($u, $v)) { my $w = $g->get_edge_attribute_by_id($u, $v, $id, $attr); $r{$id} = $w if defined $w; } keys %r ? \%r : undef; } sub adjacency_matrix { $_[0]->[ _AM ] } sub distance_matrix { $_[0]->[ _DM ] } sub vertices { @{ $_[0]->[ _V ] } } sub is_adjacent { my ($m, $u, $v) = @_; $m->[ _AM ]->get($u, $v) ? 1 : 0; } sub distance { my ($m, $u, $v) = @_; defined $m->[ _DM ] ? $m->[ _DM ]->get($u, $v) : undef; } 1; __END__ =pod =head1 NAME Graph::AdjacencyMatrix - create and query the adjacency matrix of graph G =head1 SYNOPSIS use Graph::AdjacencyMatrix; use Graph::Directed; # or Undirected my $g = Graph::Directed->new; $g->add_...(); # build $g my $am = Graph::AdjacencyMatrix->new($g); $am->is_adjacent($u, $v) my $am = Graph::AdjacencyMatrix->new($g, distance_matrix => 1); $am->distance($u, $v) my $am = Graph::AdjacencyMatrix->new($g, attribute_name => 'length'); $am->distance($u, $v) my $am = Graph::AdjacencyMatrix->new($g, ...); my @V = $am->vertices(); $g = Graph->new(multiedged => 1); $g->add_...(); # build $g $am = Graph::AdjacencyMatrix->new($g, distance_matrix => 1); $am->distance($u, $v) # returns hash-ref of ID => distance =head1 DESCRIPTION You can use C to compute the adjacency matrix and optionally also the distance matrix of a graph, and after that query the adjacencyness between vertices by using the C method, or query the distance between vertices by using the C method. By default the edge attribute used for distance is C, but you can change that in new(), see below. If you modify the graph after creating the adjacency matrix of it, the adjacency matrix and the distance matrix may become invalid. =head1 Methods =head2 Class Methods =over 4 =item new($g) Construct the adjacency matrix of the graph $g. =item new($g, options) Construct the adjacency matrix of the graph $g with options as a hash. The known options are =over 8 =item distance_matrix => boolean By default only the adjacency matrix is computed. To compute also the distance matrix, use the attribute C with a true value to the new() constructor. =item attribute_name => attribute_name By default the edge attribute used for distance is C. You can change that by giving another attribute name with the C attribute to new() constructor. Using this attribute also implicitly causes the distance matrix to be computed. =back =back =head2 Object Methods =over 4 =item is_adjacent($u, $v) Return true if the vertex $v is adjacent to vertex $u, or false if not. =item distance($u, $v) Return the distance between the vertices $u and $v, or C if the vertices are not adjacent. If the underlying graph is multiedged, returns hash-ref of ID mapped to distance. If a given edge ID does not have the attribute defined, it will not be represented. If no edge IDs have the attribute, C will be returned. =item adjacency_matrix Return the adjacency matrix itself (a list of bitvector scalars). =item vertices Return the list of vertices (useful for indexing the adjacency matrix). =back =head1 ALGORITHM The algorithm used to create the matrix is two nested loops, which is O(V**2) in time, and the returned matrices are O(V**2) in space. =head1 SEE ALSO L, L =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut Graph-0.9735/lib/Graph/Attribute.pm0000644000175000017500000000516713774262024016740 0ustar osboxesosboxespackage Graph::Attribute; use strict; use warnings; my @API = qw(get_attribute get_attributes set_attribute set_attributes has_attribute has_attributes delete_attribute delete_attributes get_attribute_names get_attribute_values); sub import { my $package = shift; my %attr = @_; my $caller = caller(0); if (exists $attr{array}) { my $i = $attr{array}; no strict 'refs'; *{"${caller}::_g_get_attributes"} = sub { $_[0]->[ $i ] }; *{"${caller}::_g_set_attributes"} = sub { $_[0]->[ $i ] ||= { }; $_[0]->[ $i ] = $_[1] if @_ == 2; $_[0]->[ $i ] }; *{"${caller}::_g_has_attributes"} = sub { defined $_[0]->[ $i ] }; *{"${caller}::_g_delete_attributes"} = sub { undef $_[0]->[ $i ]; 1 }; } elsif (exists $attr{hash}) { my $k = $attr{hash}; no strict 'refs'; *{"${caller}::_g_get_attributes"} = sub { $_[0]->{ $k } }; *{"${caller}::_g_set_attributes"} = sub { $_[0]->{ $k } ||= { }; $_[0]->{ $k } = $_[1] if @_ == 2; $_[0]->{ $k } }; *{"${caller}::_g_has_attributes"} = sub { defined $_[0]->{ $k } }; *{"${caller}::_g_delete_attributes"} = sub { delete $_[0]->{ $k } }; } else { # uncoverable statement die "Graph::Attribute::import($package @_) caller $caller\n"; } if (exists $attr{map}) { my $map = $attr{map}; for my $api (@API) { my ($first, $rest) = ($api =~ /^(\w+?)_(.+)/); no strict 'refs'; *{"${caller}::${first}_${map}_${rest}"} = \&$api; } } } sub set_attribute { my $g = shift; my $v = pop; my $a = pop; my $p = $g->_g_set_attributes; $p->{ $a } = $v; return 1; } sub set_attributes { my $g = shift; my $a = pop; my $p = $g->_g_set_attributes( $a ); return 1; } sub has_attribute { my $g = shift; my $a = pop; my $p = $g->_g_get_attributes; $p ? exists $p->{ $a } : 0; } sub has_attributes { my $g = shift; $g->_g_get_attributes ? 1 : 0; } sub get_attribute { my $g = shift; my $a = pop; my $p = $g->_g_get_attributes; $p ? $p->{ $a } : undef; } sub delete_attribute { my $g = shift; my $a = pop; return 0 unless defined(my $p = $g->_g_get_attributes); delete $p->{ $a }; return 1; } sub delete_attributes { my $g = shift; return 0 if !$g->_g_has_attributes; $g->_g_delete_attributes; return 1; } sub get_attribute_names { my $g = shift; my $p = $g->_g_get_attributes; defined $p ? keys %{ $p } : ( ); } sub get_attribute_values { my $g = shift; my $p = $g->_g_get_attributes; defined $p ? values %{ $p } : ( ); } sub get_attributes { $_[0]->_g_get_attributes; } 1; Graph-0.9735/lib/Graph/TransitiveClosure.pm0000644000175000017500000001053614741024740020452 0ustar osboxesosboxespackage Graph::TransitiveClosure; use strict; use warnings; # COMMENT THESE OUT FOR TESTING AND PRODUCTION. # $SIG{__DIE__ } = \&Graph::__carp_confess; # $SIG{__WARN__} = \&Graph::__carp_confess; use base 'Graph'; use Graph::TransitiveClosure::Matrix; sub _G () { Graph::_G() } sub new { my ($class, $g, %opt) = @_; Graph::__carp_confess(__PACKAGE__."->new given non-Graph '$g'") if !(ref $g and $g->isa('Graph')); %opt = (path_vertices => 1) unless %opt; # No delete $opt{ attribute_name } since we need to pass it on. my $attr = exists $opt{ attribute_name } ? $opt{ attribute_name } : Graph::_defattr(); $opt{ reflexive } = 1 unless exists $opt{ reflexive }; my $tcg = $g->new( multiedged => 0, ($opt{ reflexive } ? (vertices => [$g->vertices]) : ()), ); my $tcm = $g->_check_cache('transitive_closure_matrix', [], \&_transitive_closure_matrix_compute, %opt); my $tcm00 = $tcm->[0][0]; # 0=am, 0=bitmatrix my $tcm01 = $tcm->[0][1]; # , 1=hash mapping v-name to the offset into dm data structures (in retval of $g->vertices) my @edges; for my $u ($tcm->vertices) { my $tcm00i = $tcm00->[ $tcm01->{ $u } ]; for my $v ($tcm->vertices) { next if $u eq $v && ! $opt{ reflexive }; my $j = $tcm01->{ $v }; push @edges, [$u, $v] if vec($tcm00i, $j, 1); # $tcm->is_transitive($u, $v) # $tcm->[0]->get($u, $v) } } $tcg->add_edges(@edges); $tcg->set_graph_attribute('_tcm', [ $g->[ _G ], $tcm ]); bless $tcg, $class; } sub _transitive_closure_matrix_compute { Graph::TransitiveClosure::Matrix->new(@_); } sub is_transitive { my $g = shift; $g->expect_no_args(@_); Graph::TransitiveClosure::Matrix::is_transitive($g); } sub transitive_closure_matrix { $_[0]->get_graph_attribute('_tcm')->[1]; } 1; __END__ =pod =head1 NAME Graph::TransitiveClosure - create and query transitive closure of graph =head1 SYNOPSIS use Graph::TransitiveClosure; use Graph::Directed; # or Undirected my $g = Graph::Directed->new; $g->add_...(); # build $g # Compute the transitive closure graph. my $tcg = Graph::TransitiveClosure->new($g); $tcg->is_reachable($u, $v) # Identical to $tcg->has_edge($u, $v) # Being reflexive is the default, meaning that null transitions # (transitions from a vertex to the same vertex) are included. my $tcg = Graph::TransitiveClosure->new($g, reflexive => 1); my $tcg = Graph::TransitiveClosure->new($g, reflexive => 0); # is_reachable(u, v) is always reflexive. $tcg->is_reachable($u, $v) # You can check any graph for transitivity. $g->is_transitive() my $tcg = Graph::TransitiveClosure->new($g, path_length => 1); $tcg->path_length($u, $v) # path_vertices is on by default so this is a no-op. my $tcg = Graph::TransitiveClosure->new($g, path_vertices => 1); $tcg->path_vertices($u, $v) # see how many paths exist from $u to $v my $tcg = Graph::TransitiveClosure->new($g, path_count => 1); $tcg->path_length($u, $v) # Both path_length and path_vertices. my $tcg = Graph::TransitiveClosure->new($g, path => 1); $tcg->path_vertices($u, $v) $tcg->length($u, $v) my $tcg = Graph::TransitiveClosure->new($g, attribute_name => 'length'); $tcg->path_length($u, $v) =head1 DESCRIPTION You can use C to compute the transitive closure graph of a graph and optionally also the minimum paths (lengths and vertices) between vertices, and after that query the transitiveness between vertices by using the C and C methods, and the paths by using the C and C methods. For further documentation, see the L. =head2 Class Methods =over 4 =item new($g, %opt) Construct a new transitive closure object. Note that strictly speaking the returned object is not a graph; it is a graph plus other stuff. But you should be able to use it as a graph plus a couple of methods inherited from the Graph::TransitiveClosure::Matrix class. =back =head2 Object Methods These are only the methods 'native' to the class: see L for more. =over 4 =item is_transitive($g) Return true if the Graph $g is transitive. =item transitive_closure_matrix Return the transitive closure matrix of the transitive closure object. =back =cut Graph-0.9735/lib/Graph/AdjacencyMap.pm0000644000175000017500000003604614741033474017314 0ustar osboxesosboxespackage Graph::AdjacencyMap; use strict; use warnings; # $SIG{__DIE__ } = \&Graph::__carp_confess; # $SIG{__WARN__} = \&Graph::__carp_confess; my $empty = {}; sub _empty () { $empty } my (@FLAGS, %FLAG_COMBOS, %FLAG2I, @FIELDS); BEGIN { @FLAGS = qw(_COUNT _MULTI _UNORD _REF _UNIONFIND _LIGHT _STR); %FLAG_COMBOS = ( _COUNTMULTI => [qw(_COUNT _MULTI)], _REFSTR => [qw(_REF _STR)], ); # Next id, Flags, Arity, Index to path, path to index, # successors, predecessors: 2-level hashes to array-ref of path IDs # attributes - two-level for MULTI, node/multi count @FIELDS = qw(_n _f _arity _i _pi _s _p _attr _count); for my $i (0..$#FLAGS) { my $n = $FLAGS[$i]; my $f = 1 << $i; $FLAG2I{$n} = $f; no strict 'refs'; *$n = sub () { $f }; *{"_is$n"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f } for my $k (keys %FLAG_COMBOS) { my $f = 0; $f |= $_ for map $FLAG2I{$_}, @{ $FLAG_COMBOS{$k} }; no strict 'refs'; *$k = sub () { return $f }; # return to dodge pointless 5.22 stricture *{"_is$k"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f } for my $i (0..$#FIELDS) { no strict 'refs'; *{ $FIELDS[$i] }= sub () { $i }; } } sub _new { my ($class, $flags, $arity) = @_; my $hyper = !$arity; my $need_s = $arity != 1; my $need_p = $need_s && !($flags & _UNORD); bless [ 0, $flags, $arity, [], {}, ($need_s ? {} : undef), ($need_p ? {} : undef), [], [], ], $class; } require Exporter; use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); @ISA = qw(Exporter); %EXPORT_TAGS = (flags => [@FLAGS, keys %FLAG_COMBOS, qw(_GEN_ID)], fields => \@FIELDS); @EXPORT_OK = map @$_, values %EXPORT_TAGS; my $_GEN_ID = 0; sub _GEN_ID () { \$_GEN_ID } sub stringify { my ($f, $arity, $m) = (@{ $_[0] }[ _f, _arity ], $_[0]); my ($multi, @rows) = $f & _MULTI; my @p = $m->paths; @p = $arity == 1 ? sort @p : map $_->[0], sort { $a->[1] cmp $b->[1] } ($arity == 0 && !($f & _UNORD)) ? map [$_, join '|', map "@$_", @$_], @p : map [$_,"@$_"], @p; # use the Schwartz if ($arity == 2) { require Set::Object; my ($pre, $suc, @s) = (Set::Object->new(map $_->[0], @p), Set::Object->new(map $_->[1], @p)); @rows = ([ 'to:', @s = sort $suc->members ], map { my $p = $_; [ $p, map { my $text = defined(my $id = $m->has_path([$p, $_])) ? 1 : ''; my $attrs = !$text ? undef : $multi ? $m->[ _attr ][$id] : $m->_get_path_attrs([$p, $_]); defined $attrs ? $m->_dumper($attrs) : $text; } @s ]; } sort $pre->members); } else { @rows = map { my $attrs = $multi ? $m->[ _attr ][ $m->has_path($_) ] : $m->_get_path_attrs($_); [ $m->_dumper($_), ($m->get_ids_by_paths([ $_ ], 0))[0]. (!defined $attrs ? '' : ",".$m->_dumper($attrs)) ]; } @p; } join '', map "$_\n", "@{[ref $m]} arity=$arity flags: @{[_stringify_fields($m->[ _f ])]}", map join(' ', map sprintf('%4s', $_), @$_), @rows; } sub _stringify_fields { return '0' if !$_[0]; join '|', grep $_[0] & $FLAG2I{$_}, @FLAGS; } sub _dumper { my (undef, $got) = @_; return $got if defined $got and !ref $got; require Data::Dumper; my $dumper = Data::Dumper->new([$got]); $dumper->Indent(0)->Terse(1); $dumper->Sortkeys(1) if $dumper->can("Sortkeys"); $dumper->Dump; } sub has_any_paths { scalar keys %{ $_[0]->[ _pi ] }; } sub _set_path_attr_common { push @_, 0; my ($i) = &__set_path; my $attr = (my $m = $_[0])->[ _attr ]; ($m->[ _f ] & _MULTI) ? \$attr->[ $i ]{ $_[2] } : \$attr->[ $i ]; } sub _set_path_attrs { ${ &{ $_[0]->can('_set_path_attr_common') } } = $_[-1]; } sub _set_path_attr { ${ &{ $_[0]->can('_set_path_attr_common') } }->{ $_[-2] } = $_[-1]; } sub set_paths { map +($_[0]->__set_path($_, 1))[0], @_[1..$#_]; } sub set_path_by_multi_id { push @_, 1; goto &__set_path; } sub __set_path { my $inc_if_exists = pop; &__arg; my ($f, $a, $map_i, $pi, $map_s, $map_p, $m, $k, $id) = (@{ $_[0] }[ _f, _arity, _i, _pi, _s, _p ], @_); my $is_multi = $f & _MULTI; my $k_orig = $k; $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k); my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k"; if (exists $pi->{ $l }) { return ($pi->{ $l }) if !($inc_if_exists and ($f & _COUNTMULTI)); my $nc = \$m->[ _count ][ my $i = $pi->{ $l } ]; $$nc++, return ($i) if !$is_multi; my $na = $m->[ _attr ][ $i ]; if ($id eq _GEN_ID) { $$nc++ while exists $na->{ $$nc }; $id = $$nc; } $na->{ $id } = { }; return ($i, $id); } $map_i->[ $pi->{ $l } = my $i = $m->[ _n ]++ ] = $k_orig; $m->[ _attr ][ $i ] = { ($id = ($id eq _GEN_ID) ? 0 : $id) => {} } if $is_multi; $m->[ _count ][ $i ] = $is_multi ? 0 : 1 if ($f & _COUNTMULTI); _successors_add($f, $a, $map_s, $map_p, $i, $k) if $map_s; # dereffed ($i, $id); } sub _successors_add { my ($f, $a, $map_s, $map_p, $id, $path) = @_; my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path); push @{ $map_s->{ $_->[0] }{ $_->[1] } }, $id for @$pairs; return if !$map_p; push @{ $map_p->{ $_->[1] }{ $_->[0] } }, $id for @$pairs; } sub _successors_del { my ($f, $a, $map_s, $map_p, $id, $path) = @_; my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path); for (@$pairs) { my ($p, $s) = @$_; my @new = grep $_ != $id, @{ $map_s->{ $p }{ $s } }; if (@new) { $map_s->{ $p }{ $s } = \@new; $map_p->{ $s }{ $p } = \@new if $map_p; next; } delete $map_s->{ $p }{ $s }; delete $map_s->{ $p } if !keys %{ $map_s->{ $p } }; next if !$map_p; delete $map_p->{ $s }{ $p }; delete $map_p->{ $s } if !keys %{ $map_p->{ $s } }; } } sub _successors_cartesian { my ($unord, $hyper, $seq) = @_; return [ $seq ] if !$unord and !$hyper; return [] if $unord and $hyper and !@$seq; my ($allow_self, $p_s, $s_s, @pairs); if ($unord) { require Set::Object; my @a = Set::Object->new(@$seq)->members; ($allow_self, $p_s, $s_s) = (@a < 2, \@a, \@a); } else { ($allow_self, $p_s, $s_s) = (1, @$seq); } for my $p (@$p_s) { push @pairs, map [$p, $_], $allow_self ? @$s_s : grep $p != $_, @$s_s; } \@pairs; } sub _get_path_count { return 0 unless my ($i) = &__has_path; my $f = (my $m = $_[0])->[ _f ]; return ($f & _COUNT) ? $m->[ _count ][ $i ] : ($f & _MULTI) ? scalar keys %{ $m->[ _attr ][ $i ] } : 1; } sub has_path { ( &__has_path )[0]; } sub has_path_by_multi_id { return undef unless my ($i) = &__has_path; return exists $_[0]->[ _attr ][ $i ]{ $_[2] }; } sub del_path { return unless my ($i, $l) = &__has_path; return 1 if &_is_COUNT and --$_[0][ _count ][ $i ] > 0; $_[0]->_sequence_del($i, $l); 1; } sub del_path_by_multi_id { return unless my ($i, $l) = &__has_path; delete((my $attrs = (my $m = $_[0])->[ _attr ][ $i ])->{ $_[2] }); return 1 if keys %$attrs; $m->_sequence_del($i, $l); 1; } sub get_multi_ids { return unless ((my $m = $_[0])->[ _f ] & _MULTI) and my ($i) = &__has_path; keys %{ $m->[ _attr ][ $i ] }; } sub rename_path { my ($m, $from, $to) = @_; return 1 if $m->[ _arity ] != 1; # all integers, no names return unless my ($i, $l) = $m->__has_path($from); $m->[ _i ][ $i ] = $to; $to = __strval($to, $m->[ _f ]) if ref($to) and ($m->[ _f ] & _REF); $m->[ _pi ]{ $to } = delete $m->[ _pi ]{ $l }; return 1; } sub _del_path_attrs { return unless my ($i) = &__has_path; my $attr = (my $m = $_[0])->[ _attr ]; return $attr->[ $i ]{ $_[2] } = undef, 1 if ($m->[ _f ] & _MULTI); delete $attr->[ $i ]; } sub __has_path { &__arg; my ($f, $a, $pi, $k) = (@{ $_[0] }[ _f, _arity, _pi ], $_[1]); $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k); my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k"; my $id = $pi->{ $l }; (defined $id ? $id : return, $l); } sub _get_path_attrs { return unless my ($i) = &__has_path; my $attrs = (my $m = $_[0])->[ _attr ][ $i ]; ($m->[ _f ] & _MULTI) ? $attrs->{ $_[2] } : $attrs; } sub _has_path_attrs { keys %{ &{ $_[0]->can('_get_path_attrs') } || return undef } ? 1 : 0; } sub _has_path_attr { exists(( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] }); } sub _get_path_attr { ( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] }; } sub _get_path_attr_names { keys %{ &{ $_[0]->can('_get_path_attrs') } || return }; } sub _get_path_attr_values { values %{ &{ $_[0]->can('_get_path_attrs') } || return }; } sub _del_path_attr { return unless my $attrs = &{ $_[0]->can('_get_path_attrs') }; return 0 unless exists $attrs->{ my $attr = $_[-1] }; delete $attrs->{$attr}; return 1 if keys %$attrs; &{ $_[0]->can('_del_path_attrs') }; 1; } sub _sequence_del { my ($m, $id, $l) = @_; my ($f, $a, $map_i, $pi, $map_s, $map_p) = @$m[ _f, _arity, _i, _pi, _s, _p ]; delete $pi->{ $l }; delete $m->[ $_ ][ $id ] for _count, _attr; my $path = delete $map_i->[ $id ]; _successors_del($f, $a, $map_s, $map_p, $id, $path) if $map_s; return 1; } sub get_paths_by_ids { my ($i, undef, $list, $deep) = ( @{ $_[0] }[ _i ], @_ ); $deep ? map [ map [ @$i[ @$_ ] ], @$_ ], @$list : map [ @$i[ @$_ ] ], @$list; } sub paths { grep defined, @{ $_[0]->[ _i ] || Graph::_empty_array() }; } sub ids { values %{ $_[0]->[ _pi ] || Graph::_empty_array() }; } sub get_ids_by_paths { my ($f, $a, $pi, $m, $list, $ensure, $deep) = ( @{ $_[0] }[ _f, _arity, _pi ], @_ ); $deep ||= 0; my ($is_multi, $is_ref, $is_unord) = (map $f & $_, _MULTI, _REF, _UNORD); return map { # Fast path my @ret = map { my $id = $pi->{ $a != 1 ? "@$_" : $_ }; defined $id ? $id : !$ensure ? return : ($is_multi ? $m->set_path_by_multi_id($_, _GEN_ID) : $m->set_paths($_))[0]; } $deep ? @$_ : $_; $deep ? \@ret : @ret; } @$list if $a and !$is_ref and $deep < 2; map { my @ret = map { my @ret2 = map { my $k = $_; $k = __strval($k, $f) if $a == 1 && $is_ref && ref($k); my $l = ($a == 0 && !$is_unord) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k"; my $id = $pi->{ $l }; defined $id ? $id : !$ensure ? return : ($is_multi ? $m->set_path_by_multi_id($_, _GEN_ID) : $m->set_paths($_))[0]; } $deep > 1 ? @$_ : $_; $deep > 1 ? \@ret2 : @ret2; } $deep ? @$_ : $_; $deep ? \@ret : @ret; } @$list; } sub _paths_fromto { my $offset = pop; my ($i, $map_x, @v) = ( @{ $_[0] }[ _i, $offset ], @_[1..$#_] ); Graph::__carp_confess("undefined vertex") if grep !defined, @v; require Set::Object; map $i->[ $_ ], Set::Object->new(map @$_, map values %{ $map_x->{ $_ } || _empty }, @v)->members; } sub paths_from { push @_, _s; goto &_paths_fromto } sub paths_to { push @_, _p; goto &_paths_fromto } sub _cessors { my $offset = pop; my ($map_x, @v) = ( @{ $_[0] }[ $offset ], @_[1..$#_] ); Graph::__carp_confess("undefined vertex") if grep !defined, @v; require Set::Object; Set::Object->new(map keys %{ $map_x->{ $_ } || _empty }, @v)->members; } sub successors { push @_, _s; goto &_cessors } sub predecessors { push @_, _p; goto &_cessors } sub has_successor { my ($map_s, $u, $v) = ( @{ $_[0] }[ _s ], @_[1, 2] ); Graph::__carp_confess("undefined vertex") if grep !defined, $u, $v; exists ${ $map_s->{ $u } || _empty }{ $v }; } sub __strval { my ($k, $f) = @_; return $k unless ref $k && ($f & _REF); return "$k" if ($f & _STR); require Scalar::Util; Scalar::Util::refaddr($k); } sub __arg { my ($f, $a, $m, $k) = (@{ $_[0] }[ _f, _arity ], @_[0, 1]); Graph::__carp_confess(sprintf "arguments %d (%s) expected %d for\n".$m->stringify, scalar @$k, "@$k", $a) if $a > 1 and @$k != $a; } sub reindex { my ($f, $a, $i2p, $m) = (@{ $_[0] }[ _f, _arity, _i ], $_[0]); my $is_ref = $a == 1 && ($f & _REF); my $pi = $m->[ _pi ] = {}; for my $i ( 0..$#{ $i2p } ) { next if !defined(my $k = $i2p->[ $i ]); # deleted $k = __strval($k, $f) if $is_ref && ref($k); $pi->{ $k } = $i; } } 1; __END__ =pod =head1 NAME Graph::AdjacencyMap - map of graph vertices or edges =head1 SYNOPSIS Internal. =head1 DESCRIPTION B =head1 OBJECT METHODS =head2 del_path(\@seq) Delete a Map path. =head2 del_path_by_multi_id(\@seq, $id) Delete a Map path by a multi(vertex) id. =head2 get_multi_ids(\@seq) Return the multi ids. =head2 has_path(\@seq) Returns the integer ID of the path, or undef if Map doesn't have it. =head2 has_any_paths Return true if the Map has any paths, false if not. =head2 has_path_by_multi_id(\@seq, $id) Return true if the Map has the path by a multi(vertex) id, false if not. =head2 paths Return all the paths (left-hand sides) of the Map. =head2 ids Return all the right-hand sides of the Map, unsorted. =head2 set_paths(\@seq1, \@seq2, ...) @ids = set_paths($seq1, $seq2, ...) Create/identify the path of C<$seq*>. Returns the integer ID of each path. For arity other than 1, the sequence items must be integers. For arity 1, do not wrap the item in an array. For C<_UNORD>, you must give the sequence already sorted. =head2 set_path_by_multi_id(\@seq, $id) ($integer_ID, $multi_ID) = $m->set_path_by_multi_id(\@seq, $id) Set the path in the Map by the multi id. =head2 get_paths_by_ids([ \@idlist1, \@idlist2... ], $deep) Given an array-ref of array-refs of vertex IDs, returns a list of array-refs of vertex-names. This is to look up vertex paths for use in edges. Only useful for arity 1. The C<$deep> option is useful with directed hyperedges. =head2 get_ids_by_paths @ids = $m->get_ids_by_paths([ \@seq1, \@seq2... ], $ensure, 0); @id_lists = $m->get_ids_by_paths([ \@seq1, \@seq2... ], $ensure, 1); This is to look up vertex IDs for use in edges. Only useful for arity 1. Given an array-ref of array-refs with paths, returns a list of IDs of existing paths. If C<$ensure> is true, will first create paths that do not already exist. If it is not, any non-existing paths will cause an empty list to be returned. If $deep is true, each sequence will be treated as a list of paths, and IDs filled in for the return values. This can have a value up to 2. =head2 rename_path($from, $to) Rename the path. =head2 stringify Return a string describing the object in a human-friendly(ish) way. =head2 successors @successors = $m->successors(@v) Only valid for a map of arity other than 1. =head2 predecessors @predecessors = $m->predecessors($v) Only valid for a non-C<_UNORD> map of arity other than 1. =head2 paths_from @paths = $m->paths_from(@v) Only valid for a map of arity other than 1. =head2 paths_to @paths = $m->paths_to($v) Only valid for a non-C<_UNORD> map of arity other than 1. =head2 has_successor $bool = $m->has_successor($u, $v) Only valid for a map of arity other than 1. =head2 reindex Will recreate the mapping from paths to indexes. Intended for use after a deep copy. =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut Graph-0.9735/lib/Graph/Traversal/0000755000175000017500000000000014771263365016400 5ustar osboxesosboxesGraph-0.9735/lib/Graph/Traversal/BFS.pm0000644000175000017500000000171713774262024017347 0ustar osboxesosboxespackage Graph::Traversal::BFS; use strict; use warnings; use Graph::Traversal; use base 'Graph::Traversal'; sub current { my $self = shift; $self->{ order }->[ 0 ]; } sub see { my $self = shift; shift @{ $self->{ order } }; } *bfs = \&Graph::Traversal::postorder; 1; __END__ =pod =head1 NAME Graph::Traversal::BFS - breadth-first traversal of graphs =head1 SYNOPSIS use Graph; my $g = Graph->new; $g->add_edge(...); use Graph::Traversal::BFS; my $b = Graph::Traversal::BFS->new($g, %opt); $b->bfs; # Do the traversal. =head1 DESCRIPTION With this class one can traverse a Graph in breadth-first order. The callback parameters %opt are explained in L. =head2 Methods The following methods are available: =over 4 =item bfs Traverse the graph in breadth-first order. Returns all vertices traversed in post-order. =back =head1 SEE ALSO L, L, L. =cut Graph-0.9735/lib/Graph/Traversal/DFS.pm0000644000175000017500000000171013774262024017342 0ustar osboxesosboxespackage Graph::Traversal::DFS; use strict; use warnings; use Graph::Traversal; use base 'Graph::Traversal'; sub current { my $self = shift; $self->{ order }->[ -1 ]; } sub see { my $self = shift; pop @{ $self->{ order } }; } *dfs = \&Graph::Traversal::postorder; 1; __END__ =pod =head1 NAME Graph::Traversal::DFS - depth-first traversal of graphs =head1 SYNOPSIS use Graph; my $g = Graph->new; $g->add_edge(...); use Graph::Traversal::DFS; my $d = Graph::Traversal::DFS->new($g, %opt); $d->dfs; # Do the traversal. =head1 DESCRIPTION With this class one can traverse a Graph in depth-first order. The callback parameters %opt are explained in L. =head2 Methods The following methods are available: =over 4 =item dfs Traverse the graph in depth-first order. Returns all vertices traversed in post-order. =back =head1 SEE ALSO L, L, L. =cut Graph-0.9735/lib/Graph/MSTHeapElem.pm0000644000175000017500000000052713774262024017034 0ustar osboxesosboxespackage Graph::MSTHeapElem; use strict; use warnings; sub new { my $class = shift; bless { u => $_[0], v => $_[1], w => $_[2] }, $class; } sub heap { my $self = shift; @_ ? ($self->{heap} = shift) : $self->{heap}; } sub cmp { ($_[0]->{ w } || 0) <=> ($_[1]->{ w } || 0); } sub val { @{ $_[0] }{ qw(u v w) }; } 1; Graph-0.9735/lib/Graph/AdjacencyMap/0000755000175000017500000000000014771263365016754 5ustar osboxesosboxesGraph-0.9735/lib/Graph/AdjacencyMap/Light.pm0000644000175000017500000001242714741033474020360 0ustar osboxesosboxespackage Graph::AdjacencyMap::Light; # THIS IS INTERNAL IMPLEMENTATION ONLY, NOT TO BE USED DIRECTLY. # THE INTERFACE IS HARD TO USE AND GOING TO STAY THAT WAY AND # ALMOST GUARANTEED TO CHANGE OR GO AWAY IN FUTURE RELEASES. use strict; use warnings; use Graph::AdjacencyMap qw(:flags :fields); use base 'Graph::AdjacencyMap'; # $SIG{__DIE__ } = \&Graph::__carp_confess; # $SIG{__WARN__} = \&Graph::__carp_confess; my @LOCAL_OVERRIDE = (_s, _p); sub _is_COUNT () { 0 } sub _is_MULTI () { 0 } sub _is_REF () { 0 } sub _new { my ($class, $flags, $arity) = @_; (my $m = $class->SUPER::_new($flags | _LIGHT, $arity))->[ _attr ] = {}; @$m[ @LOCAL_OVERRIDE ] = map $m->[ $_ ] ? [] : undef, @LOCAL_OVERRIDE; $m; } sub set_paths { my ($m, @paths) = @_; my ($f, $a, $i, $pi, $map_s, $map_p, @ids) = (@$m[ _f, _arity, _i, _pi, _s, _p ]); for (@paths) { my $k = $_; Graph::__carp_confess("Wrong number of args, want $a, got (@$k)") if $a != 1 and $a != @$k; my $l = $a == 1 ? $k : join ' ', @$k; push(@ids, $pi->{ $l }), next if defined $pi->{ $l }; $i->[ my $n = $m->[ _n ]++ ] = $_; $pi->{ $l } = $n; push @ids, $n; _successors_add($f, $map_s, $map_p, $n, $_) if $map_s; } @ids; } sub _successors_set { my $val = pop; my ($f, $map_s, $map_p, $id, $path) = @_; my $pairs = Graph::AdjacencyMap::_successors_cartesian(($f & _UNORD), 0, $path); no warnings 'uninitialized'; # needed 5.8 vec($map_s->[ $_->[0] ], $_->[1], 1) = $val for @$pairs; # row-major return if !$map_p; vec($map_p->[ $_->[1] ], $_->[0], 1) = $val for @$pairs; } sub _successors_add { push @_, 1; goto &_successors_set } sub _successors_del { push @_, 0; goto &_successors_set } sub _paths_fromto { my $offset = pop; my ($i, $pi, $f, $map_x, @v) = ( @{ $_[0] }[ _i, _pi, _f, $offset ], @_[1..$#_] ); Graph::__carp_confess("undefined vertex") if grep !defined, @v; require Set::Object; my ($paths, $invert, $unord) = (Set::Object->new, $offset == _p, $f & _UNORD); for my $tuple (grep defined $_->[1], map [$_, $map_x->[$_]], @v) { my ($v, $s) = ($tuple->[0], scalar unpack("b*", $tuple->[1])); $paths->insert(join ' ', ( $unord ? sort($v, pos($s) - 1) : $invert ? (pos($s) - 1, $v) : ($v, pos($s) - 1) )) while $s =~ /1/g; } map $i->[ $pi->{ $_ } ], $paths->members; } sub paths_from { push @_, _s; goto &_paths_fromto } sub paths_to { push @_, _p; goto &_paths_fromto } sub _cessors { my $offset = pop; my ($map_x, @v) = ( @{ $_[0] }[ $offset ], @_[1..$#_] ); Graph::__carp_confess("undefined vertex") if grep !defined, @v; require Set::Object; my $c = Set::Object->new; for my $row (grep defined, @$map_x[ @v ]) { # 10x quicker than: grep vec($row, $_, 1), 0..$#$m my $s = unpack("b*", $row); $c->insert(pos($s) - 1) while $s =~ /1/g; } $c->members; } sub successors { push @_, _s; goto &_cessors } sub predecessors { push @_, _p; goto &_cessors } sub has_successor { my ($map_s, $u, $v) = ( @{ $_[0] }[ _s ], @_[1, 2] ); Graph::__carp_confess("undefined vertex") if grep !defined, $u, $v; vec(($map_s->[ $u ] || return 0), $v, 1); } sub get_ids_by_paths { my ($pi, $m, $list, $ensure, $deep) = ( @{ $_[0] }[ _pi ], @_ ); $deep ||= 0; map { my @ret = map { my @ret2 = map { my $id = $pi->{ $_ }; defined $id ? $id : $ensure ? $m->set_paths($_) : return; } $deep > 1 ? @$_ : $_; $deep > 1 ? \@ret2 : @ret2; } $deep ? @$_ : $_; $deep ? \@ret : @ret; } @$list; } sub has_path { my ($a, $pi, $k) = ( @{ $_[0] }[ _arity, _pi ], $_[1] ); Graph::__carp_confess("Wrong number of args, want $a, got (@$k)") if $a != 1 and $a != @$k; $pi->{ $a == 1 ? $k : join ' ', @$k }; } sub _get_path_count { defined(my $dummy = &has_path) ? 1 : 0; # defined &x asks if func defined } sub del_path { my ($f, $a, $i, $pi, $map_s, $map_p, $attr, $k) = ( @{ my $m = $_[0] }[ _f, _arity, _i, _pi, _s, _p, _attr ], $_[1] ); Graph::__carp_confess("Wrong number of args, want $a, got (@$k)") if $a != 1 and $a != @$k; my $l = $a == 1 ? $k : join ' ', @$k; return 0 if !exists $pi->{ $l }; my $id = delete $pi->{ $l }; delete $attr->{ $l }; my $path = delete $i->[ $id ]; _successors_del($f, $map_s, $map_p, $id, $path) if $map_s; return 1; } sub rename_path { my ($m, $from, $to) = @_; my ($a, $i, $pi, $attr) = @$m[ _arity, _i, _pi, _attr ]; return 1 if $a > 1; # arity > 1, all integers, no names return 0 unless exists $pi->{ $from }; $attr->{ $to } = delete $attr->{ $from } if $attr->{ $from }; $i->[ $pi->{ $to } = delete $pi->{ $from } ] = $to; return 1; } sub _set_path_attr_common { (my $m = $_[0])->set_paths($_[1]); my ($a, $attr, $k) = ( @$m[ _arity, _attr ], $_[1] ); my $l = $a == 1 ? $k : join ' ', @$k; \$attr->{ $l }; } sub _get_path_attrs { my ($a, $attr, $k) = ( @{ $_[0] }[ _arity, _attr ], $_[1] ); Graph::__carp_confess("Wrong number of args, want $a, got (@$k)") if $a != 1 and $a != @$k; my $l = $a == 1 ? $k : join ' ', @$k; $attr->{ $l }; } sub _del_path_attrs { return undef unless defined &has_path; my ($a, $attr, $k) = ( @{ $_[0] }[ _arity, _attr ], $_[1] ); my $l = $a == 1 ? $k : join ' ', @$k; return 0 unless exists $attr->{ $l }; delete $attr->{ $l }; 1; } 1; Graph-0.9735/lib/Graph/Directed.pm0000644000175000017500000000121113774262024016502 0ustar osboxesosboxespackage Graph::Directed; use Graph; use base 'Graph'; use strict; use warnings; =pod =head1 NAME Graph::Directed - directed graphs =head1 SYNOPSIS use Graph::Directed; my $g = Graph::Directed->new; # Or alternatively: use Graph; my $g = Graph->new(directed => 1); my $g = Graph->new(undirected => 0); =head1 DESCRIPTION Graph::Directed allows you to create directed graphs. For the available methods, see L. =head1 SEE ALSO L, L =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut 1; Graph-0.9735/lib/Graph/SPTHeapElem.pm0000644000175000017500000000064513774262024017040 0ustar osboxesosboxespackage Graph::SPTHeapElem; use strict; use warnings; sub new { my $class = shift; bless { u => $_[0], v => $_[1], w => $_[2] }, $class; } sub heap { my $self = shift; @_ ? ($self->{heap} = shift) : $self->{heap}; } sub cmp { ($_[0]->{ w } || 0) <=> ($_[1]->{ w } || 0) || ($_[0]->{ u } cmp $_[1]->{ u }) || ($_[0]->{ u } cmp $_[1]->{ v }); } sub val { @{ $_[0] }{ qw(u v w) }; } 1; Graph-0.9735/lib/Graph/Matrix.pm0000644000175000017500000000356313774262024016237 0ustar osboxesosboxespackage Graph::Matrix; # $SIG{__DIE__ } = \&Graph::__carp_confess; # $SIG{__WARN__} = \&Graph::__carp_confess; use strict; use warnings; sub new { my ($class, $g) = @_; my @V = $g->vertices; my $V = @V; my %V; @V{ @V } = 0 .. $#V; bless [ [ map [], 0 .. $#V ], \%V ], $class; } sub set { my ($m, $u, $v, $val) = @_; my ($i, $j) = map $m->[1]->{ $_ }, ($u, $v); $m->[0]->[$i]->[$j] = $val; } sub get { my ($m, $u, $v) = @_; my ($i, $j) = map $m->[1]->{ $_ }, ($u, $v); $m->[0]->[$i]->[$j]; } sub stringify { my ($m) = @_; my @V = sort keys %{ $m->[1] }; my $top = join ' ', map sprintf('%4s', $_), 'to:', @V; my @indices = map $m->[1]{$_}, @V; my @rows; for my $n (@V) { my $this_row = $m->[0][ $m->[1]->{$n} ]; my @vals = map $this_row->[ $_ ], @indices; push @rows, join ' ', map sprintf('%4s', defined()?$_:''), $n, @vals; } join '', map "$_\n", $top, @rows; } 1; __END__ =pod =head1 NAME Graph::Matrix - create and manipulate a V x V matrix of graph G =head1 SYNOPSIS use Graph::Matrix; use Graph::Directed; my $g = Graph::Directed->new; $g->add_...(); # build $g my $m = Graph::Matrix->new($g); $m->get($u, $v) $s->get($u, $v, $val) =head1 DESCRIPTION B =head2 Class Methods =over 4 =item new($g) Construct a new Matrix from the Graph $g. =back =head2 Object Methods =over 4 =item get($u, $v) Return the value at the edge from $u to $v. =item set($u, $v, $val) Set the edge from $u to $v to value $val. =item stringify Returns a string roughly representing the matrix, with the C<$u> down the left-hand side, and C<$v> across the top. =back =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut Graph-0.9735/lib/Graph/BitMatrix.pm0000644000175000017500000001221414741033474016667 0ustar osboxesosboxespackage Graph::BitMatrix; use strict; use warnings; # $SIG{__DIE__ } = \&Graph::__carp_confess; # $SIG{__WARN__} = \&Graph::__carp_confess; sub _E () { 3 } # Graph::_E() sub _i () { 3 } # Index to path. sub new { my ($class, $g, %opt) = @_; my @V = $g->vertices; my $V = @V; my $Z = "\0" x (($V + 7) / 8); my %V; @V{ @V } = 0 .. $#V; my $bm = bless [ [ ( $Z ) x $V ], \%V ], $class; my $bm0 = $bm->[0]; my $connect_edges = delete $opt{connect_edges}; $connect_edges = 1 unless defined $connect_edges; my $transpose = delete $opt{transpose}; Graph::_opt_unknown(\%opt); return $bm if !$connect_edges; # for (my $i = 0; $i <= $#V; $i++) { # my $u = $V[$i]; # for (my $j = 0; $j <= $#V; $j++) { # vec($bm0->[$i], $j, 1) = 1 if $g->has_edge($u, $V[$j]); # } # } my $undirected = $g->is_undirected; for my $e ($g->edges) { my ($i0, $j0) = map $V{$_}, @$e; ($j0, $i0) = ($i0, $j0) if $transpose; vec($bm0->[$i0], $j0, 1) = 1; vec($bm0->[$j0], $i0, 1) = 1 if $undirected; } $bm; } sub stringify { my ($m) = @_; my @V = sort keys %{ $m->[1] }; my $top = join ' ', map sprintf('%4s', $_), 'to:', @V; my @indices = map $m->[1]{$_}, @V; my @rows; for my $n (@V) { my @vals = $m->get_row($n, @V); push @rows, join ' ', map sprintf('%4s', defined()?$_:''), $n, @vals; } join '', map "$_\n", $top, @rows; } sub set { push @_, 1; goto &_get_set } sub unset { push @_, 0; goto &_get_set } sub get { push @_, undef; goto &_get_set } sub _get_set { my $val = pop; my ($m, $u, $v) = @_; my ($m0, $m1) = @$m[0, 1]; return if grep !defined, my ($i, $j) = @$m1{ $u, $v }; defined $val ? vec($m0->[$i], $j, 1) = $val : vec($m0->[$i], $j, 1); } sub _set_row { my $val = pop; my ($m, $u) = splice @_, 0, 2; my ($m0, $m1) = @$m[0, 1]; return unless defined(my $i = $m1->{ $u }); vec($m0->[$i], $_, 1) = $val for grep defined, @$m1{ @_ }; } sub set_row { push @_, 1; goto &_set_row } sub unset_row { push @_, 0; goto &_set_row } sub get_row { my ($m, $u) = splice @_, 0, 2; my ($m0, $m1) = @$m[0, 1]; return () x @_ unless defined(my $i = $m1->{ $u }); map defined() ? (vec($m0->[$i], $_, 1) ? 1 : 0) : undef, @$m1{ @_ }; } sub vertices { keys %{ $_[0]->[1] }; } 1; __END__ =pod =head1 NAME Graph::BitMatrix - create and manipulate a V x V bit matrix of graph G =head1 SYNOPSIS use Graph::BitMatrix; use Graph::Directed; my $g = Graph::Directed->new; $g->add_...(); # build $g my $m = Graph::BitMatrix->new($g, %opt); $m->get($u, $v) $m->set($u, $v) $m->unset($u, $v) $m->get_row($u, $v1, $v2, ..., $vn) $m->set_row($u, $v1, $v2, ..., $vn) $m->unset_row($u, $v1, $v2, ..., $vn) $a->vertices() =head1 DESCRIPTION This class enables creating bit matrices that compactly describe the connected of the graphs. =head2 Class Methods =over 4 =item new($g) Create a bit matrix from a Graph $g. The C<%opt>, if present, can have the following options: =over 8 =item * connect_edges If true or if not present, set the bits in the bit matrix that correspond to edges. If false, do not set any bits. In either case the bit matrix of V x V bits is allocated. =item * transpose If true, set the bits in the bit matrix that correspond to edges but in the reverse direction. This has the effect of transposing the matrix. Obviously makes no difference to the result for undirected graphs. =back =back =head2 Object Methods =over 4 =item get($u, $v) Return true if the bit matrix has a "one bit" between the vertices $u and $v; in other words, if there is (at least one) a vertex going from $u to $v. If there is no vertex and therefore a "zero bit", return false. =item set($u, $v) Set the bit between the vertices $u and $v; in other words, connect the vertices $u and $v by an edge. The change does not get mirrored back to the original graph. Returns nothing. =item unset($u, $v) Unset the bit between the vertices $u and $v; in other words, disconnect the vertices $u and $v by an edge. The change does not get mirrored back to the original graph. Returns nothing. =item get_row($u, $v1, $v2, ..., $vn) Test the row at vertex C for the vertices C, C, ..., C Returns a list of I truth values. =item set_row($u, $v1, $v2, ..., $vn) Sets the row at vertex C for the vertices C, C, ..., C, in other words, connects the vertex C to the vertices C. The changes do not get mirrored back to the original graph. Returns nothing. =item unset_row($u, $v1, $v2, ..., $vn) Unsets the row at vertex C for the vertices C, C, ..., C, in other words, disconnects the vertex C from the vertices C. The changes do not get mirrored back to the original graph. Returns nothing. =item vertices Return the list of vertices in the bit matrix. =back =head1 ALGORITHM The algorithm used to create the matrix is two nested loops, which is O(V**2) in time, and the returned matrices are O(V**2) in space. =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut Graph-0.9735/lib/Graph/Undirected.pm0000644000175000017500000000142014741033474017047 0ustar osboxesosboxespackage Graph::Undirected; use Graph; use base 'Graph'; use strict; use warnings; =pod =head1 NAME Graph::Undirected - undirected graphs =head1 SYNOPSIS use Graph::Undirected; my $g = Graph::Undirected->new; # Or alternatively: use Graph; my $g = Graph->new(undirected => 1); my $g = Graph->new(directed => 0); =head1 DESCRIPTION Graph::Undirected allows you to create undirected graphs. For the available methods, see L. =head1 SEE ALSO L, L =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut sub new { my $class = shift; $class->SUPER::new((ref $class && $class->directed) ? () : (undirected => 1), @_); } 1; Graph-0.9735/lib/Graph/UnionFind.pm0000644000175000017500000000714114741033474016660 0ustar osboxesosboxespackage Graph::UnionFind; use strict; use warnings; sub _PARENT () { 0 } sub _RANK () { 1 } sub new { my $class = shift; bless { }, $class; } sub add { my ($self, @elems) = @_; @elems = grep !defined $self->{$_}, @elems; @$self{ @elems } = map [ $_, 0 ], @elems; } sub _parent { return undef unless defined $_[1]; Graph::__carp_confess(__PACKAGE__ . "::_parent: bad arity") if @_ < 2 or @_ > 3; if (@_ == 2) { exists $_[0]->{ $_[ 1 ] } ? $_[0]->{ $_[1] }->[ _PARENT ] : undef; } else { $_[0]->{ $_[1] }->[ _PARENT ] = $_[2]; } } sub _rank { return unless defined $_[1]; Graph::__carp_confess(__PACKAGE__ . "::_rank: bad arity") if @_ < 2 or @_ > 3; if (@_ == 2) { exists $_[0]->{ $_[1] } ? $_[0]->{ $_[1] }->[ _RANK ] : undef; } else { $_[0]->{ $_[1] }->[ _RANK ] = $_[2]; } } sub find { my ($self, @v) = @_; my @ret; for my $x (@v) { push(@ret, undef), next unless defined(my $px = $self->_parent($x)); $self->_parent( $x, $self->find( $px ) ) if $px ne $x; push @ret, $self->_parent( $x ); } @ret; } sub union { my ($self, @edges) = @_; $self->add(map @$_, @edges); for my $e (@edges) { my ($px, $py) = $self->find( @$e ); next if $px eq $py; my $rx = $self->_rank( $px ); my $ry = $self->_rank( $py ); # print "union($x, $y): px = $px, py = $py, rx = $rx, ry = $ry\n"; if ( $rx > $ry ) { $self->_parent( $py, $px ); } else { $self->_parent( $px, $py ); $self->_rank( $py, $ry + 1 ) if $rx == $ry; } } } sub same { my ($uf, $u, $v) = @_; my ($fu, $fv) = $uf->find($u, $v); return undef if grep !defined, $fu, $fv; $fu eq $fv; } 1; __END__ =pod =head1 NAME Graph::UnionFind - union-find data structures =head1 SYNOPSIS use Graph::UnionFind; my $uf = Graph::UnionFind->new; # Add the vertices to the data structure. $uf->add($u); $uf->add($v); # Join the partitions of the vertices. $uf->union( $u, $v ); # Find the partitions the vertices belong to # in the union-find data structure. If they # are equal, they are in the same partition. # If the vertex has not been seen, # undef is returned. my $pu = $uf->find( $u ); my $pv = $uf->find( $v ); $uf->same($u, $v) # Equal to $pu eq $pv. # Has the union-find seen this vertex? $uf->has( $v ) =head1 DESCRIPTION I is a special data structure that can be used to track the partitioning of a set into subsets (a problem also known as I). C is used for L, L, and L if you specify a true C parameter when you create an undirected graph. Union-find is one way: you cannot (easily) 'ununion' vertices once you have 'unioned' them. This is why L throws an exception if you try to delete edges from a union-find graph. =head2 API =over 4 =item add $uf->add(@v) Add the vertices to the union-find. =item union $uf->union([$u, $v], [$w, $x], ...) Add the edge u-v to the union-find. Also implicitly adds the vertices. =item find @partitions = $uf->find(@v) For each given vertex, return the union-find partition it belongs to, or C if it has not been added. =item new $uf = Graph::UnionFind->new() The constructor. =item same $uf->same($u, $v) Return true of the vertices belong to the same union-find partition the vertex v belongs to, false otherwise. =back =head1 AUTHOR AND COPYRIGHT Jarkko Hietaniemi F =head1 LICENSE This module is licensed under the same terms as Perl itself. =cut Graph-0.9735/lib/Graph.pod0000644000175000017500000023375714771263144015155 0ustar osboxesosboxes=pod =encoding utf8 =head1 NAME Graph - graph data structures and algorithms =head1 SYNOPSIS use Graph; my $g0 = Graph->new; # A directed graph. use Graph::Directed; my $g1 = Graph::Directed->new; # A directed graph. use Graph::Undirected; my $g2 = Graph::Undirected->new; # An undirected graph. $g->add_edge(...); $g->has_edge(...) $g->any_edge(...) $g->delete_edge(...); $g->add_vertex(...); $g->has_vertex(...); $g->delete_vertex(...); $g->vertices(...) $g->edges(...) # And many, many more, see below. =head1 DESCRIPTION =head2 Non-Description This module is not for B or B any sort of I or I, business, visualization, or otherwise. =head2 Description Instead, this module is for creating I called graphs, and for doing various operations on those. =head2 Perl 5.6.0 minimum The implementation depends on a Perl feature called "weak references" and Perl 5.6.0 was the first to have those. =head2 Constructors =over 4 =item new Create an empty graph. =item Graph->new(%options) The options are a hash with option names as the hash keys and the option values as the hash values. The following options are available: =over 8 =item directed A boolean option telling that a directed graph should be created. Often somewhat redundant because a directed graph is the default for the Graph class or one could simply use the C constructor of the Graph::Directed class. You can test the directness of a graph with $g->is_directed() and $g->is_undirected(). =item undirected A boolean option telling that an undirected graph should be created. One could also use the C constructor the Graph::Undirected class instead. Note that while often it is possible to think of undirected graphs as bidirectional graphs, or as directed graphs with edges going both ways, in this module directed graphs and undirected graphs are two different things that often behave differently. You can test the directness of a graph with $g->is_directed() and $g->is_undirected(). =item refvertexed =item refvertexed_stringified If you want to use references (including Perl objects) as vertices, use C. Note that using C means that internally the memory address of the reference (for example, a Perl object) is used as the "identifier" of the vertex, not the stringified form of the reference, even if you have defined your own stringification using C. This avoids the problem of the stringified references potentially being identical (because they are identical in value, for example) even if the references are different. If you really want to use references B their stringified forms as the identities, use the C. But please do B stringify different objects to the same stringified value. =item unionfind If the graph is undirected, you can specify the C parameter to use the so-called union-find scheme to speed up the computation of I of the graph (see L, L, L, L, and L). If C is used, adding edges (and vertices) becomes slower, but connectedness queries become faster. You B delete edges or vertices of an unionfind graph, only add them. You can test a graph for "union-findness" with =item has_union_find Returns true if the graph was created with a true C parameter. =item vertices An array reference of vertices to add. =item edges An array reference of array references of edge vertices to add. =back =item copy =item copy_graph my $c = $g->copy_graph; Create a shallow copy of the structure (vertices and edges) of the graph. If you want a deep copy that includes attributes, see L. The copy will have the same directedness as the original. Also the following vertex/edge attributes are copied: refvertexed/countvertexed/multivertexed hyperedged/countedged/multiedged B: You can get an even shallower copy of a graph by my $c = $g->new; This will copy only the graph properties (directed, and so forth), but none of the vertices or edges. As of 0.9712, you can also copy the graph properties of an existing object, I with overrides: my $c = $g->new(multiedged => 0); =item deep_copy =item deep_copy_graph my $c = $g->deep_copy_graph; Create a deep copy of the graph (vertices, edges, and attributes) of the graph. If you want a shallow copy that does not include attributes, see L. Note that copying code references only works with Perls 5.8 or later, and even then only if B::Deparse can reconstruct your code. This functionality uses either Storable or Data::Dumper behind the scenes, depending on which is available (Storable is preferred). If your vertices are references, the copied graph will have its connections fixed up. Support for this is new as of 0.9723, so please report any problems. =item undirected_copy =item undirected_copy_graph my $c = $g->undirected_copy_graph; Create an undirected shallow copy (vertices and edges) of the directed graph so that for any directed edge (u, v) there is an undirected edge (u, v). As of 0.9731 this preserves C and C IDs. =item undirected_copy_clear_cache $g->undirected_copy_clear_cache; See L. =item weak_connectivity_undirected_graph_clear_cache $g->weak_connectivity_undirected_graph_clear_cache; See L. =item undirected_copy_attributes Added in 0.9731. Like L but also sets the copy's attributes to the same values as the original's. This is not a deep copy, so use caution in handling the copy's attributes. =item directed_copy =item directed_copy_graph my $c = $g->directed_copy_graph; Create a directed shallow copy (vertices and edges) of the undirected graph so that for any undirected edge (u, v) there are two directed edges (u, v) and (v, u). As of 0.9731 this preserves C and C IDs. =item directed_copy_clear_cache $g->directed_copy_clear_cache; See L. =item directed_copy_attributes Added in 0.9731. Like L but also sets the copy's attributes to the same values as the original's. This is not a deep copy, so use caution in handling the copy's attributes. =item transpose =item transpose_graph my $t = $g->transpose_graph; Create a directed shallow transposed copy (vertices and edges) of the directed graph so that for any directed edge (u, v) there is a directed edge (v, u). You can also transpose a single edge with =over 8 =item transpose_edge $g->transpose_edge($u, $v) =back =item complete_graph =item complete my $c = $g->complete_graph; Create a complete graph that has the same vertices as the original graph. A complete graph has an edge between every pair of vertices. =item max_cliques my @cliques = $g->max_cliques; Returns a list (or array reference in scalar context), each of its elements is an anonymous array of vertices forming a maximal clique in the graph. The implementation uses the Bron-Kerbosch pivot algorithm. =item bron_kerbosch_pivot $g->bron_kerbosch_pivot([], [$g->vertices], [], \ my @cliques); Implements the Bron-Kerbosch pivot algorithm, mutating its fourth argument with the result. =item complement_graph =item complement my $c = $g->complement_graph; Create a complement graph that has the same vertices as the original graph. A complement graph has an edge (u,v) if and only if the original graph does not have edge (u,v). =item subgraph my $c = $g->subgraph(\@src, \@dst); my $c = $g->subgraph(\@src); Creates a subgraph of a given graph. The created subgraph has the same graph properties (directedness, and so forth) as the original graph, but none of the attributes (graph, vertex, or edge). A vertex is added to the subgraph if it is in the original graph. An edge is added to the subgraph if there is an edge in the original graph that starts from the C set of vertices and ends in the C set of vertices. You can leave out C in which case C is assumed to be the same: this is called a I. =back See also L for a random constructor. =head2 Basics =over 4 =item add_vertex $g->add_vertex($v) Add the vertex to the graph. Returns the graph. By default idempotent, but a graph can be created I. A vertex is also known as a I. Adding C as vertex is not allowed. Note that unless you have isolated vertices (or I vertices), you do not need to explicitly use C since L will implicitly add its vertices. =item add_edge $g->add_edge($u, $v) Add the edge to the graph. Implicitly first adds the vertices if the graph does not have them. Returns the graph. By default idempotent, but a graph can be created I. An edge is also known as an I. For a hypergraph, the interface is different: if undirected, give a list of one or more vertices. If directed, give a list of two array-refs of vertices. As conceptually these are sets, the ordering of the contents is not important. =item has_vertex $g->has_vertex($v) Return true if the vertex exists in the graph, false otherwise. =item has_edge $g->has_edge($u, $v) Return true if the edge exactly as specified exists in the graph, false otherwise. Hyperedges which contain all the given vertices (in the right places if directed), but which also have others will not match. =item any_edge $g->any_edge($u, $v) Return true if any edge in the graph connects the first vertex to the second, false otherwise. Note this is a different question from C. It will give the same result as checking the first vertex's L to see if any match the second one, but in a more efficient way. =item delete_vertex $g->delete_vertex($v) Delete the vertex from the graph. Returns the graph, even if the vertex did not exist in the graph. If the graph has been created I or I and a vertex has been added multiple times, the vertex will require at least an equal number of deletions to become completely deleted. =item delete_vertices $g->delete_vertices($v1, $v2, ...) Delete the vertices from the graph. Returns the graph, even if none of the vertices existed in the graph. If the graph has been created I or I and a vertex has been added multiple times, the vertex will require at least an equal number of deletions to become completely deleted. =item delete_edge $g->delete_edge($u, $v) Delete the edge from the graph. Returns the graph, even if the edge did not exist in the graph. If the graph has been created I or I and an edge has been added multiple times, the edge will require at least an equal number of deletions to become completely deleted. =item delete_edges $g->delete_edges($u1, $v1, $u2, $v2, ...) Delete the edges from the graph. Returns the graph, even if none of the edges existed in the graph. If the graph has been created I or I and an edge has been added multiple times, the edge will require at least an equal number of deletions to become completely deleted. =back =head2 Displaying Graphs have stringification overload, so you can do things like print "The graph is $g\n" One-way (directed, unidirected) edges are shown as '-', two-way (undirected, bidirected) edges are shown as '='. If you want to, you can call the stringification via the method =over 4 =item stringify =back =head2 Boolean Graphs have boolifying overload, so you can do things like if ($g) { print "The graph is: $g\n" } which works even if the graph is empty. In fact, the boolify always returns true. If you want to test for example for vertices, test for vertices. =over 4 =item boolify =back =head2 Comparing Testing for equality can be done either by the overloaded C operator $g eq "a-b,a-c,d" or by the method =over 4 =item eq $g->eq("a-b,a-c,d") =back The equality testing compares the stringified forms, and therefore it assumes total equality, not isomorphism: all the vertices must be named the same, and they must have identical edges between them. For unequality there are correspondingly the overloaded C operator and the method =over 4 =item ne $g->ne("a-b,a-c,d") =back See also L. =head2 Paths and Cycles Paths and cycles are simple extensions of edges: paths are edges starting from where the previous edge ended, and cycles are paths returning back to the start vertex of the first edge. =over 4 =item add_path $g->add_path($a, $b, $c, ..., $x, $y, $z) Add the edges $a-$b, $b-$c, ..., $x-$y, $y-$z to the graph. Returns the graph. =item has_path $g->has_path($a, $b, $c, ..., $x, $y, $z) Return true if the graph has all the edges $a-$b, $b-$c, ..., $x-$y, $y-$z, false otherwise. =item delete_path $g->delete_path($a, $b, $c, ..., $x, $y, $z) Delete all the edges $a-$b, $b-$c, ..., $x-$y, $y-$z (regardless of whether they exist or not). Returns the graph. =item add_cycle $g->add_cycle($a, $b, $c, ..., $x, $y, $z) Add the edges $a-$b, $b-$c, ..., $x-$y, $y-$z, and $z-$a to the graph. Returns the graph. =item has_cycle =item has_this_cycle $g->has_cycle($a, $b, $c, ..., $x, $y, $z) Return true if the graph has all the edges $a-$b, $b-$c, ..., $x-$y, $y-$z, and $z-$a, false otherwise. B This does not I cycles, see L and L. =item delete_cycle $g->delete_cycle($a, $b, $c, ..., $x, $y, $z) Delete all the edges $a-$b, $b-$c, ..., $x-$y, $y-$z, and $z-$a (regardless of whether they exist or not). Returns the graph. =item has_a_cycle $g->has_a_cycle Returns true if the graph has a cycle, false if not. =item find_a_cycle $g->find_a_cycle Returns a cycle if the graph has one (as a list of vertices), an empty list if no cycle can be found. Note that this just returns the vertices of I: not any particular cycle, just the first one it finds. A repeated call might find the same cycle, or it might find a different one, and you cannot call this repeatedly to find all the cycles. =back =head2 Graph Types =over 4 =item is_simple_graph $g->is_simple_graph Return true if the graph has no multiedges, false otherwise. =item is_pseudo_graph $g->is_pseudo_graph Return true if the graph has any multiedges or any self-loops, false otherwise. =item is_multi_graph $g->is_multi_graph Return true if the graph has any multiedges but no self-loops, false otherwise. =item is_directed_acyclic_graph =item is_dag $g->is_directed_acyclic_graph $g->is_dag Return true if the graph is directed and acyclic, false otherwise. =item is_cyclic $g->is_cyclic Return true if the graph is cyclic (contains at least one cycle). (This is identical to C.) To find at least one such cycle, see L. =item is_acyclic Return true if the graph is acyclic (does not contain any cycles). =item is_bipartite Return true if the graph is bipartite (also known as 2-colourable, or not containing cycles of odd length). Currently only works with undirected graphs. =item is_planar Return true if the graph is planar. The implementation is based on left-right planarity test as described in L. Currently only works with undirected graphs. =back To find a cycle, use L. =head2 Transitivity =over 4 =item is_transitive $g->is_transitive Return true if the graph is transitive, false otherwise. =item TransitiveClosure_Floyd_Warshall =item transitive_closure $tcg = $g->TransitiveClosure_Floyd_Warshall Return the transitive closure graph of the graph. =item transitive_closure_matrix_clear_cache $g->transitive_closure_matrix_clear_cache See L. =back You can query the reachability from $u to $v with =over 4 =item is_reachable $tcg->is_reachable($u, $v) =back See L for more information about creating and querying transitive closures. With =over 4 =item transitive_closure_matrix $tcm = $g->transitive_closure_matrix; =back you can (create if not existing and) query the transitive closure matrix that underlies the transitive closure graph. See L for more information. =head2 Mutators =over 4 =item add_vertices $g->add_vertices('d', 'e', 'f') Add zero or more vertices to the graph. Returns the graph. =item add_edges $g->add_edges(['d', 'e'], ['f', 'g']) $g->add_edges(qw(d e f g)); Add zero or more edges to the graph. The edges are specified as a list of array references, or as a list of vertices where the even (0th, 2nd, 4th, ...) items are start vertices and the odd (1st, 3rd, 5th, ...) are the corresponding end vertices. Returns the graph. For a hypergraph, each item in this list must be an array-ref of arguments suitable for C - so for undirected, of vertices; for directed, of two array-refs of vertices. =item rename_vertex $g->rename_vertex('d', 'e') Renames a vertex. It retains all of its edges. Throws exception if doesn't exist. Returns the graph. =item rename_vertices $g->rename_vertices(sub { uc $_[0] }) Calls a function for each vertex-name, renaming it to the return value. Returns the graph. =item filter_vertices $g->filter_vertices(sub { my ($g, $v)=@_; $v =~ /^a/i }) Calls a function for each vertex; if it returns false, the vertex is deleted. Passed the graph, the vertex, and if C, the ID (called once per "incarnation", i.e. ID). Returns the graph. =item filter_edges $g->filter_edges(sub { my ($g, $u, $v, $id)=@_; $id eq 'Bakerloo' }) Calls a function for each edge; if it returns false, the edge is deleted. Passed the graph, the vertices, and if C, the ID (called once per "incarnation", i.e. ID). Returns the graph. =item ingest $g->ingest($g2) Ingests all the vertices and edges of the given graph, including attributes. Returns the ingesting graph. =back =head2 Accessors =over 4 =item is_directed =item directed $g->is_directed() $g->directed() Return true if the graph is directed, false otherwise. =item is_undirected =item undirected $g->is_undirected() $g->undirected() Return true if the graph is undirected, false otherwise. =item is_refvertexed =item is_refvertexed_stringified =item refvertexed =item refvertexed_stringified Return true if the graph can handle references (including Perl objects) as vertices. =item vertices my $V = $g->vertices my @V = $g->vertices In scalar context, return the number of vertices in the graph. In list context, return the vertices, in no particular order. =item has_vertices $g->has_vertices() Return true if the graph has any vertices, false otherwise. =item edges my $E = $g->edges my @E = $g->edges In scalar context, return the number of edges in the graph. In list context, return the edges, in no particular order. I =item has_edges $g->has_edges() Return true if the graph has any edges, false otherwise. =item is_connected $g->is_connected For an undirected graph, return true if the graph is connected, false otherwise. Being connected means that from every vertex it is possible to reach every other vertex. If the graph has been created with a true C parameter, the time complexity is (essentially) O(V), otherwise O(V log V). See also L, L, L, and L, and L. For directed graphs, see L and L. =item connected_components @cc = $g->connected_components() For an undirected graph, returns the vertices of the connected components of the graph as a list of anonymous arrays. The ordering of the anonymous arrays or the ordering of the vertices inside the anonymous arrays (the components) is undefined. For directed graphs, see L and L. =item connected_component_by_vertex $i = $g->connected_component_by_vertex($v) For an undirected graph, return an index identifying the connected component the vertex belongs to, the indexing starting from zero. For the inverse, see L. If the graph has been created with a true C parameter, the time complexity is (essentially) O(1), otherwise O(V log V). See also L. For directed graphs, see L and L. =item connected_component_by_index @v = $g->connected_component_by_index($i) For an undirected graph, return the vertices of the ith connected component, the indexing starting from zero. The order of vertices is undefined, while the order of the connected components is same as from connected_components(). For the inverse, see L. For directed graphs, see L and L. =item same_connected_components $g->same_connected_components($u, $v, ...) For an undirected graph, return true if the vertices are in the same connected component. If the graph has been created with a true C parameter, the time complexity is (essentially) O(1), otherwise O(V log V). For directed graphs, see L and L. =item connected_graph $cg = $g->connected_graph For an undirected graph, return its connected graph. =item connectivity_clear_cache $g->connectivity_clear_cache See L. See L for further discussion. =item biconnectivity my ($ap, $bc, $br) = $g->biconnectivity For an undirected graph, return the various biconnectivity components of the graph: the articulation points (cut vertices), biconnected components, and bridges. Note: currently only handles connected graphs. =item is_biconnected $g->is_biconnected For an undirected graph, return true if the graph is biconnected (if it has no articulation points, also known as cut vertices). =item is_edge_connected $g->is_edge_connected For an undirected graph, return true if the graph is edge-connected (if it has no bridges). Note: more precisely, this would be called is_edge_biconnected, since there is a more general concept of being k-connected. =item is_edge_separable $g->is_edge_separable For an undirected graph, return true if the graph is edge-separable (if it has bridges). Note: more precisely, this would be called is_edge_biseparable, since there is a more general concept of being k-connected. =item articulation_points =item cut_vertices $g->articulation_points For an undirected graph, return the articulation points (cut vertices) of the graph as a list of vertices. The order is undefined. =item biconnected_components $g->biconnected_components For an undirected graph, return the biconnected components of the graph as a list of anonymous arrays of vertices in the components. The ordering of the anonymous arrays or the ordering of the vertices inside the anonymous arrays (the components) is undefined. Also note that one vertex can belong to more than one biconnected component. =item biconnected_component_by_vertex $i = $g->biconnected_component_by_index($v) For an undirected graph, return the indices identifying the biconnected components the vertex belongs to, the indexing starting from zero. The order of of the components is undefined. For the inverse, see L. For directed graphs, see L and L. =item biconnected_component_by_index @v = $g->biconnected_component_by_index($i) For an undirected graph, return the vertices in the ith biconnected component of the graph as an anonymous arrays of vertices in the component. The ordering of the vertices within a component is undefined. Also note that one vertex can belong to more than one biconnected component. =item same_biconnected_components $g->same_biconnected_components($u, $v, ...) For an undirected graph, return true if the vertices are in the same biconnected component. =item biconnected_graph $bcg = $g->biconnected_graph For an undirected graph, return its biconnected graph. See L for further discussion. =item bridges $g->bridges For an undirected graph, return the bridges of the graph as a list of anonymous arrays of vertices in the bridges. The order of bridges and the order of vertices in them is undefined. =item biconnectivity_clear_cache $g->biconnectivity_clear_cache See L. =item strongly_connected =item is_strongly_connected $g->is_strongly_connected For a directed graph, return true if the directed graph is strongly connected, false if not. See also L. For undirected graphs, see L, or L. =item strongly_connected_component_by_vertex $i = $g->strongly_connected_component_by_vertex($v) For a directed graph, return an index identifying the strongly connected component the vertex belongs to, the indexing starting from zero. For the inverse, see L. See also L. For undirected graphs, see L or L. =item strongly_connected_component_by_index @v = $g->strongly_connected_component_by_index($i) For a directed graph, return the vertices of the ith connected component, the indexing starting from zero. The order of vertices within a component is undefined, while the order of the connected components is as from strongly_connected_components(). For the inverse, see L. For undirected graphs, see L. =item same_strongly_connected_components $g->same_strongly_connected_components($u, $v, ...) For a directed graph, return true if the vertices are in the same strongly connected component. See also L. For undirected graphs, see L or L. =item strong_connectivity_clear_cache $g->strong_connectivity_clear_cache See L. =item weakly_connected =item is_weakly_connected $g->is_weakly_connected For a directed graph, return true if the directed graph is weakly connected, false if not. A weakly connected graph is also known as a I graph. Note that there are two mutually incompatible definitions of "weakly connected". The definition used here is based on connectedness in the underlying undirected graph, hence this is also sometimes called "undirected-connected". -- The other definition, proposed by Graham, Knuth, and Motzkin in 1972, is currently not available within the Graph module. See also L. For undirected graphs, see L or L. =item weakly_connected_components @wcc = $g->weakly_connected_components() For a directed graph, returns the vertices of the weakly connected components of the graph as a list of anonymous arrays. The ordering of the anonymous arrays or the ordering of the vertices inside the anonymous arrays (the components) is undefined. See also L. For undirected graphs, see L or L. =item weakly_connected_component_by_vertex $i = $g->weakly_connected_component_by_vertex($v) For a directed graph, return an index identifying the weakly connected component the vertex belongs to, the indexing starting from zero. For the inverse, see L. For undirected graphs, see L and L. =item weakly_connected_component_by_index @v = $g->weakly_connected_component_by_index($i) For a directed graph, return the vertices of the ith weakly connected component, the indexing starting zero. The order of vertices within a component is undefined, while the order of the weakly connected components is same as from weakly_connected_components(). For the inverse, see L. For undirected graphs, see L and L. =item same_weakly_connected_components $g->same_weakly_connected_components($u, $v, ...) Return true if the vertices are in the same weakly connected component. =item weakly_connected_graph $wcg = $g->weakly_connected_graph For a directed graph, return its weakly connected graph. For undirected graphs, see L and L. =item strongly_connected_components my @scc = $g->strongly_connected_components; For a directed graph, return the strongly connected components as a list of anonymous arrays. The elements in the anonymous arrays are the vertices belonging to the strongly connected component; both the elements and the components are in no particular order. Note that strongly connected components can have single-element components even without self-loops: if a vertex is any of I, I, or a I, the vertex is alone in its own strong component. See also L. For undirected graphs, see L, or see L. =item strongly_connected_graph my $scg = $g->strongly_connected_graph; See L for further discussion. Strongly connected graphs are also known as I. See also L. For undirected graphs, see L, or L. =item is_sink_vertex $g->is_sink_vertex($v) Return true if the vertex $v is a sink vertex, false if not. A sink vertex is defined as a vertex with predecessors but no successors: this definition means that isolated vertices are not sink vertices. If you want also isolated vertices, use is_successorless_vertex(). =item is_source_vertex $g->is_source_vertex($v) Return true if the vertex $v is a source vertex, false if not. A source vertex is defined as a vertex with successors but no predecessors: the definition means that isolated vertices are not source vertices. If you want also isolated vertices, use is_predecessorless_vertex(). =item is_successorless_vertex $g->is_successorless_vertex($v) Return true if the vertex $v has no successors (no edges leaving the vertex), false if it has. Isolated vertices will return true: if you do not want this, use is_sink_vertex(). =item is_successorful_vertex $g->is_successorful_vertex($v) Return true if the vertex $v has successors, false if not. =item is_predecessorless_vertex $g->is_predecessorless_vertex($v) Return true if the vertex $v has no predecessors (no edges entering the vertex), false if it has. Isolated vertices will return true: if you do not want this, use is_source_vertex(). =item is_predecessorful_vertex $g->is_predecessorful_vertex($v) Return true if the vertex $v has predecessors, false if not. =item is_isolated_vertex $g->is_isolated_vertex($v) Return true if the vertex $v is an isolated vertex: no successors and no predecessors. =item is_interior_vertex $g->is_interior_vertex($v) Return true if the vertex $v is an interior vertex: both successors and predecessors. =item is_exterior_vertex $g->is_exterior_vertex($v) Return true if the vertex $v is an exterior vertex: has either no successors or no predecessors, or neither. =item is_self_loop_vertex $g->is_self_loop_vertex($v) Return true if the vertex $v is a self loop vertex: has an edge from itself to itself. For an undirected hypergraph, only true if an edge has the vertex as its sole participant. =item sink_vertices @v = $g->sink_vertices() Return the sink vertices of the graph. In scalar context return the number of sink vertices. See L for the definition of a sink vertex. =item source_vertices @v = $g->source_vertices() Return the source vertices of the graph. In scalar context return the number of source vertices. See L for the definition of a source vertex. =item successorful_vertices @v = $g->successorful_vertices() Return the successorful vertices of the graph. In scalar context return the number of successorful vertices. =item successorless_vertices @v = $g->successorless_vertices() Return the successorless vertices of the graph. In scalar context return the number of successorless vertices. =item successors @s = $g->successors($v) Return the immediate successor vertices of the vertex. See also L, L, and L. =item all_successors @s = $g->all_successors(@v) For a directed graph, returns all successor vertices of the argument vertices, recursively. For undirected graphs, see L and L. See also L, L. =item successors_by_radius @s = $g->successors_by_radius(@v, $radius) For a directed graph, returns all successor vertices of the argument vertices, recursively. For undirected graphs, see L and L. See also L, L. =item neighbors =item neighbours @n = $g->neighbours($v) Return the neighboring/neighbouring vertices. Also known as the I. See also L L, and L. =item all_neighbors =item all_neighbours @n = $g->all_neighbours(@v) Return the neighboring/neighbouring vertices of the argument vertices, recursively. For a directed graph, recurses up predecessors and down successors. For an undirected graph, returns all the vertices reachable from the argument vertices: equivalent to C. See also L, L, and L. =item neighbors_by_radius =item neighbours_by_radius @n = $g->neighbours_by_radius(@v, $radius) Return the neighboring/neighbouring vertices of the argument vertices, recursively, out to the given radius. =item all_reachable @r = $g->all_reachable(@v) Return all the vertices reachable from the argument vertices, recursively. For a directed graph, equivalent to C. For an undirected graph, equivalent to C. The argument vertices are not included in the results unless there are explicit self-loops. See also L, L, L, and L. =item reachable_by_radius @r = $g->reachable_by_radius(@v, $radius) Return all the vertices reachable from the argument vertices, recursively, out to the given radius. =item predecessorful_vertices @v = $g->predecessorful_vertices() Return the predecessorful vertices of the graph. In scalar context return the number of predecessorful vertices. =item predecessorless_vertices @v = $g->predecessorless_vertices() Return the predecessorless vertices of the graph. In scalar context return the number of predecessorless vertices. =item predecessors @p = $g->predecessors($v) Return the immediate predecessor vertices of the vertex. See also L, L, and L. =item all_predecessors @p = $g->all_predecessors(@v) For a directed graph, returns all predecessor vertices of the argument vertices, recursively. For undirected graphs, see L and L. See also L, L. =item predecessors_by_radius @p = $g->predecessors_by_radius(@v, $radius) For a directed graph, returns all predecessor vertices of the argument vertices, recursively, out to the given radius. =item isolated_vertices @v = $g->isolated_vertices() Return the isolated vertices of the graph. In scalar context return the number of isolated vertices. See L for the definition of an isolated vertex. =item interior_vertices @v = $g->interior_vertices() Return the interior vertices of the graph. In scalar context return the number of interior vertices. See L for the definition of an interior vertex. =item exterior_vertices @v = $g->exterior_vertices() Return the exterior vertices of the graph. In scalar context return the number of exterior vertices. See L for the definition of an exterior vertex. =item self_loop_vertices @v = $g->self_loop_vertices() Return the self-loop vertices of the graph. In scalar context return the number of self-loop vertices. See L for the definition of a self-loop vertex. =item as_hashes ($nodes, $edges) = $g->as_hashes Return hash-refs which map vertices to their attributes, and for edges, a two-level hash mapping the predecessor to its successors, mapped to the attributes. If C is true, the vertices hash will have the second-level values be the multivertex's ID, and the third level will be attributes as above. If C is true, similar will be true for the edges hash. For a hypergraph, the edges will instead be an array-ref of hashes with a key of C, value a hash-ref (if C, two-level as above). Then with values of array-refs of vertex-names, for undirected: =over =item vertices =back And directed: =over =item predecessors =item successors =back =back =head2 Connected Graphs and Their Components In this discussion I refers to any of I, I, and I. B: if the vertices of the original graph are Perl objects, (in other words, references, so you must be using C) the vertices of the I are NOT by default usable as Perl objects because they are blessed into a package with a rather unusable name. By default, the vertex names of the I are formed from the names of the vertices of the original graph by (alphabetically sorting them and) concatenating their names with C<+>. The vertex attribute C is also used to store the list (as an array reference) of the original vertices. To change the 'supercomponent' vertex names and the whole logic of forming these supercomponents use the C) option to the method calls: $g->connected_graph(super_component => sub { ... }) $g->biconnected_graph(super_component => sub { ... }) $g->strongly_connected_graph(super_component => sub { ... }) The subroutine reference gets the 'subcomponents' (the vertices of the original graph) as arguments, and it is supposed to return the new supercomponent vertex, the "stringified" form of which is used as the vertex name. =head2 Degree A vertex has a degree based on the number of incoming and outgoing edges. This really makes sense only for directed graphs. =over 4 =item degree =item vertex_degree $d = $g->degree($v) $d = $g->vertex_degree($v) For directed graphs: the in-degree minus the out-degree at the vertex. For undirected graphs: the number of edges at the vertex (identical to C, C). =item in_degree $d = $g->in_degree($v) For directed graphs: the number of incoming edges at the vertex. For undirected graphs: the number of edges at the vertex (identical to C, C, C). =item out_degree $o = $g->out_degree($v) For directed graphs: The number of outgoing edges at the vertex. For undirected graphs: the number of edges at the vertex (identical to C, C, C). =back Related methods are =over 4 =item edges_at @e = $g->edges_at($v) The union of edges from, and edges to, the vertex. =item edges_from @e = $g->edges_from($v) The edges leaving the vertex. =item edges_to @e = $g->edges_to($v) The edges entering the vertex. =back =head2 Counted Vertices I are vertices with more than one instance, normally adding vertices is idempotent. To enable counted vertices on a graph, give the C parameter a true value use Graph; my $g = Graph->new(countvertexed => 1); To find out how many times the vertex has been added: =over 4 =item get_vertex_count my $c = $g->get_vertex_count($v); Return the count of the vertex, or undef if the vertex does not exist. =back =head2 Multiedges, Multivertices, Multigraphs I are edges with more than one "life", meaning that one has to delete them as many times as they have been added. Normally adding edges is idempotent (in other words, adding edges more than once makes no difference). There are two kinds or degrees of creating multiedges and multivertices. The two kinds are mutually exclusive. The weaker kind is called I, in which the edge or vertex has a count on it: add operations increase the count, and delete operations decrease the count, and once the count goes to zero, the edge or vertex is deleted. If there are attributes, they all are attached to the same vertex. You can think of this as the graph elements being I, or I, if that sounds more familiar. The stronger kind is called (true) I, in which the edge or vertex really has multiple separate identities, so that you can for example attach different attributes to different instances. To enable multiedges on a graph: use Graph; my $g0 = Graph->new(countedged => 1); # "weaker" kind my $g0 = Graph->new(multiedged => 1); # "stronger" kind Similarly for vertices use Graph; my $g1 = Graph->new(countvertexed => 1); # "weaker" kind my $g1 = Graph->new(multivertexed => 1); # "stronger" kind You can test for these by =over 4 =item is_countedged =item countedged $g->is_countedged $g->countedged Return true if the graph is countedged. =item is_countvertexed =item countvertexed $g->is_countvertexed $g->countvertexed Return true if the graph is countvertexed. =item is_multiedged =item multiedged $g->is_multiedged $g->multiedged Return true if the graph is multiedged. =item is_multivertexed =item multivertexed $g->is_multivertexed $g->multivertexed Return true if the graph is multivertexed. =back A multiedged (either the weak kind or the strong kind) graph is a I, for which you can test with C. B: The various graph algorithms do not in general work well with multigraphs (they often assume I, that is, no multiedges or loops), and no effort has been made to test all algorithms with multigraphs. However, L and L I been tested with multiedges and do work (they choose the lowest weight of a given edge's incarnations). vertices() and edges() will return the multiple elements: if you want just the unique elements, use =over 4 =item unique_vertices =item unique_edges @uv = $g->unique_vertices; # unique @mv = $g->vertices; # possible multiples @ue = $g->unique_edges; @me = $g->edges; =back If you are using (the stronger kind of) multielements, you should use the I variants: =over 4 =item add_vertex_by_id =item has_vertex_by_id =item delete_vertex_by_id =item add_edge_by_id =item has_edge_by_id =item delete_edge_by_id =back $g->add_vertex_by_id($v, $id) $g->has_vertex_by_id($v, $id) $g->delete_vertex_by_id($v, $id) $g->add_edge_by_id($u, $v, $id) $g->has_edge_by_id($u, $v, $id) $g->delete_edge_by_id($u, $v, $id) These interfaces only apply to multivertices and multiedges. When you delete the last vertex/edge in a multivertex/edge, the whole vertex/edge is deleted. You can use add_vertex()/add_edge() on a multivertex/multiedge graph, in which case an id is generated automatically. To find out which the generated id was, you need to use =over 4 =item add_vertex_get_id =item add_edge_get_id =back $idv = $g->add_vertex_get_id($v) $ide = $g->add_edge_get_id($u, $v) To return all the ids of vertices/edges in a multivertex/multiedge, use =over 4 =item get_multivertex_ids =item get_multiedge_ids =back $g->get_multivertex_ids($v) $g->get_multiedge_ids($u, $v) The ids are returned in random order. To find out how many times the edge has been added (this works for either kind of multiedges): =over 4 =item get_edge_count my $c = $g->get_edge_count($u, $v); Return the count (the "countedness") of the edge, or undef if the edge does not exist. =back The following multi-entity utility functions exist, mirroring the non-multi vertices and edges: =over 4 =item add_path_by_id =item add_edges_by_id =item add_weighted_edge_by_id =item add_weighted_edges_by_id =item add_weighted_path_by_id =item add_weighted_vertex_by_id =item add_weighted_vertices_by_id =item delete_edge_weight_by_id =item delete_vertex_weight_by_id =item get_edge_weight_by_id =item get_vertex_weight_by_id =item has_edge_weight_by_id =item has_vertex_weight_by_id =item set_edge_weight_by_id =item set_vertex_weight_by_id =back =head2 Topological Sort =over 4 =item topological_sort =item toposort my @ts = $g->topological_sort; Return the vertices of the graph sorted topologically. Note that there may be several possible topological orderings; one of them is returned. If the graph contains a cycle, a fatal error is thrown, you can either use C to trap that, or supply the C argument with a true value my @ts = $g->topological_sort(empty_if_cyclic => 1); in which case an empty array is returned if the graph is cyclic. =back =head2 Minimum Spanning Trees (MST) Minimum Spanning Trees or MSTs are tree subgraphs derived from an undirected graph. MSTs "span the graph" (covering all the vertices) using as lightly weighted (hence the "minimum") edges as possible. =over 4 =item MST_Kruskal $mstg = $g->MST_Kruskal; Returns the Kruskal MST of the graph. =item MST_Prim $mstg = $g->MST_Prim(%opt); Returns the Prim MST of the graph. You can choose the first vertex with $opt{ first_root }. =item MST_Dijkstra =item minimum_spanning_tree $mstg = $g->MST_Dijkstra; $mstg = $g->minimum_spanning_tree; Aliases for MST_Prim. =back =head2 Single-Source Shortest Paths (SSSP) Single-source shortest paths, also known as Shortest Path Trees (SPTs). For either a directed or an undirected graph, return a (tree) subgraph that from a single start vertex (the "single source") travels the shortest possible paths (the paths with the lightest weights) to all the other vertices. Note that the SSSP is neither reflexive (the shortest paths do not include the zero-length path from the source vertex to the source vertex) nor transitive (the shortest paths do not include transitive closure paths). If no weight is defined for an edge, 1 (one) is assumed. =over 4 =item SPT_Dijkstra $sptg = $g->SPT_Dijkstra($root) $sptg = $g->SPT_Dijkstra(%opt) Return as a graph the the single-source shortest paths of the graph using Dijkstra's algorithm. The graph cannot contain negative edges (negative edges cause the algorithm to abort with an error message C). You can choose the first vertex of the result with either a single vertex argument or with $opt{ first_root }, otherwise a random vertex is chosen. B: note that all the vertices might not be reachable from the selected (explicit or random) start vertex. B: after the first reachable tree from the first start vertex has been finished, and if there still are unvisited vertices, SPT_Dijkstra will keep on selecting unvisited vertices. The next roots (in case the first tree doesn't visit all the vertices) can be chosen by setting one of the following options to true: C, C, C, C. The C is the most customizable: the value needs to be a subroutine reference which will receive the graph and the unvisited vertices as hash reference. If you want to only visit the first tree, use C sub { undef }>. The rest of these options are booleans. If none of them are true, a random unvisited vertex will be selected. The first start vertex is available as the graph attribute C). The result weights of vertices can be retrieved from the result graph by my $w = $sptg->get_vertex_attribute($v, 'weight'); The predecessor vertex of a vertex in the result graph can be retrieved by my $u = $sptg->get_vertex_attribute($v, 'p'); ("A successor vertex" cannot be retrieved as simply because a single vertex can have several successors. You can first find the C vertices and then remove the predecessor vertex.) If you want to find the shortest path between two vertices, see L. =item SSSP_Dijkstra =item single_source_shortest_paths Aliases for SPT_Dijkstra. =item SP_Dijkstra @path = $g->SP_Dijkstra($u, $v) Return the vertices in the shortest path in the graph $g between the two vertices $u, $v. If no path can be found, an empty list is returned. Uses SPT_Dijkstra(). =item SPT_Dijkstra_clear_cache $g->SPT_Dijkstra_clear_cache See L. =item SPT_Bellman_Ford $sptg = $g->SPT_Bellman_Ford(%opt) Return as a graph the single-source shortest paths of the graph using Bellman-Ford's algorithm. The graph can contain negative edges but not negative cycles (negative cycles cause the algorithm to abort with an error message C). You can choose the start vertex of the result with either a single vertex argument or with $opt{ first_root }, otherwise a random vertex is chosen. B: note that all the vertices might not be reachable from the selected (explicit or random) start vertex. The start vertex is available as the graph attribute C). The result weights of vertices can be retrieved from the result graph by my $w = $sptg->get_vertex_attribute($v, 'weight'); The predecessor vertex of a vertex in the result graph can be retrieved by my $u = $sptg->get_vertex_attribute($v, 'p'); ("A successor vertex" cannot be retrieved as simply because a single vertex can have several successors. You can first find the C vertices and then remove the predecessor vertex.) If you want to find the shortest path between two vertices, see L. =item SSSP_Bellman_Ford Alias for SPT_Bellman_Ford. =item SP_Bellman_Ford @path = $g->SP_Bellman_Ford($u, $v) Return the vertices in the shortest path in the graph $g between the two vertices $u, $v. If no path can be found, an empty list is returned. Uses SPT_Bellman_Ford(). =item SPT_Bellman_Ford_clear_cache $g->SPT_Bellman_Ford_clear_cache See L. =back =head2 All-Pairs Shortest Paths (APSP) For either a directed or an undirected graph, return the APSP object describing all the possible paths between any two vertices of the graph. If no weight is defined for an edge, 1 (one) is assumed. Note that weight of 0 (zero) does not mean do not use this edge, it means essentially the opposite: an edge that has zero cost, an edge that makes the vertices the same. =over 4 =item APSP_Floyd_Warshall =item all_pairs_shortest_paths my $apsp = $g->APSP_Floyd_Warshall(...); Return the all-pairs shortest path object computed from the graph using the Floyd-Warshall algorithm, of class L. The length of a path between two vertices is the sum of weight attribute of the edges along the shortest path between the two vertices. If no weight attribute name is specified explicitly $g->APSP_Floyd_Warshall(attribute_name => 'height'); the attribute C is assumed. B Once computed, you can query the APSP object with =over 8 =item path_length my $l = $apsp->path_length($u, $v); Return the length of the shortest path between the two vertices. =item path_vertices my @v = $apsp->path_vertices($u, $v); Return the list of vertices along the shortest path. =item path_successor my $u = $apsp->path_successor($u, $v); Returns the successor of vertex $u in the all-pairs shortest path to $v. =item all_paths my @paths = $apsp->all_paths($u, $v); Return list of array-refs with all the paths from $u to $v. =item average_path_length my $apl = $g->average_path_length; # All vertex pairs. my $apl = $g->average_path_length($u); # From $u. my $apl = $g->average_path_length($u, undef); # From $u. my $apl = $g->average_path_length($u, $v); # From $u to $v. my $apl = $g->average_path_length(undef, $v); # To $v. Return the average (shortest) path length over all the non-zero paths between vertex pairs of the graph's transitive closure. Depending on the arguments, this can be from a vertex, between two vertices, or to a vertex. An undefined (or not-given) vertex will match all. =item longest_path my @lp = $g->longest_path; my $lp = $g->longest_path; In scalar context return the I path length over all the vertex pairs of the graph. In list context return the vertices along a I path. Note that there might be more than one such path; this interface returns a random one of them. B: this returns the I path, B the I path. =item diameter =item graph_diameter my $gd = $g->diameter; The longest path over all the vertex pairs is known as the I. For an unconnected graph, single-vertex, or empty graph, returns C. =item shortest_path my @sp = $g->shortest_path; my $sp = $g->shortest_path; In scalar context return the shortest length over all the vertex pairs of the graph. In list context return the vertices along a shortest path. Note that there might be more than one such path; this interface returns a random one of them. For an unconnected, single-vertex, or empty graph, returns C or an empty list. =item radius my $gr = $g->radius; The I path over all the vertex pairs is known as the I. See also L. For an unconnected, single-vertex, or empty graph, returns Infinity. =item center_vertices =item centre_vertices my @c = $g->center_vertices; my @c = $g->center_vertices($delta); The I is the set of vertices for which the I is equal to the I. The vertices are returned in random order. By specifying a delta value you can widen the criterion from strict equality (handy for non-integer edge weights). For an unconnected, single-vertex, or empty graph, returns an empty list. =item vertex_eccentricity my $ve = $g->vertex_eccentricity($v); The longest path to a vertex is known as the I. If the graph is unconnected, single-vertex, or empty graph, returns Inf. =back You can walk through the matrix of the shortest paths by using =over 4 =item for_shortest_paths $n = $g->for_shortest_paths($callback) The number of shortest paths is returned (this should be equal to V*V). The $callback is a sub reference that receives four arguments: the transitive closure object from Graph::TransitiveClosure, the two vertices, and the index to the current shortest paths (0..V*V-1). =back =back =head2 Clearing cached results For many graph algorithms there are several different but equally valid results. (Pseudo)Randomness is used internally by the Graph module to for example pick a random starting vertex, and to select random edges from a vertex. For efficiency the computed result is often cached to avoid recomputing the potentially expensive operation, and this also gives additional determinism (once a correct result has been computed, the same result will always be given). However, sometimes the exact opposite is desirable, and the possible alternative results are wanted (within the limits of the pseudorandomness: not all the possible solutions are guaranteed to be returned, usually only a subset is returned). To undo the caching, the following methods are available: =over 4 =item * connectivity_clear_cache Affects L, L, L, L, L, L, L, L, L, L, L, L. =item * biconnectivity_clear_cache Affects L, L, L, L, L, L, L, L, L, L, L. =item * strong_connectivity_clear_cache Affects L, L, L, L, L, L, L. =item * SPT_Dijkstra_clear_cache Affects L, L, L, L. =item * SPT_Bellman_Ford_clear_cache Affects L, L, L. =back Note that any such computed and cached results are of course always automatically discarded whenever the graph is modified. =head2 Random You can either ask for random elements of existing graphs or create random graphs. =over 4 =item random_vertex my $v = $g->random_vertex; Return a random vertex of the graph, or undef if there are no vertices. =item random_edge my $e = $g->random_edge; Return a random edge of the graph as an array reference having the vertices as elements, or undef if there are no edges. =item random_successor my $v = $g->random_successor($v); Return a random successor of the vertex in the graph, or undef if there are no successors. =item random_predecessor my $u = $g->random_predecessor($v); Return a random predecessor of the vertex in the graph, or undef if there are no predecessors. =item random_graph my $g = Graph->random_graph(%opt); Construct a random graph. The I<%opt> B contain the C argument vertices => vertices_def where the I is one of =over 8 =item * an array reference where the elements of the array reference are the vertices =item * a number N in which case the vertices will be integers 0..N-1 =back =back The %opt may have either of the argument C or the argument C. Both are used to define how many random edges to add to the graph; C is an absolute number, while C is a relative number (relative to the number of edges in a complete graph, C). The number of edges can be larger than C, but only if the graph is countedged. The random edges will not include self-loops. If neither C nor C is specified, an C of 0.5 is assumed. If you want repeatable randomness (what is an oxymoron?) you can use the C option: $g = Graph->random_graph(vertices => 10, random_seed => 1234); As this uses the standard Perl srand(), the usual caveat applies: use it sparingly, and consider instead using a single srand() call at the top level of your application. The default random distribution of edges is flat, that is, any pair of vertices is equally likely to appear. To define your own distribution, use the C option: $g = Graph->random_graph(vertices => 10, random_edge => \&d); where C is a code reference receiving I<($g, $u, $v, $p)> as parameters, where the I<$g> is the random graph, I<$u> and I<$v> are the vertices, and the I<$p> is the probability ([0,1]) for a flat distribution. It must return a probability ([0,1]) that the vertices I<$u> and I<$v> have an edge between them. Note that returning one for a particular pair of vertices doesn't guarantee that the edge will be present in the resulting graph because the required number of edges might be reached before that particular pair is tested for the possibility of an edge. Be very careful to adjust also C or C so that there is a possibility of the filling process terminating. B: a known problem with randomness in openbsd pre-perl-5.20 is that using a seed does not give you deterministic randomness. This affects any Perl code, not just Graph. =head2 Attributes You can attach free-form attributes (key-value pairs, in effect a full Perl hash) to each vertex, edge, and the graph itself. Note that attaching attributes does slow down some other operations on the graph by a factor of three to ten. For example adding edge attributes does slow down anything that walks through all the edges. For vertex attributes: =over 4 =item set_vertex_attribute $g->set_vertex_attribute($v, $name, $value) Set the named vertex attribute. If the vertex does not exist, the set_...() will create it, and the other vertex attribute methods will return false or empty. B =item get_vertex_attribute $value = $g->get_vertex_attribute($v, $name) Return the named vertex attribute. =item has_vertex_attribute $g->has_vertex_attribute($v, $name) Return true if the vertex has an attribute, false if not. =item delete_vertex_attribute $g->delete_vertex_attribute($v, $name) Delete the named vertex attribute. =item set_vertex_attributes $g->set_vertex_attributes($v, $attr) Set all the attributes of the vertex from the anonymous hash $attr. B: any attributes beginning with an underscore (C<_>) are reserved for the internal use of the Graph module. =item get_vertex_attributes $attr = $g->get_vertex_attributes($v) Return all the attributes of the vertex as an anonymous hash, or C if no such vertex. =item get_vertex_attribute_names @name = $g->get_vertex_attribute_names($v) Return the names of vertex attributes. =item get_vertex_attribute_values @value = $g->get_vertex_attribute_values($v) Return the values of vertex attributes. =item has_vertex_attributes $g->has_vertex_attributes($v) Return true if the vertex has any attributes, false if not. =item delete_vertex_attributes $g->delete_vertex_attributes($v) Delete all the attributes of the named vertex. =back If you are using multivertices, use the I variants: =over 4 =item set_vertex_attribute_by_id =item get_vertex_attribute_by_id =item has_vertex_attribute_by_id =item delete_vertex_attribute_by_id =item set_vertex_attributes_by_id =item get_vertex_attributes_by_id =item get_vertex_attribute_names_by_id =item get_vertex_attribute_values_by_id =item has_vertex_attributes_by_id =item delete_vertex_attributes_by_id $g->set_vertex_attribute_by_id($v, $id, $name, $value) $g->get_vertex_attribute_by_id($v, $id, $name) $g->has_vertex_attribute_by_id($v, $id, $name) $g->delete_vertex_attribute_by_id($v, $id, $name) $g->set_vertex_attributes_by_id($v, $id, $attr) $g->get_vertex_attributes_by_id($v, $id) $g->get_vertex_attribute_values_by_id($v, $id) $g->get_vertex_attribute_names_by_id($v, $id) $g->has_vertex_attributes_by_id($v, $id) $g->delete_vertex_attributes_by_id($v, $id) =back For edge attributes: =over 4 =item set_edge_attribute $g->set_edge_attribute($u, $v, $name, $value) Set the named edge attribute. If the edge does not exist, the set_...() will create it, and the other edge attribute methods will return false or empty. B: any attributes beginning with an underscore (C<_>) are reserved for the internal use of the Graph module. =item get_edge_attribute $value = $g->get_edge_attribute($u, $v, $name) Return the named edge attribute. =item has_edge_attribute $g->has_edge_attribute($u, $v, $name) Return true if the edge has an attribute, false if not. =item delete_edge_attribute $g->delete_edge_attribute($u, $v, $name) Delete the named edge attribute. =item set_edge_attributes $g->set_edge_attributes($u, $v, $attr) Set all the attributes of the edge from the anonymous hash $attr. B: any attributes beginning with an underscore (C<_>) are reserved for the internal use of the Graph module. =item get_edge_attributes $attr = $g->get_edge_attributes($u, $v) Return all the attributes of the edge as an anonymous hash, or C if no such edge. =item get_edge_attribute_names @name = $g->get_edge_attribute_names($u, $v) Return the names of edge attributes. =item get_edge_attribute_values @value = $g->get_edge_attribute_values($u, $v) Return the values of edge attributes. =item has_edge_attributes $g->has_edge_attributes($u, $v) Return true if the edge has any attributes, false if not. =item delete_edge_attributes $g->delete_edge_attributes($u, $v) Delete all the attributes of the named edge. =back If you are using multiedges, use the I variants: =over 4 =item set_edge_attribute_by_id =item get_edge_attribute_by_id =item has_edge_attribute_by_id =item delete_edge_attribute_by_id =item set_edge_attributes_by_id =item get_edge_attributes_by_id =item get_edge_attribute_names_by_id =item get_edge_attribute_values_by_id =item has_edge_attributes_by_id =item delete_edge_attributes_by_id $g->set_edge_attribute_by_id($u, $v, $id, $name, $value) $g->get_edge_attribute_by_id($u, $v, $id, $name) $g->has_edge_attribute_by_id($u, $v, $id, $name) $g->delete_edge_attribute_by_id($u, $v, $id, $name) $g->set_edge_attributes_by_id($u, $v, $id, $attr) $g->get_edge_attributes_by_id($u, $v, $id) $g->get_edge_attribute_values_by_id($u, $v, $id) $g->get_edge_attribute_names_by_id($u, $v, $id) $g->has_edge_attributes_by_id($u, $v, $id) $g->delete_edge_attributes_by_id($u, $v, $id) =back For handling transparently graphs that are either C or not: =over =item get_edge_attribute_all To get all values of a given attribute for a given edge, use C: @values = $g->get_edge_attribute_all($u, $v, $name) This will return all defined values for that edge and attribute, whether the graph is C or not. This will be in no particular order. This is useful for path-weight calculation. =back For graph attributes: =over 4 =item set_graph_attribute $g->set_graph_attribute($name, $value) Set the named graph attribute. B: any attributes beginning with an underscore (C<_>) are reserved for the internal use of the Graph module. =item get_graph_attribute $value = $g->get_graph_attribute($name) Return the named graph attribute. =item has_graph_attribute $g->has_graph_attribute($name) Return true if the graph has an attribute, false if not. =item delete_graph_attribute $g->delete_graph_attribute($name) Delete the named graph attribute. =item set_graph_attributes $g->get_graph_attributes($attr) Set all the attributes of the graph from the anonymous hash $attr. B: any attributes beginning with an underscore (C<_>) are reserved for the internal use of the Graph module. =item get_graph_attributes $attr = $g->get_graph_attributes() Return all the attributes of the graph as an anonymous hash. =item get_graph_attribute_names @name = $g->get_graph_attribute_names() Return the names of graph attributes. =item get_graph_attribute_values @value = $g->get_graph_attribute_values() Return the values of graph attributes. =item has_graph_attributes $g->has_graph_attributes() Return true if the graph has any attributes, false if not. =item delete_graph_attributes $g->delete_graph_attributes() Delete all the attributes of the named graph. =back =head2 Weighted As convenient shortcuts the following methods add, query, and manipulate the attribute C with the specified value to the respective Graph elements. =over 4 =item add_weighted_edge $g->add_weighted_edge($u, $v, $weight) =item add_weighted_edges $g->add_weighted_edges($u1, $v1, $weight1, ...) =item add_weighted_path $g->add_weighted_path($v1, $weight1, $v2, $weight2, $v3, ...) =item add_weighted_vertex $g->add_weighted_vertex($v, $weight) =item add_weighted_vertices $g->add_weighted_vertices($v1, $weight1, $v2, $weight2, ...) =item delete_edge_weight $g->delete_edge_weight($u, $v) =item delete_vertex_weight $g->delete_vertex_weight($v) =item get_edge_weight $g->get_edge_weight($u, $v) =item get_vertex_weight $g->get_vertex_weight($v) =item has_edge_weight $g->has_edge_weight($u, $v) =item has_vertex_weight $g->has_vertex_weight($v) =item set_edge_weight $g->set_edge_weight($u, $v, $weight) =item set_vertex_weight $g->set_vertex_weight($v, $weight) =back =head2 Isomorphism Two graphs being I means that they are structurally the same graph, the difference being that the vertices might have been I or I. For example in the below example $g0 and $g1 are isomorphic: the vertices C have been renamed as C. $g0 = Graph->new; $g0->add_edges(qw(a b a c c d)); $g1 = Graph->new; $g1->add_edges(qw(a x x y a z)); In the general case determining isomorphism is I, in other words, really hard (time-consuming), no other ways of solving the problem are known than brute force check of of all the possibilities (with possible optimization tricks, of course, but brute force still rules at the end of the day). A B at whether two graphs B be isomorphic is possible via the method =over 4 =item could_be_isomorphic $g0->could_be_isomorphic($g1) =back If the graphs do not have the same number of vertices and edges, false is returned. If the distribution of I and I at the vertices of the graphs does not match, false is returned. Otherwise, true is returned. What is actually returned is the maximum number of possible isomorphic graphs between the two graphs, after the above sanity checks have been conducted. It is basically the product of the factorials of the absolute values of in-degrees and out-degree pairs at each vertex, with the isolated vertices ignored (since they could be reshuffled and renamed arbitrarily). Note that for large graphs the product of these factorials can overflow the maximum presentable number (the floating point number) in your computer (in Perl) and you might get for example I as the result. =head2 Miscellaneous =over 4 =item betweenness %b = $g->betweenness Returns a map of vertices to their Freeman's betweennesses: C_b(v) = \sum_{s \neq v \neq t \in V} \frac{\sigma_{s,t}(v)}{\sigma_{s,t}} It is described in Freeman LC. A set of measures of centrality based on betweenness. 1977. https://www.scribd.com/document/580201172/A-Set-of-Measures-of-Centrality-Based-on-Betweenness and in Newman MEJ. A measure of betweenness centrality based on random walks. 2003. http://arxiv.org/pdf/cond-mat/0309045 The implementation is based on the algorithm from: Brandes U. A faster algorithm for betweenness centrality. 2000. https://doi.org/10.1080/0022250X.2001.9990249. =item clustering_coefficient $gamma = $g->clustering_coefficient() ($gamma, %clustering) = $g->clustering_coefficient() Returns the clustering coefficient gamma as described in Duncan J. Watts and Steven Strogatz, Collective dynamics of 'small-world' networks, https://web.archive.org/web/20120616204225/http://audiophile.tam.cornell.edu/SS_nature_smallworld.pdf In scalar context returns just the average gamma, in list context returns the average gamma and a hash of vertices to clustering coefficients. Returns an empty list (and therefore undefined in scalar context) if the graph has no vertices. =item connected_subgraphs @s = $g->connected_subgraphs; Returns all connected subgraphs of $g. =item subgraph_by_radius $s = $g->subgraph_by_radius(@v, $radius); Returns a subgraph representing the ball of $radius around the given vertices (breadth-first search). =back The "expect" methods can be used to test a graph and croak if the graph call is not as expected. =over 4 =item expect_acyclic =item expect_dag =item expect_directed =item expect_hyperedged =item expect_multiedge =item expect_multiedged =item expect_multivertex =item expect_multivertexed =item expect_no_args =item expect_non_multiedge =item expect_non_multiedged =item expect_non_multivertex =item expect_non_multivertexed =item expect_non_unionfind =item expect_undirected =back In many algorithms it is useful to have a value representing the infinity. The Graph provides (and itself uses): =over 4 =item Infinity (Not exported, use Graph::Infinity explicitly) =back =head2 Size Requirements A graph takes up at least 1172 bytes of memory. A vertex takes up at least 100 bytes of memory. An edge takes up at least 400 bytes of memory. (A Perl scalar value takes 16 bytes, or 12 bytes if it's a reference.) These size approximations are B approximate and optimistic (they are based on total_size() of Devel::Size). In real life many factors affect these numbers, for example how Perl is configured. The numbers are for a 32-bit platform and for Perl 5.8.8. Roughly, the above numbers mean that in a megabyte of memory you can fit for example a graph of about 1000 vertices and about 2500 edges. =head2 Hyperedges, hypergraphs B: this is a rather thinly tested feature, and the theory is even less so. Do not expect this to stay as it is (or at all) in future releases. B: most usual graph algorithms (and basic concepts) break horribly (or at least will look funny) with these hyperthingies. Caveat emptor. Hyperedges are edges that connect a number of vertices different from the usual two. Hypergraphs are graphs with hyperedges. To enable hyperness when constructing Graphs use the C attribute: my $h = Graph->new(hyperedged => 1); To test for hyperness of a graph use the =over 4 =item is_hyperedged =item hyperedged $g->is_hyperedged $g->hyperedged =back Edges in hypergraphs are either directed or undirected, as with simple graphs. If undirected, the edge is a blob of 0 or more vertices. For directed, the set of heads and set of tails are also possibly empty. In general, hypergraphs are simply generalisations of simple-graph ideas, with some of the arbitrary limitations removed. For more information on directed hypergraphs, see L. It defines hyperarcs (directed edges in a hypergraph) as ordered pairs of subsets of V, and hyperedges (undirected) as single subsets of V. Since sets are unordered and elements within them are unique, this implies that the only valuable use for hypergraphs is where in a given connection entity (edge or arc), each vertex only appears at most once. Additionally, how the C property of edges works may change. The underpinning notion is that each edge will be considered an entry in an incidence matrix (dimensions |V| x |E|), with values of either (0, 1=participating) for undirected (hyperedges), or (-1=tail, 0, 1=head) for directed (hyperarcs) against each vertex. An extension to this is that to extend directed multigraphs with self-loops (aka "quivers") to hypergraphs, the incidence-matrix values will instead be a bitfield, with bit 0 being participation in the tail, and bit 1 in the head. =head2 DIAGNOSTICS =over 4 =item * Graph::...Map...: arguments X expected Y ... If you see these (more user-friendly error messages should have been triggered above and before these) please report any such occurrences, but in general you should be happy to see these since it means that an attempt to call something with a wrong number of arguments was caught in time. =item * Graph::add_edge: graph is not hyperedged ... Maybe you used add_weighted_edge() with only the two vertex arguments. =item * Not an ARRAY reference at lib/Graph.pm ... One possibility is that you have code based on Graph 0.2xxxx that assumes Graphs being blessed hash references, possibly also assuming that certain hash keys are available to use for your own purposes. In Graph 0.50 none of this is true. Please do not expect any particular internal implementation of Graphs. Use inheritance and graph/vertex/edge attributes instead. Another possibility is that you meant to have objects (blessed references) as graph vertices, but forgot to use C (see L) when creating the graph. =item * Deep recursion on subroutine "Graph::_biconnectivity_dfs" at ... If you have more than 100 vertices, the recursive algorithm will trigger Perl's recursion protection. If you set environment variable C to a true value, this protection will be disabled, e.g.: $ GRAPH_ALLOW_RECURSION=1 perl -Ilib util/grand.pl --test=bcc 101 =back =head1 ACKNOWLEDGEMENTS All bad terminology, bugs, and inefficiencies are naturally mine, all mine, and not the fault of the below. Thanks to Nathan Goodman and Andras Salamon for bravely betatesting my pre-0.50 code. If they missed something, that was only because of my fiendish code. The following literature for algorithms and some test cases: =over 4 =item * Algorithms in C, Third Edition, Part 5, Graph Algorithms, Robert Sedgewick, Addison Wesley =item * Introduction to Algorithms, First Edition, Cormen-Leiserson-Rivest, McGraw Hill =item * Graphs, Networks and Algorithms, Dieter Jungnickel, Springer =back =head1 SEE ALSO Persistent/Serialized graphs? You want to read/write Graphs? See the L and L in CPAN. =head1 AUTHOR Jarkko Hietaniemi F Now being maintained by Neil Bowers Eneilb@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright (c) 1998-2014 Jarkko Hietaniemi. All rights reserved. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut Graph-0.9735/lib/Graph.pm0000644000175000017500000021742414771263272015002 0ustar osboxesosboxespackage Graph; use strict; use warnings; BEGIN { warnings->unimport('recursion') if $ENV{GRAPH_ALLOW_RECURSION} } sub __carp_confess { require Carp; Carp::confess(@_) } BEGIN { if (0) { # SET THIS TO ZERO FOR TESTING AND RELEASES! $SIG{__DIE__ } = \&__carp_confess; $SIG{__WARN__} = \&__carp_confess; } } use Graph::AdjacencyMap qw(:flags :fields); our $VERSION = '0.9735'; require 5.006; # Weak references are absolutely required. my @GRAPH_PROPS_COPIED = qw( undirected refvertexed countvertexed multivertexed __stringified hyperedged countedged multiedged ); my $_empty_array = []; sub _empty_array () { $_empty_array } my $can_deep_copy_Storable; sub _can_deep_copy_Storable () { return $can_deep_copy_Storable if defined $can_deep_copy_Storable; return $can_deep_copy_Storable = 0 if $] < 5.010; # no :load tag Safe 5.8 eval { require Storable; require B::Deparse; Storable->VERSION(2.05); B::Deparse->VERSION(0.61); }; $can_deep_copy_Storable = !$@; } sub _F () { 0 } # Flags. sub _G () { 1 } # Generation. sub _V () { 2 } # Vertices. sub _E () { 3 } # Edges. sub _A () { 4 } # Attributes. sub _U () { 5 } # Union-Find. my $Inf; BEGIN { if ($] >= 5.022) { $Inf = eval '+"Inf"'; # uncoverable statement } else { local $SIG{FPE}; # uncoverable statement eval { $Inf = exp(999) } || # uncoverable statement eval { $Inf = 9**9**9 } || # uncoverable statement eval { $Inf = 1e+999 } || # uncoverable statement { $Inf = 1e+99 }; # uncoverable statement # Close enough for most practical purposes. } } sub Infinity () { $Inf } # Graphs are blessed array references. # - The first element contains the flags. # - The second element is the vertices. # - The third element is the edges. # - The fourth element is the attributes of the whole graph. # The defined flags for Graph are: # - unionfind # The vertices are contained in a "simplemap" # (if no attributes) or in a "map". # The edges are always in a "map". # The defined flags for maps are: # - _COUNT for countedness: more than one instance # expects one for vertices and two for edges # - _UNORD for unordered coordinates (a set): if _UNORD is not set # the coordinates are assumed to be meaningfully ordered # Vertices and edges assume none of these flags. use Graph::Attribute array => _A, map => 'graph'; sub stringify { my ($u, $h) = (&is_undirected, &is_hyperedged); my $e = $u ? '=' : '-'; my @edges = map join($e, $u ? sort { "$a" cmp "$b" } @$_ : $h ? map '['.join(",", sort { "$a" cmp "$b" } @$_).']', @$_ : @$_), &_edges05; my @s = sort @edges; push @s, sort { "$a" cmp "$b" } &isolated_vertices; join(",", @s); } sub eq { "$_[0]" eq "$_[1]" } sub boolify { 1; # Important for empty graphs: they stringify to "", which is false. } sub ne { "$_[0]" ne "$_[1]" } use overload '""' => \&stringify, 'bool' => \&boolify, 'eq' => \&eq, 'ne' => \≠ sub _opt { my ($opt, $flags, %flags) = @_; while (my ($flag, $FLAG) = each %flags) { $$flags |= $FLAG if delete $opt->{$flag}; $$flags &= ~$FLAG if delete $opt->{"non$flag"}; } } sub _opt_get { my ($opt, $key, $var) = @_; return if !exists $opt->{$key}; $$var = delete $opt->{$key}; } sub _opt_unknown { my ($opt) = @_; return unless my @opt = keys %$opt; __carp_confess sprintf "@{[(caller(1))[3]]}: Unknown option%s: @{[map qq['$_'], sort @opt]}", @opt > 1 ? 's' : ''; } sub _opt_from_existing { my ($g) = @_; my %existing; $existing{$_}++ for grep $g->$_, @GRAPH_PROPS_COPIED; $existing{unionfind}++ if $g->has_union_find; %existing; } sub _opt_to_vflags { my ($vflags, $opt) = (0, @_); _opt($opt, \$vflags, countvertexed => _COUNT, multivertexed => _MULTI, refvertexed => _REF, refvertexed_stringified => _REFSTR , __stringified => _STR, ); $vflags; } sub _opt_to_eflags { my ($eflags, $opt) = (0, @_); $opt->{undirected} = !delete $opt->{directed} if exists $opt->{directed}; _opt($opt, \$eflags, countedged => _COUNT, multiedged => _MULTI, undirected => _UNORD, ); ($eflags, delete $opt->{hyperedged}); } sub new { my ($class, @args) = @_; my $gflags = 0; my %opt = _get_options( \@args ); %opt = (_opt_from_existing($class), %opt) # allow overrides if ref $class && $class->isa('Graph'); my $vflags = _opt_to_vflags(\%opt); my ($eflags, $is_hyper) = _opt_to_eflags(\%opt); _opt(\%opt, \$gflags, unionfind => _UNIONFIND, ); my @V; if ($opt{vertices}) { __carp_confess "Graph: vertices should be an array ref" if ref $opt{vertices} ne 'ARRAY'; @V = @{ delete $opt{vertices} }; } my @E; if ($opt{edges}) { __carp_confess "Graph: edges should be an array ref of array refs" if ref $opt{edges} ne 'ARRAY'; @E = @{ delete $opt{edges} }; __carp_confess "Graph: edges should be array refs" if grep ref $_ ne 'ARRAY', @E; } _opt_unknown(\%opt); __carp_confess "Graph: both countvertexed and multivertexed" if ($vflags & _COUNT) && ($vflags & _MULTI); __carp_confess "Graph: both countedged and multiedged" if ($eflags & _COUNT) && ($eflags & _MULTI); my $g = bless [ ], ref $class || $class; $g->[ _F ] = $gflags; $g->[ _G ] = 0; $g->[ _V ] = _make_v($vflags); $g->[ _E ] = _make_e($is_hyper, $eflags); $g->[ _U ] = do { require Graph::UnionFind; Graph::UnionFind->new } if $gflags & _UNIONFIND; $g->add_vertices(@V) if @V; $g->add_edges(@E) if @E; return $g; } sub _make_v { my ($vflags) = @_; $vflags ? _am_heavy($vflags, 1) : _am_light($vflags, 1); } sub _make_e { my ($is_hyper, $eflags) = @_; ($is_hyper or $eflags & ~_UNORD) ? _am_heavy($eflags, $is_hyper ? 0 : 2) : _am_light($eflags, 2); } sub _am_light { require Graph::AdjacencyMap::Light; Graph::AdjacencyMap::Light->_new(@_); } sub _am_heavy { Graph::AdjacencyMap->_new(@_); } sub countvertexed { $_[0]->[ _V ]->_is_COUNT } sub multivertexed { $_[0]->[ _V ]->_is_MULTI } sub refvertexed { $_[0]->[ _V ]->_is_REF } sub refvertexed_stringified { $_[0]->[ _V ]->_is_REFSTR } sub __stringified { $_[0]->[ _V ]->_is_STR } sub countedged { $_[0]->[ _E ]->_is_COUNT } sub multiedged { $_[0]->[ _E ]->_is_MULTI } sub hyperedged { !$_[0]->[ _E ]->[ _arity ] } sub undirected { $_[0]->[ _E ]->_is_UNORD } sub directed { ! $_[0]->[ _E ]->_is_UNORD } *is_directed = \&directed; *is_undirected = \&undirected; *is_countvertexed = \&countvertexed; *is_multivertexed = \&multivertexed; *is_refvertexed = \&refvertexed; *is_refvertexed_stringified = \&refvertexed_stringified; *is_countedged = \&countedged; *is_multiedged = \&multiedged; *is_hyperedged = \&hyperedged; sub has_union_find { $_[0]->[ _U ] } sub add_vertex { __carp_confess "Graph::add_vertex: use add_vertices for more than one vertex" if @_ != 2; __carp_confess "Graph::add_vertex: undef vertex" if grep !defined, @_; goto &add_vertices; } sub has_vertex { my $g = $_[0]; my $V = $g->[ _V ]; return defined $V->has_path($_[1]) if ($V->[ _f ] & _REF); exists $V->[ _pi ]->{ $_[1] }; } sub _vertices05 { my $g = $_[0]; $g->[ _V ]->paths; } sub vertices { my $g = $_[0]; my @v = &_vertices05; return @v if !(&is_multivertexed || &is_countvertexed); return map +(($_) x $g->get_vertex_count($_)), @v if wantarray; my $V = 0; $V += $g->get_vertex_count($_) for @v; return $V; } *unique_vertices = \&_vertices05; sub has_vertices { my $g = shift; scalar $g->[ _V ]->has_any_paths; } sub add_edge { &expect_hyperedged, &expect_undirected if @_ != 3; $_[0]->add_edges([ @_[1..$#_] ]); } sub _vertex_ids_ensure { push @_, 1; goto &_vertex_ids_maybe_ensure; } sub _vertex_ids_ensure_multi { my $id = pop; my @i = &_vertex_ids_ensure; push @_, $id; @i ? (@i, $id) : (); } sub _vertex_ids { push @_, 0; goto &_vertex_ids_maybe_ensure; } sub _vertex_ids_multi { my $id = pop; my @i = &_vertex_ids; push @_, $id; @i ? (@i, $id) : (); } sub _vertex_ids_maybe_ensure { my $ensure = pop; my ($g, @args) = @_; __carp_confess "Graph: given undefined vertex" if grep !defined, @args; my $V = $g->[ _V ]; my $deep = &is_hyperedged && &is_directed; return $V->get_ids_by_paths(\@args, $ensure, $deep) if ($V->[ _f ] & _REF) or $deep; my $pi = $V->[ _pi ]; my @non_exist = grep !exists $pi->{ $_ }, @args; return if !$ensure and @non_exist; $V->get_ids_by_paths(\@non_exist, 1) if @non_exist; @$pi{ @args }; } sub has_edge { my $g = $_[0]; my $E = $g->[ _E ]; my ($Ef, $Ea) = @$E[ _f, _arity ]; return 0 if $Ea and @_ != $Ea + 1; my $directed = &is_directed; my $deep = &is_hyperedged && $directed; return 0 if (my @i = &_vertex_ids) != @_ - 1; return defined $E->has_path($directed ? \@i : [ map [ sort @$_ ], @i ]) if $deep; @i = sort @i if !$directed; exists $E->[ _pi ]{ "@i" }; } sub any_edge { my ($g, @args) = @_; my $E = $g->[ _E ]; my $V = $g->[ _V ]; return 0 if (my @i = $V->get_ids_by_paths(\@args)) != @args; $E->has_successor(@i); } sub _edges05 { my $g = $_[0]; my @e = $g->[ _E ]->paths; return @e if !wantarray; $g->[ _V ]->get_paths_by_ids(\@e, &is_hyperedged && &is_directed); } *unique_edges = \&_edges05; sub edges { my $g = $_[0]; my @e = &_edges05; return @e if !(&is_multiedged || &is_countedged); return map +(($_) x $g->get_edge_count(@$_)), @e if wantarray; my $E = 0; $E += $g->get_edge_count(@$_) for @e; return $E; } sub has_edges { scalar $_[0]->[ _E ]->has_any_paths; } ### # by_id # sub add_vertex_by_id { &expect_multivertexed; my ($g, $v, $id) = @_; my $V = $g->[ _V ]; return $g if $V->has_path_by_multi_id( my @args = ($v, $id) ); my ($i) = $V->set_path_by_multi_id( @args ); $g->[ _U ]->add($i) if &has_union_find; $g->[ _G ]++; return $g; } sub add_vertex_get_id { &expect_multivertexed; my ($g, $v) = @_; my ($i, $multi_id) = $g->[ _V ]->set_path_by_multi_id( $v, _GEN_ID ); $g->[ _U ]->add($i) if &has_union_find; $g->[ _G ]++; return $multi_id; } sub has_vertex_by_id { &expect_multivertexed; my ($g, $v, $id) = @_; $g->[ _V ]->has_path_by_multi_id( $v, $id ); } sub delete_vertex_by_id { &expect_multivertexed; &expect_non_unionfind; my ($g, $v, $id) = @_; return $g unless &has_vertex_by_id; if ($g->[ _V ]->get_multi_ids( $v ) == 1) { # only incarnation, zap edges my @i = &_vertex_ids_multi; pop @i; # the id my $E = $g->[ _E ]; my @edges = $E->paths_from(@i); push @edges, $E->paths_to(@i) if !&is_undirected; $E->del_path( $_ ) for @edges; } $g->[ _V ]->del_path_by_multi_id( $v, $id ); $g->[ _G ]++; return $g; } sub get_multivertex_ids { &expect_multivertexed; my $g = shift; $g->[ _V ]->get_multi_ids( @_ ); } sub add_edge_by_id { &expect_multiedged; my $g = $_[0]; my @i = &_vertex_ids_ensure_multi; my $id = pop @i; @i = sort @i if &is_undirected; $g->[ _E ]->set_path_by_multi_id( \@i, $id ); $g->[ _G ]++; $g->[ _U ]->union(\@i) if &has_union_find; return $g; } sub add_edge_get_id { &expect_multiedged; my $g = $_[0]; my @i = &_vertex_ids_ensure; @i = sort @i if &is_undirected; my (undef, $id) = $g->[ _E ]->set_path_by_multi_id( \@i, _GEN_ID ); $g->[ _G ]++; $g->[ _U ]->union(\@i) if &has_union_find; return $id; } sub has_edge_by_id { &expect_multiedged; my $g = $_[0]; my @i = &_vertex_ids_multi; return 0 if @i < @_ - 2; my $id = pop @i; @i = sort @i if &is_undirected; $g->[ _E ]->has_path_by_multi_id( \@i, $id ); } sub delete_edge_by_id { &expect_multiedged; &expect_non_unionfind; my $g = $_[0]; my $E = $g->[ _E ]; my @i = &_vertex_ids_multi; return if @i < @_ - 2; my $id = pop @i; @i = sort @i if &is_undirected; return unless $E->has_path_by_multi_id( my @args = (\@i, $id) ); $E->del_path_by_multi_id( @args ); $g->[ _G ]++; return $g; } sub get_multiedge_ids { &expect_multiedged; return unless @_-1 == (my @i = &_vertex_ids); $_[0]->[ _E ]->get_multi_ids( \@i ); } ### # Neighbourhood. # sub _edges_at { goto &_edges_from if &is_undirected; require Set::Object; Set::Object->new(&_edges_from, &_edges_to)->${ wantarray ? \'members' : \'size' }; } sub _edges_from { my ($g, @args) = @_; my ($V, $E) = @$g[ _V, _E ]; return if (my @i = $V->get_ids_by_paths(\@args, &is_hyperedged && &is_directed)) != @args; $E->paths_from(@i); } sub _edges_to { goto &_edges_from if &is_undirected; my ($g, @args) = @_; my ($V, $E) = @$g[ _V, _E ]; return if (my @i = $V->get_ids_by_paths(\@args, &is_hyperedged && &is_directed)) != @args; $E->paths_to(@i); } sub edges_at { goto &_edges_at if !wantarray; $_[0]->[ _V ]->get_paths_by_ids([ &_edges_at ], &is_hyperedged && &is_directed); } sub edges_from { goto &_edges_from if !wantarray; $_[0]->[ _V ]->get_paths_by_ids([ &_edges_from ], &is_hyperedged && &is_directed); } sub edges_to { goto &edges_from if &is_undirected; goto &_edges_to if !wantarray; $_[0]->[ _V ]->get_paths_by_ids([ &_edges_to ], &is_hyperedged && &is_directed); } sub successors { my ($g, @args) = @_; my ($V, $E) = @$g[ _V, _E ]; return if (my @i = $V->get_ids_by_paths(\@args)) != @args; my @v = $E->successors(@i); return @v if !wantarray; map @$_, $V->get_paths_by_ids([ \@v ]); } sub predecessors { goto &successors if &is_undirected; my ($g, @args) = @_; my ($V, $E) = @$g[ _V, _E ]; return if (my @i = $V->get_ids_by_paths(\@args)) != @args; my @v = $E->predecessors(@i); return @v if !wantarray; map @$_, $V->get_paths_by_ids([ \@v ]); } sub _cessors_by_radius { my ($radius, $method, $self_only_if_loop) = splice @_, -3, 3; my ($g, @v) = @_; require Set::Object; my ($init, $next) = map Set::Object->new(@v), 1..2; my $self = $self_only_if_loop ? Set::Object->new(grep $g->has_edge($_, $_), @v) : undef; my ($got, $found) = map Set::Object->new, 1..2; while (!defined $radius or $radius-- > 0) { $found->insert($g->$method($next->members)); $next = $found->difference($got); last if $next->is_null; # Leave if no new found. $got->insert($next->members); $found->clear; } $got->remove($init->difference($self)->members) if $self_only_if_loop; $got->${ wantarray ? \'members' : \'size' }; } sub all_successors { &expect_directed; push @_, undef, 'successors', 0; goto &_cessors_by_radius; } sub successors_by_radius { &expect_directed; push @_, 'successors', 0; goto &_cessors_by_radius; } sub all_predecessors { &expect_directed; push @_, undef, 'predecessors', 0; goto &_cessors_by_radius; } sub predecessors_by_radius { &expect_directed; push @_, 'predecessors', 0; goto &_cessors_by_radius; } sub neighbours_by_radius { push @_, 'neighbours', 1; goto &_cessors_by_radius; } *neighbors_by_radius = \&neighbours_by_radius; sub neighbours { require Set::Object; my $s = Set::Object->new(&successors); $s->insert(&predecessors) if &is_directed; $s->${ wantarray ? \'members' : \'size' }; } *neighbors = \&neighbours; sub all_neighbours { push @_, undef, 'neighbours', 1; goto &_cessors_by_radius; } *all_neighbors = \&all_neighbours; sub all_reachable { &directed ? goto &all_successors : goto &all_neighbors; } sub reachable_by_radius { &directed ? goto &successors_by_radius : goto &neighbors_by_radius; } sub delete_edge { &expect_non_unionfind; my $g = $_[0]; return $g if (my @i = &_vertex_ids) != @_ - 1; @i = sort @i if &is_undirected; return $g unless @i and $g->[ _E ]->del_path( \@i ); $g->[ _G ]++; return $g; } sub delete_vertex { &expect_non_unionfind; my $g = $_[0]; return $g if @_ != 2; my $V = $g->[ _V ]; return $g unless defined $V->has_path($_[1]); # TODO: _edges_at is excruciatingly slow (rt.cpan.org 92427) my $E = $g->[ _E ]; $E->del_path( $_ ) for &_edges_at; $V->del_path($_[1]); $g->[ _G ]++; return $g; } sub get_vertex_count { my $g = shift; $g->[ _V ]->_get_path_count( @_ ); } sub get_edge_count { my $g = $_[0]; return 0 if (my @i = &_vertex_ids) != @_ - 1; @i = sort @i if &is_undirected; $g->[ _E ]->_get_path_count( \@i ); } sub delete_vertices { &expect_non_unionfind; my $g = shift; while (@_) { my $v = shift @_; $g->delete_vertex($v); } return $g; } sub delete_edges { &expect_non_unionfind; my $g = shift; while (@_) { my ($u, $v) = splice @_, 0, 2; $g->delete_edge($u, $v); } return $g; } ### # Degrees. # sub in_degree { my $g = $_[0]; return undef unless @_ > 1 && &has_vertex; my $in = 0; $in += $g->get_edge_count( @$_ ) for &edges_to; $in++ if &is_undirected and &is_self_loop_vertex; return $in; } sub out_degree { my $g = $_[0]; return undef unless @_ > 1 && &has_vertex; my $out = 0; $out += $g->get_edge_count( @$_ ) for &edges_from; $out++ if &is_undirected and &is_self_loop_vertex; return $out; } sub _total_degree { return undef unless @_ > 1 && &has_vertex; &is_undirected ? &in_degree : &in_degree - &out_degree; } sub degree { goto &_total_degree if @_ > 1; return 0 if &is_directed; my $g = $_[0]; my $total = 0; $total += $g->_total_degree( $_ ) for &_vertices05; return $total; } *vertex_degree = \°ree; sub is_sink_vertex { return 0 unless @_ > 1; &successors == 0 && &predecessors > 0; } sub is_source_vertex { return 0 unless @_ > 1; &predecessors == 0 && &successors > 0; } sub is_successorless_vertex { return 0 unless @_ > 1; &successors == 0; } sub is_predecessorless_vertex { return 0 unless @_ > 1; &predecessors == 0; } sub is_successorful_vertex { return 0 unless @_ > 1; &successors > 0; } sub is_predecessorful_vertex { return 0 unless @_ > 1; &predecessors > 0; } sub is_isolated_vertex { return 0 unless @_ > 1; &predecessors == 0 && &successors == 0; } sub is_interior_vertex { return 0 unless @_ > 1; my $s = &successors; $s-- if my $isl = &is_self_loop_vertex; return 0 if $s == 0; return $s > 0 if &is_undirected; my $p = &predecessors; $p-- if $isl; $p > 0; } sub is_exterior_vertex { return 0 unless @_ > 1; &predecessors == 0 || &successors == 0; } sub is_self_loop_vertex { return 0 unless @_ > 1; return 1 if grep $_ eq $_[1], &successors; # @todo: multiedges return 0; } for my $p (qw( is_sink_vertex is_source_vertex is_successorless_vertex is_predecessorless_vertex is_successorful_vertex is_predecessorful_vertex is_isolated_vertex is_interior_vertex is_exterior_vertex is_self_loop_vertex )) { no strict 'refs'; (my $m = $p) =~ s/^is_(.*)ex$/${1}ices/; *$m = sub { my $g = $_[0]; grep $g->$p($_), &_vertices05 }; } ### # Paths and cycles. # sub add_path { my $g = shift; my $u = shift; my @edges; while (@_) { my $v = shift; push @edges, [ $u, $v ]; $u = $v; } $g->add_edges(@edges); return $g; } sub delete_path { &expect_non_unionfind; my $g = shift; my $u = shift; while (@_) { my $v = shift; $g->delete_edge($u, $v); $u = $v; } return $g; } sub has_path { my $g = shift; my $u = shift; while (@_) { my $v = shift; return 0 unless $g->has_edge($u, $v); $u = $v; } return $g; } sub add_cycle { push @_, $_[1]; goto &add_path; } sub delete_cycle { &expect_non_unionfind; push @_, $_[1]; goto &delete_path; } sub has_cycle { return 0 if @_ == 1; push @_, $_[1]; goto &has_path; } *has_this_cycle = \&has_cycle; sub has_a_cycle { my $g = shift; require Graph::Traversal::DFS; my $t = Graph::Traversal::DFS->new($g, has_a_cycle => 1, @_); $t->dfs; return $t->get_state('has_a_cycle'); } sub find_a_cycle { require Graph::Traversal::DFS; my @r = ( back_edge => \&Graph::Traversal::find_a_cycle); push @r, down_edge => \&Graph::Traversal::find_a_cycle if &is_undirected; my $g = shift; my $t = Graph::Traversal::DFS->new($g, @r, @_); $t->dfs; $t->has_state('a_cycle') ? @{ $t->get_state('a_cycle') } : (); } ### # Attributes. my @generic_methods = ( [ 'set_attribute', 'my (\$attr, \$value) = splice \@_, -2; &$add unless &$has;', '\$_[0]->[ $offset ]->_set_path_attr( \@args, \$attr, \$value );' ], [ 'set_attributes', 'my \$attr = pop; &$add unless &$has;', '\$_[0]->[ $offset ]->_set_path_attrs( \@args, \$attr );', ], [ 'has_attributes', 'return 0 unless &$has;', '\$_[0]->[ $offset ]->_has_path_attrs( \@args );', ], [ 'has_attribute', 'my \$attr = pop; return 0 unless &$has;', '\$_[0]->[ $offset ]->_has_path_attr( \@args, \$attr );', ], [ 'get_attributes', 'return undef unless &$has;', 'scalar \$_[0]->[ $offset ]->_get_path_attrs( \@args );', ], [ 'get_attribute', 'my \$attr = pop; return undef unless &$has;', 'scalar \$_[0]->[ $offset ]->_get_path_attr( \@args, \$attr );', ], [ 'get_attribute_names', 'return unless &$has;', '\$_[0]->[ $offset ]->_get_path_attr_names( \@args );', ], [ 'get_attribute_values', 'return unless &$has;', '\$_[0]->[ $offset ]->_get_path_attr_values( \@args );', ], [ 'delete_attributes', 'return undef unless &$has;', '\$_[0]->[ $offset ]->_del_path_attrs( \@args );', ], [ 'delete_attribute', 'my \$attr = pop; return undef unless &$has;', '\$_[0]->[ $offset ]->_del_path_attr( \@args, \$attr );', ], ); my %entity2offset = (vertex => '_V', edge => '_E'); my %entity2args = (edge => '&_vertex_ids'); my $template_mid = 'my \@args = @{[ $args || "\@_[1..\$#_]" ]};$munge'; for my $entity (qw(vertex edge)) { no strict 'refs'; my $has_base = 'has_' . $entity; my $add_base = 'add_' . $entity; my $offset = $entity2offset{$entity}; for my $t (@generic_methods) { my ($raw, $t1, $t2) = @$t; my ($first, $rest) = ($raw =~ /^(\w+?)_(.+)/); my $is_vertex = $entity eq 'vertex'; my $m = join '_', $first, $entity, $rest; my ($args, $munge, $has, $add) = ($entity2args{$entity}, $is_vertex ? '' : "\n\@args = &is_undirected ? [sort \@args] : [\@args];", $has_base, $add_base); my $func_text = "qq{sub $m {\n&expect_non_multi$entity;\n$t1\n$template_mid\n$t2\n}}\n"; #warn "$m:\n$func_text\n"; my $tv2 = eval $func_text; #warn "$m v2:\n$tv2\n"; eval $tv2; die if $@; $m .= '_by_id'; ($args, $munge, $has, $add) = ($entity2args{$entity} && "$entity2args{$entity}_multi", $is_vertex ? '' : "\n\@args = (&is_undirected ? [sort \@args[0..\$#args-1]] : [\@args[0..\$#args-1]], \$args[-1]);", $has_base.'_by_id', $add_base.'_by_id'); $func_text = "qq{sub $m {\n&expect_multi$entity;\n$t1\n$template_mid\n$t2\n}}\n"; #warn "$m:\n$func_text\n"; $tv2 = eval $func_text; #warn "$m v2:\n$tv2\n"; eval $tv2; die if $@; } } sub get_edge_attribute_all { my ($g, $u, $v, $name) = @_; die "no attribute name given" if !defined $name; grep defined(), &is_multiedged ? (map $g->get_edge_attribute_by_id($u, $v, $_, $name), $g->get_multiedge_ids($u, $v)) : $g->get_edge_attribute($u, $v, $name); } sub add_vertices { my ($g, @v) = @_; if (&is_multivertexed) { $g->add_vertex_by_id($_, _GEN_ID) for @v; return $g; } my @i = $g->[ _V ]->set_paths(@v); $g->[ _G ]++; return $g if !&has_union_find; $g->[ _U ]->add(@i); $g; } sub add_edges { my ($g, @args) = @_; my @edges; while (defined(my $u = shift @args)) { push @edges, ref $u eq 'ARRAY' ? $u : @args ? [ $u, shift @args ] : __carp_confess "Graph::add_edges: missing end vertex"; } if (&is_multiedged) { $g->add_edge_by_id(@$_, _GEN_ID) for @edges; return $g; } my $uf = &has_union_find; my $deep = &is_hyperedged && &is_directed; my @paths = $g->[ _V ]->get_ids_by_paths(\@edges, 1, 1 + ($deep ? 1 : 0)); @paths = map [ sort @$_ ], @paths if &is_undirected; $g->[ _E ]->set_paths( @paths ); $uf->union(@paths) if $uf; $g->[ _G ]++; return $g; } sub add_edges_by_id { &expect_multiedged; my ($g, $id) = (shift, pop); my @edges; while (defined(my $u = shift @_)) { push @edges, ref $u eq 'ARRAY' ? $u : @_ ? [ $u, shift @_ ] : __carp_confess "Graph::add_edges: missing end vertex"; } $g->add_edge_by_id(@$_, $id) for @edges; return $g; } sub rename_vertex { my $g = shift; $g->[ _V ]->rename_path(@_); return $g; } sub rename_vertices { my ($g, $code) = @_; my %seen; $g->rename_vertex($_, $code->($_)) for grep !$seen{$_}++, $g->[ _V ]->paths; return $g; } sub filter_vertices { my ($g, $code) = @_; my @v = &_vertices05; if (&is_multivertexed) { for my $v (@v) { $g->delete_vertex_by_id($v, $_) for grep !$code->($g, $v, $_), $g->get_multivertex_ids($v); } } else { $g->delete_vertices(grep !$code->($g, $_), @v); } $g; } sub filter_edges { my ($g, $code) = @_; my @e = &_edges05; if (&is_multiedged) { for my $e (@e) { $g->delete_edge_by_id(@$e, $_) for grep !$code->($g, @$e, $_), $g->get_multiedge_ids(@$e); } } else { $g->delete_edges(map @$_, grep !$code->($g, @$_), @e); } $g; } sub as_hashes { my ($g) = @_; my (%v, %e, @e); my ($is_hyper, $is_directed) = (&is_hyperedged, &is_directed); if (&is_multivertexed) { for my $v ($g->unique_vertices) { $v{$v} = { map +($_ => $g->get_vertex_attributes_by_id($v, $_) || {}), $g->get_multivertex_ids($v) }; } } else { %v = map +($_ => $g->get_vertex_attributes($_) || {}), $g->unique_vertices; } my $multi_e = &is_multiedged; for my $e ($g->edges) { my $edge_attr = { $multi_e ? map +($_ => $g->get_edge_attributes_by_id(@$e, $_) || {}), $g->get_multiedge_ids(@$e) : %{ $g->get_edge_attributes(@$e)||{} } }; if ($is_hyper) { my %h = (attributes => $edge_attr); if ($is_directed) { @h{qw(predecessors successors)} = @$e; } else { $h{vertices} = $e; } push @e, \%h; } else { $e{ $e->[0] }{ $e->[1] } = $edge_attr; $e{ $e->[1] }{ $e->[0] } = $edge_attr if !$is_directed; } } ( \%v, $is_hyper ? \@e : \%e ); } sub ingest { my ($g, $g2) = @_; _copy_vertices($g2, $g, 1); _copy_edges($g2, $g, 1); $g; } ### # More constructors. # sub copy { my ($g, @args) = @_; my $c = $g->new(@args); _copy_vertices($g, $c); _copy_edges($g, $c); return $c; } *copy_graph = \© sub _deep_copy_best { _can_deep_copy_Storable() ? _deep_copy_Storable(@_) : _deep_copy_DataDumper(@_); } sub _deep_copy_Storable { my $g = shift; require Safe; # For deep_copy(). my $safe = Safe->new; $safe->permit(qw/:load/); local $Storable::Deparse = 1; local $Storable::Eval = sub { $safe->reval($_[0]) }; return Storable::thaw(Storable::freeze($g)); } sub _deep_copy_DataDumper { my $g = shift; require Data::Dumper; my $d = Data::Dumper->new([$g]); use vars qw($VAR1); $d->Purity(1)->Terse(1)->Deepcopy(1); $d->Deparse(1) if $] >= 5.008; eval $d->Dump; } sub deep_copy { local $. = $.; my $g2 = _deep_copy_best(@_); $g2->[ _V ]->reindex if grep ref, &_vertices05; $g2; } *deep_copy_graph = \&deep_copy; sub transpose_edge { my $g = $_[0]; return $g if !&is_directed; return undef unless &has_edge; my $c = &get_edge_count; my $a = &get_edge_attributes; my @e = reverse @_[1..$#_]; &delete_edge unless $g->has_edge( @e ); $g->add_edges(map \@e, 1..$c); $g->set_edge_attributes(@e, $a) if $a; return $g; } sub transpose_graph { my $t = © return $t if !&directed; $t->transpose_edge(@$_) for &_edges05; return $t; } *transpose = \&transpose_graph; sub complete_graph { my $directed = &is_directed; my $c = &new; my @v = &_vertices05; my @edges; for (my $i = $#v; $i >= 0; $i-- ) { push @edges, map +([$v[$i], $v[$_]], $directed ? [$v[$_], $v[$i]] : ()), 0..$i - 1; } $c->add_edges(@edges); return $c; } sub max_cliques { my ($g) = @_; &expect_undirected; $g->bron_kerbosch_pivot([], [$g->vertices], [], \ my @cliques); return wantarray ? @cliques : \@cliques } sub bron_kerbosch_pivot { my ($g, $r, $p, $x, $max_cliques) = @_; if (! @$p && ! @$x && @$r) { push @$max_cliques, [@$r]; return; } my $pivot = (@$p, @$x)[0]; for my $v (my @p = @$p) { next if $g->has_edge($pivot, $v); $g->bron_kerbosch_pivot( [@$r, $v], [grep { my $w = $_; grep $_ eq $w, @$p } $g->neighbours($v)], [grep { my $w = $_; grep $_ eq $w, @$x } $g->neighbours($v)], $max_cliques); @$p = grep $_ ne $v, @$p; push @$x, $v; } } *complement = \&complement_graph; sub complement_graph { my $c = &complete_graph; $c->delete_edge(@$_) for &edges; return $c; } *complete = \&complete_graph; sub subgraph { my ($g, $src, $dst) = @_; __carp_confess "Graph::subgraph: need src and dst array references" unless ref $src eq 'ARRAY' && (!defined($dst) or ref $dst eq 'ARRAY'); require Set::Object; my $s = $g->new; my @u = grep $g->has_vertex($_), @$src; my $v = Set::Object->new($dst ? grep $g->has_vertex($_), @$dst : @u); $s->add_vertices(@u, $dst ? $v->members : ()); my $directed = &is_directed; if ($directed) { $s->add_edges(grep $v->contains($_->[1]), $g->edges_from(@u)); } else { my $valid = $dst ? $v + Set::Object->new(@u) : $v; $s->add_edges( grep +($v->contains($_->[0]) || $v->contains($_->[1])) && ($valid->contains($_->[0]) && $valid->contains($_->[1])), $g->edges_from(@u) ); } return $s; } ### # Transitivity. # sub is_transitive { my $g = shift; require Graph::TransitiveClosure; Graph::TransitiveClosure::is_transitive($g); } ### # Weighted vertices. # my $defattr = 'weight'; sub _defattr { return $defattr; } sub add_weighted_vertex { &expect_non_multivertexed; push @_, $defattr, pop; goto &set_vertex_attribute; } sub add_weighted_vertices { &expect_non_multivertexed; my $g = shift; while (@_) { my ($v, $w) = splice @_, 0, 2; $g->set_vertex_attribute($v, $defattr, $w); } } sub get_vertex_weight { &expect_non_multivertexed; push @_, $defattr; goto &get_vertex_attribute; } sub has_vertex_weight { &expect_non_multivertexed; push @_, $defattr; goto &has_vertex_attribute; } sub set_vertex_weight { &expect_non_multivertexed; push @_, $defattr, pop; goto &set_vertex_attribute; } sub delete_vertex_weight { &expect_non_multivertexed; push @_, $defattr; goto &delete_vertex_attribute; } sub add_weighted_vertex_by_id { &expect_multivertexed; push @_, $defattr, pop; goto &set_vertex_attribute_by_id; } sub add_weighted_vertices_by_id { &expect_multivertexed; my $g = shift; my $id = pop; while (@_) { my ($v, $w) = splice @_, 0, 2; $g->add_vertex_by_id($v, $id); $g->set_vertex_attribute_by_id($v, $id, $defattr, $w); } } sub get_vertex_weight_by_id { &expect_multivertexed; push @_, $defattr; goto &get_vertex_attribute_by_id; } sub has_vertex_weight_by_id { &expect_multivertexed; push @_, $defattr; goto &has_vertex_attribute_by_id; } sub set_vertex_weight_by_id { &expect_multivertexed; push @_, $defattr, pop; goto &set_vertex_attribute_by_id; } sub delete_vertex_weight_by_id { &expect_multivertexed; push @_, $defattr; goto &delete_vertex_attribute_by_id; } ### # Weighted edges. # sub add_weighted_edge { &expect_non_multiedged; push @_, $defattr, pop; goto &set_edge_attribute; } sub add_weighted_edges { &expect_non_multiedged; my $g = shift; while (@_) { my ($u, $v, $w) = splice @_, 0, 3; $g->set_edge_attribute($u, $v, $defattr, $w); } } sub add_weighted_edges_by_id { &expect_multiedged; my $g = shift; my $id = pop; while (@_) { my ($u, $v, $w) = splice @_, 0, 3; $g->set_edge_attribute_by_id($u, $v, $id, $defattr, $w); } } sub add_weighted_path { &expect_non_multiedged; my $g = shift; my $u = shift; while (@_) { my ($w, $v) = splice @_, 0, 2; $g->set_edge_attribute($u, $v, $defattr, $w); $u = $v; } } sub get_edge_weight { &expect_non_multiedged; push @_, $defattr; goto &get_edge_attribute; } sub has_edge_weight { &expect_non_multiedged; push @_, $defattr; goto &has_edge_attribute; } sub set_edge_weight { &expect_non_multiedged; push @_, $defattr, pop; goto &set_edge_attribute; } sub delete_edge_weight { &expect_non_multiedged; push @_, $defattr; goto &delete_edge_attribute; } sub add_weighted_edge_by_id { &expect_multiedged; push @_, $defattr, pop; goto &set_edge_attribute_by_id; } sub add_path_by_id { &expect_multiedged; my ($g, $u, $id) = (shift, shift, pop); my @edges; while (@_) { my $v = shift; push @edges, [ $u, $v ]; $u = $v; } $g->add_edges_by_id(@edges, $id); return $g; } sub add_weighted_path_by_id { &expect_multiedged; my ($g, $u, $id) = (shift, shift, pop); while (@_) { my ($w, $v) = splice @_, 0, 2; $g->set_edge_attribute_by_id($u, $v, $id, $defattr, $w); $u = $v; } } sub get_edge_weight_by_id { &expect_multiedged; push @_, $defattr; goto &get_edge_attribute_by_id; } sub has_edge_weight_by_id { &expect_multiedged; push @_, $defattr; goto &has_edge_attribute_by_id; } sub set_edge_weight_by_id { &expect_multiedged; push @_, $defattr, pop; goto &set_edge_attribute_by_id; } sub delete_edge_weight_by_id { &expect_multiedged; push @_, $defattr; goto &delete_edge_attribute_by_id; } ### # Error helpers. # my %expected; @expected{qw(directed undirected acyclic)} = qw(undirected directed cyclic); sub _expected { my $exp = shift; my $got = @_ ? shift : $expected{$exp}; $got = defined $got ? ", got $got" : ""; if (my @caller2 = caller(2)) { die "$caller2[3]: expected $exp graph$got, at $caller2[1] line $caller2[2].\n"; } else { my @caller1 = caller(1); # uncoverable statement die "$caller1[3]: expected $exp graph$got, at $caller1[1] line $caller1[2].\n"; # uncoverable statement } } sub expect_no_args { my $g = shift; return unless @_; my @caller1 = caller(1); # uncoverable statement die "$caller1[3]: expected no arguments, got " . scalar @_ . ", at $caller1[1] line $caller1[2]\n"; # uncoverable statement } sub expect_undirected { _expected('undirected') unless &is_undirected; } sub expect_directed { _expected('directed') unless &is_directed; } sub expect_acyclic { _expected('acyclic') unless &is_acyclic; } sub expect_dag { my @got; push @got, 'undirected' unless &is_directed; push @got, 'cyclic' unless &is_acyclic; _expected('directed acyclic', "@got") if @got; } sub expect_hyperedged { _expected('hyperedged') unless &is_hyperedged; } sub expect_multivertexed { _expected('multivertexed') unless &is_multivertexed; } *expect_multivertex = \&expect_multivertexed; sub expect_non_multivertexed { _expected('non-multivertexed') if &is_multivertexed; } *expect_non_multivertex = \&expect_non_multivertexed; sub expect_non_multiedged { _expected('non-multiedged') if &is_multiedged; } *expect_non_multiedge = \&expect_non_multiedged; sub expect_multiedged { _expected('multiedged') unless &is_multiedged; } *expect_multiedge = \&expect_multiedged; sub expect_non_unionfind { _expected('non-unionfind') if &has_union_find; } sub _get_options { my @caller = caller(1); unless (@_ == 1 && ref $_[0] eq 'ARRAY') { die "$caller[3]: internal error: should be called with only one array ref argument, at $caller[1] line $caller[2].\n"; } my @opt = @{ $_[0] }; unless (@opt % 2 == 0) { die "$caller[3]: expected an options hash, got a non-even number of arguments, at $caller[1] line $caller[2].\n"; # uncoverable statement } return @opt; } ### # Random constructors and accessors. # sub __fisher_yates_shuffle (@) { # From perlfaq4, but modified to be non-modifying. my @a = @_; my $i = @a; while ($i--) { my $j = int rand ($i+1); @a[$i,$j] = @a[$j,$i]; } return @a; } BEGIN { sub _shuffle(@); # Workaround for the Perl bug [perl #32383] where -d:Dprof and # List::Util::shuffle do not like each other: if any debugging # (-d) flags are on, fall back to our own Fisher-Yates shuffle. # The bug was fixed by perl changes #26054 and #26062, which # went to Perl 5.9.3. If someone tests this with a pre-5.9.3 # bleadperl that calls itself 5.9.3 but doesn't yet have the # patches, oh, well. *_shuffle = $^P && $] < 5.009003 ? \&__fisher_yates_shuffle : do { require List::Util; \&List::Util::shuffle }; } sub random_graph { my $class = (@_ % 2) == 0 ? 'Graph' : shift; my %opt = _get_options( \@_ ); __carp_confess "Graph::random_graph: argument 'vertices' missing or undef" unless defined $opt{vertices}; __carp_confess "Graph::random_graph: both arguments 'edges' and 'edges_fill' specified" if exists $opt{edges} && exists $opt{edges_fill}; srand delete $opt{random_seed} if exists $opt{random_seed}; my $random_edge = delete $opt{random_edge}; my @V; if (my $ref = ref $opt{vertices}) { __carp_confess "Graph::random_graph: argument 'vertices' illegal" if $ref ne 'ARRAY'; @V = @{ $opt{vertices} }; } else { @V = 0..($opt{vertices} - 1); } delete $opt{vertices}; my $V = @V; my $C = $V * ($V - 1) / 2; my $E = exists $opt{edges_fill} ? $opt{edges_fill} * $C : $opt{edges}; delete @opt{qw(edges edges_fill)}; my $g = $class->new(%opt); $g->add_vertices(@V); return $g if $V < 2; $C *= 2 if $g->directed; $E = $C / 2 unless defined $E; $E = int($E + 0.5); my $p = $E / $C; $random_edge = sub { $p } unless defined $random_edge; __carp_confess "Graph::random_graph: needs to be countedged or multiedged ($E > $C)" if $p > 1.0 && !($g->countedged || $g->multiedged); # Shuffle the vertex lists so that the pairs at # the beginning of the lists are not more likely. my @V1 = _shuffle @V; my @V2 = _shuffle @V; LOOP: while ($E) { for my $v1 (@V1) { for my $v2 (@V2) { next if $v1 eq $v2; # TODO: allow self-loops? my $q = $random_edge->($g, $v1, $v2, $p); if ($q && ($q == 1 || rand() <= $q) && !$g->has_edge($v1, $v2)) { $g->add_edge($v1, $v2); $E--; last LOOP unless $E; } } } } $g; } sub random_vertex { my @V = &_vertices05; @V[rand @V]; } sub random_edge { my @E = &_edges05; @E[rand @E]; } sub random_successor { my @S = &successors; @S[rand @S]; } sub random_predecessor { my @P = &predecessors; @P[rand @P]; } ### # Algorithms. # my $MST_comparator = sub { ($_[0] || 0) <=> ($_[1] || 0) }; sub _MST_attr { my $attr = shift; my $attribute = exists $attr->{attribute} ? $attr->{attribute} : $defattr; my $comparator = exists $attr->{comparator} ? $attr->{comparator} : $MST_comparator; return ($attribute, $comparator); } sub _MST_edges { my ($g, $attr) = @_; my ($attribute, $comparator) = _MST_attr($attr); map $_->[1], sort { $comparator->($a->[0], $b->[0], $a->[1], $b->[1]) } map [ $g->get_edge_attribute(@$_, $attribute), $_ ], &_edges05; } sub MST_Kruskal { &expect_undirected; my ($g, %attr) = @_; require Graph::UnionFind; my $MST = Graph->new(directed => 0); my $UF = Graph::UnionFind->new; $UF->add(&_vertices05); my @edges; for my $e ($g->_MST_edges(\%attr)) { my ($u, $v) = @$e; # TODO: hyperedges next if $UF->same( @$e ); $UF->union([$u, $v]); push @edges, [ $u, $v ]; } $MST->add_edges(@edges); return $MST; } sub _MST_add { my ($g, $h, $HF, $r, $attr, $unseen) = @_; $HF->add( Graph::MSTHeapElem->new( $r, $_, $g->get_edge_attribute( $r, $_, $attr ) ) ) for grep exists $unseen->{ $_ }, $g->successors( $r ); } sub _next_alphabetic { shift; (sort keys %{ $_[0] })[0] } sub _next_numeric { shift; (sort { $a <=> $b } keys %{ $_[0] })[0] } sub _next_random { shift; (values %{ $_[0] })[ rand keys %{ $_[0] } ] } sub _root_opt { my ($g, @args) = @_; my %opt = @args == 1 ? ( first_root => $args[0] ) : _get_options( \@args ); my %unseen; my @unseen = $g->_vertices05; @unseen{ @unseen } = @unseen; @unseen = _shuffle @unseen; my $r; if (exists $opt{ start }) { $opt{ first_root } = delete $opt{ start }; $opt{ next_root } = undef; } if (exists $opt{ first_root }) { if (ref $opt{ first_root } eq 'CODE') { $r = $opt{ first_root }->( $g, \%unseen ); } else { $r = $opt{ first_root }; } } else { $r = shift @unseen; } my $next = exists $opt{ next_root } ? $opt{ next_root } : $opt{ next_alphabetic } ? \&_next_alphabetic : $opt{ next_numeric } ? \&_next_numeric : \&_next_random; my $code = ref $next eq 'CODE'; my $attr = exists $opt{ attribute } ? $opt{ attribute } : $defattr; return ( \%opt, \%unseen, \@unseen, $r, $next, $code, $attr ); } sub _heap_walk { my ($g, $h, $add, $etc, $opt, $unseenh, $unseena, $r, $next, $code, $attr) = @_; require Heap::Fibonacci; my $HF = Heap::Fibonacci->new; while (defined $r) { # print "r = $r\n"; $add->($g, $h, $HF, $r, $attr, $unseenh, $etc); delete $unseenh->{ $r }; while (defined $HF->top) { my $t = $HF->extract_top; # use Data::Dumper; print "t = ", Dumper($t); if (defined $t) { my ($u, $v, $w) = $t->val; # print "extracted top: $u $v $w\n"; if (exists $unseenh->{ $v }) { $h->set_edge_attribute($u, $v, $attr, $w); delete $unseenh->{ $v }; $add->($g, $h, $HF, $v, $attr, $unseenh, $etc); } } } return $h unless defined $next; $r = $code ? $next->( $g, $unseenh ) : shift @$unseena; last unless defined $r; } return $h; } sub MST_Prim { &expect_undirected; require Graph::MSTHeapElem; $_[0]->_heap_walk(Graph->new(directed => 0), \&_MST_add, undef, &_root_opt); } *MST_Dijkstra = \&MST_Prim; *minimum_spanning_tree = \&MST_Prim; ### # Cycle detection. # *is_cyclic = \&has_a_cycle; sub is_acyclic { !&is_cyclic; } sub is_dag { &is_directed && &is_acyclic ? 1 : 0; } *is_directed_acyclic_graph = \&is_dag; ### # Simple DFS uses. # sub topological_sort { my $g = shift; my %opt = _get_options( \@_ ); my $eic = delete $opt{ empty_if_cyclic }; my $hac; if ($eic) { $hac = $g->has_a_cycle; } else { $g->expect_dag; } require Graph::Traversal::DFS; my $t = Graph::Traversal::DFS->new($g, %opt); my @s = $t->dfs; $hac ? () : reverse @s; } *toposort = \&topological_sort; sub _copy_vertices { my ($g, $gc, $attr_too) = @_; if (&is_multivertexed) { for my $v (&_vertices05) { if ($attr_too) { $gc->set_vertex_attributes_by_id($v, $_, $g->get_vertex_attributes_by_id($v, $_)) for $g->get_multivertex_ids($v); } else { $gc->add_vertex_by_id($v, $_) for $g->get_multivertex_ids($v); } } } else { if ($attr_too) { $gc->set_vertex_attributes($_, $g->get_vertex_attributes($_)) for &_vertices05; } else { $gc->add_vertices(&_vertices05); } } } sub _copy_edges { my ($g, $gc, $attr_too, $mirror) = @_; my @edges = &_edges05; if (&is_multiedged) { for my $e (@edges) { for my $id ($g->get_multiedge_ids(@$e)) { if ($attr_too) { $gc->set_edge_attributes_by_id(@$e, $id, $g->get_edge_attributes_by_id(@$e, $id)); $gc->set_edge_attributes_by_id(reverse(@$e), $id, $g->get_edge_attributes_by_id(@$e, $id)) if $mirror; } else { $gc->add_edge_by_id(@$e, $id); $gc->add_edge_by_id(reverse(@$e), $id) if $mirror; } } } } else { if ($attr_too) { $gc->set_edge_attributes(@$_, $g->get_edge_attributes(@$_)) for @edges; if ($mirror) { $gc->set_edge_attributes(reverse(@$_), $g->get_edge_attributes(@$_)) for @edges; } } else { $gc->add_edges(@edges, !$mirror ? () : map [reverse @$_], @edges); } } } sub undirected_copy { &expect_directed; my $gc = $_[0]->new(undirected=>1); _copy_vertices($_[0], $gc); _copy_edges($_[0], $gc); $gc; } *undirected_copy_graph = \&undirected_copy; sub undirected_copy_attributes { &expect_directed; my $gc = $_[0]->new(undirected=>1); $gc->set_graph_attributes($_[0]->get_graph_attributes); _copy_vertices($_[0], $gc, 1); _copy_edges($_[0], $gc, 1); $gc; } sub directed_copy { &expect_undirected; my $gc = $_[0]->new(undirected=>0); _copy_vertices($_[0], $gc); _copy_edges($_[0], $gc, 0, 1); $gc; } *directed_copy_graph = \&directed_copy; sub directed_copy_attributes { &expect_undirected; my $gc = $_[0]->new(directed=>1); $gc->set_graph_attributes($_[0]->get_graph_attributes); _copy_vertices($_[0], $gc, 1); _copy_edges($_[0], $gc, 1, 1); $gc; } sub is_bipartite { &expect_undirected; my ($g) = @_; my $is_bipartite = 1; my %colors; my $operations = { tree_edge => sub { my( $seen, $unseen ) = @_; ( $seen, $unseen ) = sort { exists $colors{$b} <=> exists $colors{$a} } ( $seen, $unseen ); $colors{$seen} ||= -1; $colors{$unseen} = -$colors{$seen}; }, non_tree_edge => sub { $is_bipartite = '' if $colors{$_[0]} == $colors{$_[1]}; }, }; require Graph::Traversal::DFS; Graph::Traversal::DFS->new( $g, %$operations )->dfs; return $is_bipartite; } sub is_planar { &expect_undirected; my ($g) = @_; my @paths_at = map [], 1..$g->vertices; my $path_graph = Graph->new(undirected => 1); my ($n, $d, %order) = (0, 0); my $operations = { pre => sub { $order{$_[0]} = $n; $n++; }, non_tree_edge => sub { my( $i, $j ) = sort map { $order{$_} } @_[0..1]; for (@{$paths_at[$i]}) { # for all crossed paths $path_graph->add_edge( $_, $d ); } for ($i+1..$j-1) { push @{$paths_at[$_]}, $d; } $d++; }, }; require Graph::Traversal::DFS; Graph::Traversal::DFS->new( $g, %$operations )->dfs; return $path_graph->is_bipartite; } ### # Cache or not. # my %_cache_type = ( 'connectivity' => ['_ccc'], 'strong_connectivity' => ['_scc'], 'weak_connectivity_undirected_graph' => ['_wcug'], 'biconnectivity' => ['_bcc'], 'SPT_Dijkstra' => ['_spt_di', 'SPT_Dijkstra_root'], 'SPT_Bellman_Ford' => ['_spt_bf', 'SPT_Bellman_Ford_root'], 'transitive_closure_matrix' => ['_tcm'], ); for my $t (keys %_cache_type) { no strict 'refs'; my @attr = @{ $_cache_type{$t} }; *{$t."_clear_cache"} = sub { $_[0]->delete_graph_attribute($_) for @attr }; } sub _check_cache { my ($g, $type, $extra_vals, $code, @args) = @_; my $c = $_cache_type{$type}; __carp_confess "Graph: unknown cache type '$type'" if !defined $c; my ($main_key, @extra_keys) = @$c; __carp_confess "Graph: wrong number of extra values (@extra_keys) vs (@$extra_vals)" if @extra_keys != @$extra_vals; my $a = $g->get_graph_attribute($main_key); __carp_confess "$c attribute set to unexpected value $a" if defined $a and ref $a ne 'ARRAY'; unless (defined $a && $a->[ 0 ] == $g->[ _G ]) { $g->set_graph_attribute($main_key, $a = [ $g->[ _G ], $code->( $g, @args ) ]); } my $i = -1; my $extra_invalid = grep { my $v = $a->[ 1 ]->get_graph_attribute($_); $i++; # here so still incremented even if short-cut !defined $v or $v ne $extra_vals->[$i]; } @extra_keys; if ($extra_invalid) { $g->set_graph_attribute($main_key, $a = [ $g->[ _G ], $code->( $g, @args ) ]); } return $a->[ 1 ]; } ### # Connected components. # sub _connected_components_compute { my $g = $_[0]; my %v2c; my @c; return [ [], {} ] unless my @v = $g->unique_vertices; if (my $UF = &has_union_find) { my $V = $g->[ _V ]; my @ids = $V->get_ids_by_paths(\@v, 0); my ($counter, %cc2counter) = 0; my @cc = $UF->find(@ids); for (my $i = 0; $i <= $#v; $i++) { my $cc = $cc[$i]; __carp_confess "connected_component union-find did not have vertex '$v[$i]', please report" if !defined $cc; $cc2counter{$cc} = $counter++ if !exists $cc2counter{$cc}; my $ci = $cc2counter{$cc}; $v2c{ $v[$i] } = $ci; push @{ $c[$ci] }, $v[$i]; } } else { require Graph::Traversal::DFS; my %r; @r{ @v } = @v; @c = []; my $t = Graph::Traversal::DFS->new( $g, first_root => sub { (each %r)[1] }, next_root => sub { push @c, [] if keys %r; (each %r)[1]; }, pre => sub { my ($v, $t) = @_; $v2c{ $v } = $#c; push @{ $c[-1] }, $v; delete $r{ $v }; }, @_[1..$#_] ); $t->dfs; } return [ \@c, \%v2c ]; } sub _connected_components { my $ccc = _check_cache($_[0], 'connectivity', [], \&_connected_components_compute); return @{ $ccc }; } sub connected_component_by_vertex { &expect_undirected; (&_connected_components)[1]->{ $_[1] }; } sub connected_component_by_index { &expect_undirected; my $value = (&_connected_components)[0]->[$_[1]]; $value ? @{ $value || _empty_array } : (); } sub connected_components { &expect_undirected; @{ (&_connected_components)[0] }; } sub same_connected_components { &expect_undirected; my ($g, @args) = @_; my @components; if (my $UF = &has_union_find) { my @ids = &_vertex_ids; return 0 if @ids != @args; @components = $UF->find(@ids); } else { @components = @{ (&_connected_components)[1] }{ @args }; } return 0 if grep !defined, @components; require List::Util; List::Util::uniq( @components ) == 1; } sub _super_component { join("+", sort @_) } sub connected_graph { &expect_undirected; my ($g, %opt) = @_; my $cg = Graph->new(undirected => 1); if ($g->has_union_find && $g->vertices == 1) { # TODO: super_component? $cg->add_vertices($g->vertices); } else { my $sc_cb = $opt{super_component} || \&_super_component; $cg->set_vertex_attribute(scalar $sc_cb->(@$_), 'subvertices', $_) for $g->connected_components; } return $cg; } sub is_connected { &expect_undirected; return @{ (&_connected_components)[0] } == 1; } sub is_weakly_connected { &expect_directed; splice @_, 0, 1, &undirected_copy; goto &is_connected; } *weakly_connected = \&is_weakly_connected; # because recreating undirected copy every time has different hash ordering # so weakly_connected_component_by_index etc would be unstable sub _weakly_connected_undir_graph { _check_cache($_[0], 'weak_connectivity_undirected_graph', [], \&undirected_copy); } sub weakly_connected_components { &expect_directed; splice @_, 0, 1, &_weakly_connected_undir_graph; goto &connected_components; } sub weakly_connected_component_by_vertex { &expect_directed; splice @_, 0, 1, &_weakly_connected_undir_graph; goto &connected_component_by_vertex; } sub weakly_connected_component_by_index { &expect_directed; splice @_, 0, 1, &_weakly_connected_undir_graph; goto &connected_component_by_index; } sub same_weakly_connected_components { &expect_directed; splice @_, 0, 1, &_weakly_connected_undir_graph; goto &same_connected_components; } sub weakly_connected_graph { &expect_directed; splice @_, 0, 1, &_weakly_connected_undir_graph; goto &connected_graph; } sub _strongly_connected_components_compute { my $g = $_[0]; require Graph::Traversal::DFS; require List::Util; my $t = Graph::Traversal::DFS->new($g); my @d = reverse $t->dfs; my @c; my %v2c; my $u = Graph::Traversal::DFS->new( $g->transpose_graph, next_root => sub { my ($t, $u) = @_; return if !defined(my $root = List::Util::first( sub { exists $u->{$_} }, @d )); push @c, []; return $root; }, pre => sub { my ($v, $t) = @_; push @{ $c[-1] }, $v; $v2c{$v} = $#c; }, next_alphabetic => 1, ); $u->dfs; return [ \@c, \%v2c ]; } sub _strongly_connected_components_v2c { &_strongly_connected_components->[1]; } sub _strongly_connected_components_arrays { @{ &_strongly_connected_components->[0] }; } sub _strongly_connected_components { _check_cache($_[0], 'strong_connectivity', [], \&_strongly_connected_components_compute); } sub strongly_connected_components { &expect_directed; goto &_strongly_connected_components_arrays; } sub strongly_connected_component_by_vertex { &expect_directed; &_strongly_connected_components_v2c->{$_[1]}; } sub strongly_connected_component_by_index { &expect_directed; my $i = $_[1]; return if !defined(my $c = &_strongly_connected_components->[0][ $i ]); @$c; } sub same_strongly_connected_components { &expect_directed; my ($g, @args) = @_; require Set::Object; Set::Object->new(@{ &_strongly_connected_components_v2c }{@args})->size <= 1; } sub is_strongly_connected { &strongly_connected_components == 1; } *strongly_connected = \&is_strongly_connected; sub strongly_connected_graph { &expect_directed; my ($g, %attr) = @_; my $sc_cb = \&_super_component; _opt_get(\%attr, super_component => \$sc_cb); _opt_unknown(\%attr); my ($c, $v2c) = @{ &_strongly_connected_components }; my $s = Graph->new; my @s = map $sc_cb->(@$_), @$c; $s->set_vertex_attribute($s[$_], 'subvertices', $c->[$_]) for 0..$#$c; require List::Util; $s->add_edges(map [@s[ @$v2c{ @$_ } ]], grep List::Util::uniq( @$v2c{ @$_ } ) > 1, &_edges05); return $s; } ### # Biconnectivity. # sub _biconnectivity_out { my ($state, $u, $v) = @_; my @BC; while (@{$state->{stack}}) { push @BC, my $e = pop @{$state->{stack}}; last if $e->[0] eq $u && $e->[1] eq $v; } push @{$state->{BC}}, \@BC if @BC; } sub _biconnectivity_dfs { my ($E, $u, $state) = @_; $state->{low}{$u} = $state->{num}{$u} = $state->{dfs}++; for my $v ($E->successors($u)) { if (!exists $state->{num}{$v}) { push @{$state->{stack}}, [$u, $v]; $state->{pred}{$v} = $u; _biconnectivity_dfs($E, $v, $state); $state->{low}{$u} = List::Util::min(@{ $state->{low} }{$u, $v}); _biconnectivity_out($state, $u, $v) if $state->{low}{$v} >= $state->{num}{$u}; } elsif (defined $state->{pred}{$u} && $state->{pred}{$u} ne $v && $state->{num}{$v} < $state->{num}{$u}) { push @{$state->{stack}}, [$u, $v]; $state->{low}{$u} = List::Util::min($state->{low}{$u}, $state->{num}{$v}); } } } sub _biconnectivity_compute { require List::Util; my ($g) = @_; my ($V, $E) = @$g[ _V, _E ]; my %state = (BC=>[], dfs=>0); my @u = $V->ids; for my $u (@u) { next if exists $state{num}->{$u}; _biconnectivity_dfs($E, $u, \%state); push @{$state{BC}}, delete $state{stack} if @{ $state{stack} || _empty_array }; } # Mark the components each vertex belongs to. my ($bci, %v2bc, %bc2v) = 0; for my $bc (@{$state{BC}}) { $v2bc{$_}{$bci} = undef for map @$_, @$bc; $bci++; } # Any isolated vertices get each their own component. $v2bc{$_}{$bci++} = undef for grep !exists $v2bc{$_}, @u; # build vector now we know how big to make it my ($Z, %v2bc_vec, @ap) = "\0" x (($bci + 7) / 8); @v2bc_vec{@u} = ($Z) x @u; for my $v (@u) { my @components = keys %{ $v2bc{$v} }; vec($v2bc_vec{$v}, $_, 1) = 1 for @components; $bc2v{$_}{$v}{$_} = undef for @components; # Articulation points / cut vertices are the vertices # which belong to more than one component. push @ap, $v if @components > 1; } # Bridges / cut edges are the components of two vertices. my @br = grep @$_ == 2, map [keys %$_], values %bc2v; # Create the subgraph components. my @sg = map [ List::Util::uniq( map @$_, @$_ ) ], @{$state{BC}}; my ($apdeep, $sgv, $brv) = $V->get_paths_by_ids([[\@ap], \@sg, \@br], 1); return [ @$apdeep, $sgv, $brv, \%v2bc, \%v2bc_vec, $Z ]; } sub biconnectivity { &expect_undirected; @{ _check_cache($_[0], 'biconnectivity', [], \&_biconnectivity_compute, @_[1..$#_]) || _empty_array }; } sub is_biconnected { &edges >= 2 ? @{ (&biconnectivity)[0] } == 0 : undef ; } sub is_edge_connected { &edges >= 2 ? @{ (&biconnectivity)[2] } == 0 : undef; } sub is_edge_separable { &edges >= 2 ? @{ (&biconnectivity)[2] } > 0 : undef; } sub articulation_points { @{ (&biconnectivity)[0] }; } *cut_vertices = \&articulation_points; sub biconnected_components { @{ (&biconnectivity)[1] }; } sub biconnected_component_by_index { my ($i) = splice @_, 1, 1; (&biconnectivity)[1]->[ $i ]; } sub biconnected_component_by_vertex { my ($v) = splice @_, 1, 1; my $v2bc = (&biconnectivity)[3]; splice @_, 1, 0, $v; my $V = $_[0]->[ _V ]; ($v) = $V->get_ids_by_paths([$v]); return defined $v2bc->{ $v } ? keys %{ $v2bc->{ $v } } : (); } sub same_biconnected_components { my ($v2bc, $Z) = (&biconnectivity)[4,5]; my $V = $_[0]->[ _V ]; my @vs = $V->get_ids_by_paths([@_[1..$#_]]); return 0 if grep !defined, my @vecs = @$v2bc{ @vs }; my $accumulator = $vecs[0]; $accumulator &= $_ for @vecs[1..$#vecs]; # accumulate 0s -> all in same $accumulator ne $Z; } sub biconnected_graph { my ($g, %opt) = @_; my $bc = (&biconnectivity)[1]; my $bcg = Graph->new(directed => 0); my $sc_cb = $opt{super_component} || \&_super_component; my @s = map $sc_cb->(@$_), @$bc; $bcg->set_vertex_attribute($s[$_], 'subvertices', $bc->[$_]) for 0..$#$bc; my @edges; for my $i (0..$#$bc) { my @u = @{ $bc->[ $i ] }; for my $j (0..$i-1) { my %j; @j{ @{ $bc->[ $j ] } } = (); next if !grep exists $j{ $_ }, @u; push @edges, [ @s[$i, $j] ]; } } $bcg->add_edges(@edges); return $bcg; } sub bridges { @{ (&biconnectivity)[2] || _empty_array }; } ### # SPT. # sub _SPT_add { my ($g, $h, $HF, $r, $attr, $unseen, $etc) = @_; my $etc_r = $etc->{ $r } || 0; for my $s ( grep exists $unseen->{ $_ }, $g->successors( $r ) ) { my ($t) = sort {$a<=>$b} $g->get_edge_attribute_all($r, $s, $attr); $t = 1 unless defined $t; __carp_confess "Graph::SPT_Dijkstra: edge $r-$s is negative ($t)" if $t < 0; if (!defined($etc->{ $s }) || ($etc_r + $t) < $etc->{ $s }) { my $etc_s = $etc->{ $s } || 0; $etc->{ $s } = $etc_r + $t; # print "$r - $s : setting $s to $etc->{ $s } ($etc_r, $etc_s)\n"; $h->set_vertex_attributes($s, { $attr=>$etc->{ $s }, 'p', $r }); $HF->add( Graph::SPTHeapElem->new($r, $s, $etc->{ $s }) ); } } } sub _SPT_Dijkstra_compute { require Graph::SPTHeapElem; my $sptg = $_[0]->_heap_walk($_[0]->new(multiedged=>0), \&_SPT_add, {}, @_[1..$#_]); $sptg->set_graph_attribute('SPT_Dijkstra_root', $_[4]); $sptg; } sub SPT_Dijkstra { my $g = $_[0]; my @args = &_root_opt; _check_cache($g, 'SPT_Dijkstra', [$args[3]], \&_SPT_Dijkstra_compute, @args); } *SSSP_Dijkstra = \&SPT_Dijkstra; *single_source_shortest_paths = \&SPT_Dijkstra; sub SP_Dijkstra { my ($g, $u, $v) = @_; my $sptg = $g->SPT_Dijkstra(first_root => $u); my @path = ($v); require Set::Object; my $seen = Set::Object->new; my $V = $g->vertices; my $p; while (defined($p = $sptg->get_vertex_attribute($v, 'p'))) { last if $seen->contains($p); push @path, $p; $v = $p; $seen->insert($p); last if $seen->size == $V || $u eq $v; } return if !@path or $path[-1] ne $u; return reverse @path; } sub __SPT_Bellman_Ford { my ($g, $u, $v, $attr, $d, $p, $c0, $c1) = @_; return unless $c0->{ $u }; my ($w) = sort {$a<=>$b} $g->get_edge_attribute_all($u, $v, $attr); $w = 1 unless defined $w; if (defined $d->{ $v }) { if (defined $d->{ $u }) { if ($d->{ $v } > $d->{ $u } + $w) { $d->{ $v } = $d->{ $u } + $w; $p->{ $v } = $u; $c1->{ $v }++; } } # else !defined $d->{ $u } && defined $d->{ $v } } else { if (defined $d->{ $u }) { # defined $d->{ $u } && !defined $d->{ $v } $d->{ $v } = $d->{ $u } + $w; $p->{ $v } = $u; $c1->{ $v }++; } # else !defined $d->{ $u } && !defined $d->{ $v } } } sub _SPT_Bellman_Ford { my ($g, $opt, $unseenh, $unseena, $r, $next, $code, $attr) = @_; my %d; return unless defined $r; $d{ $r } = 0; my %p; my $V = $g->vertices; my %c0; # Changed during the last iteration? $c0{ $r }++; for (my $i = 0; $i < $V; $i++) { my %c1; for my $e ($g->edges) { my ($u, $v) = @$e; __SPT_Bellman_Ford($g, $u, $v, $attr, \%d, \%p, \%c0, \%c1); __SPT_Bellman_Ford($g, $v, $u, $attr, \%d, \%p, \%c0, \%c1) if $g->undirected; } %c0 = %c1 unless $i == $V - 1; } for my $e ($g->edges) { my ($u, $v) = @$e; if (defined $d{ $u } && defined $d{ $v }) { my ($d) = sort {$a<=>$b} $g->get_edge_attribute_all($u, $v, $attr); __carp_confess "Graph::SPT_Bellman_Ford: negative cycle exists" if defined $d && $d{ $v } > $d{ $u } + $d; } } return (\%p, \%d); } sub _SPT_Bellman_Ford_compute { my ($g, @args) = @_; my ($p, $d) = $g->_SPT_Bellman_Ford(@args); my $h = $g->new(multiedged=>0); for my $v (keys %$p) { my $u = $p->{ $v }; my ($w) = sort {$a<=>$b} $g->get_edge_attribute_all($u, $v, $args[6]); $h->set_edge_attribute( $u, $v, $args[6], $w); $h->set_vertex_attributes( $v, { $args[6], $d->{ $v }, p => $u } ); } $h->set_graph_attribute('SPT_Bellman_Ford_root', $args[3]); $h; } sub SPT_Bellman_Ford { my @args = &_root_opt; _check_cache($_[0], 'SPT_Bellman_Ford', [$args[3]], \&_SPT_Bellman_Ford_compute, @args); } *SSSP_Bellman_Ford = \&SPT_Bellman_Ford; sub SP_Bellman_Ford { my ($g, $u, $v) = @_; my $sptg = $g->SPT_Bellman_Ford(first_root => $u); my @path = ($v); require Set::Object; my $seen = Set::Object->new; my $V = $g->vertices; my $p; while (defined($p = $sptg->get_vertex_attribute($v, 'p'))) { last if $seen->contains($p); push @path, $p; $v = $p; $seen->insert($p); last if $seen->size == $V; } # @path = () if @path && "$path[-1]" ne "$u"; return reverse @path; } ### # Transitive Closure. # sub TransitiveClosure_Floyd_Warshall { my $self = shift; require Graph::TransitiveClosure; Graph::TransitiveClosure->new($self, @_); } *transitive_closure = \&TransitiveClosure_Floyd_Warshall; sub APSP_Floyd_Warshall { my $self = shift; require Graph::TransitiveClosure; Graph::TransitiveClosure->new($self, path => 1, @_); } *all_pairs_shortest_paths = \&APSP_Floyd_Warshall; sub _transitive_closure_matrix_compute { &APSP_Floyd_Warshall->transitive_closure_matrix; } sub transitive_closure_matrix { _check_cache($_[0], 'transitive_closure_matrix', [], \&_transitive_closure_matrix_compute, @_[1..$#_]); } sub path_length { shift->transitive_closure_matrix->path_length(@_); } sub path_successor { shift->transitive_closure_matrix->path_successor(@_); } sub path_vertices { shift->transitive_closure_matrix->path_vertices(@_); } sub all_paths { shift->transitive_closure_matrix->all_paths(@_); } sub is_reachable { shift->transitive_closure_matrix->is_reachable(@_); } sub for_shortest_paths { my $g = shift; my $c = shift; my $t = $g->transitive_closure_matrix; my @v = $g->vertices; my $n = 0; for my $u (@v) { $c->($t, $u, $_, ++$n) for grep $t->is_reachable($u, $_), @v; } return $n; } sub _minmax_path { my $g = shift; my $min; my $max; my $minp; my $maxp; $g->for_shortest_paths(sub { my ($t, $u, $v, $n) = @_; my $l = $t->path_length($u, $v); return unless defined $l; my $p; if ($u ne $v && (!defined $max || $l > $max)) { $max = $l; $maxp = $p = [ $t->path_vertices($u, $v) ]; } if ($u ne $v && (!defined $min || $l < $min)) { $min = $l; $minp = $p || [ $t->path_vertices($u, $v) ]; } }); return ($min, $max, $minp, $maxp); } sub diameter { my $g = shift; my ($min, $max, $minp, $maxp) = $g->_minmax_path(@_); return defined $maxp ? (wantarray ? @$maxp : $max) : undef; } *graph_diameter = \&diameter; sub longest_path { my ($g, $u, $v) = @_; my $t = $g->transitive_closure_matrix; if (defined $u) { return wantarray ? $t->path_vertices($u, $v) : $t->path_length($u, $v) if defined $v; my $max; my @max; for my $v (grep $u ne $_, $g->vertices) { my $l = $t->path_length($u, $v); next if !(defined $l && (!defined $max || $l > $max)); $max = $l; @max = $t->path_vertices($u, $v); } return wantarray ? @max : $max; } if (defined $v) { my $max; my @max; for my $u (grep $_ ne $v, $g->vertices) { my $l = $t->path_length($u, $v); next if !(defined $l && (!defined $max || $l > $max)); $max = $l; @max = $t->path_vertices($u, $v); } return wantarray ? @max : @max - 1; } my ($min, $max, $minp, $maxp) = $g->_minmax_path(@_); return defined $maxp ? (wantarray ? @$maxp : $max) : undef; } sub vertex_eccentricity { &expect_undirected; my ($g, $u) = @_; return Infinity() if !&is_connected; my $max; for my $v (grep $u ne $_, $g->vertices) { my $l = $g->path_length($u, $v); next if !(defined $l && (!defined $max || $l > $max)); $max = $l; } return defined $max ? $max : Infinity(); } sub shortest_path { &expect_undirected; my ($g, $u, $v) = @_; my $t = $g->transitive_closure_matrix; if (defined $u) { return wantarray ? $t->path_vertices($u, $v) : $t->path_length($u, $v) if defined $v; my $min; my @min; for my $v (grep $u ne $_, $g->vertices) { my $l = $t->path_length($u, $v); next if !(defined $l && (!defined $min || $l < $min)); $min = $l; @min = $t->path_vertices($u, $v); } # print "min/1 = @min\n"; return wantarray ? @min : $min; } if (defined $v) { my $min; my @min; for my $u (grep $_ ne $v, $g->vertices) { my $l = $t->path_length($u, $v); next if !(defined $l && (!defined $min || $l < $min)); $min = $l; @min = $t->path_vertices($u, $v); } # print "min/2 = @min\n"; return wantarray ? @min : $min; } my ($min, $max, $minp, $maxp) = $g->_minmax_path(@_); return if !defined $minp; wantarray ? @$minp : $min; } sub radius { &expect_undirected; my $g = shift; my ($center, $radius) = (undef, Infinity()); for my $v ($g->vertices) { my $x = $g->vertex_eccentricity($v); ($center, $radius) = ($v, $x) if defined $x && $x < $radius; } return $radius; } sub center_vertices { &expect_undirected; my ($g, $delta) = @_; $delta = 0 unless defined $delta; $delta = abs($delta); my @c; my $Inf = Infinity(); my $r = $g->radius; if (defined $r && $r != $Inf) { for my $v ($g->vertices) { my $e = $g->vertex_eccentricity($v); next unless defined $e && $e != $Inf; push @c, $v if abs($e - $r) <= $delta; } } return @c; } *centre_vertices = \¢er_vertices; sub average_path_length { my $g = shift; my @A = @_; my $d = 0; my $m = 0; $g->for_shortest_paths(sub { my ($t, $u, $v, $n) = @_; return unless my $l = $t->path_length($u, $v); return if defined $A[0] && $u ne $A[0]; return if defined $A[1] && $v ne $A[1]; $d += $l; $m++; }); return $m ? $d / $m : undef; } ### # Simple tests. # sub is_multi_graph { return 0 unless &is_multiedged || &is_countedged; my $g = $_[0]; my $multiedges = 0; for my $e (&_edges05) { my ($u, @v) = @$e; return 0 if grep $u eq $_, @v; $multiedges++ if $g->get_edge_count(@$e) > 1; } return $multiedges; } sub is_simple_graph { return 1 unless &is_multiedged || &is_countedged; my $g = $_[0]; return 0 if grep $g->get_edge_count(@$_) > 1, &_edges05; return 1; } sub is_pseudo_graph { my $m = &is_countedged || &is_multiedged; my $g = $_[0]; for my $e (&_edges05) { my ($u, @v) = @$e; return 1 if grep $u eq $_, @v; return 1 if $m && $g->get_edge_count($u, @v) > 1; } return 0; } ### # Rough isomorphism guess. # my %_factorial = (0 => 1, 1 => 1); sub __factorial { my $n = shift; for (my $i = 2; $i <= $n; $i++) { next if exists $_factorial{$i}; $_factorial{$i} = $i * $_factorial{$i - 1}; } $_factorial{$n}; } sub _factorial { my $n = int(shift); __carp_confess "factorial of a negative number" if $n < 0; __factorial($n) unless exists $_factorial{$n}; return $_factorial{$n}; } sub could_be_isomorphic { my ($g0, $g1) = @_; return 0 unless &vertices == $g1->vertices; return 0 unless &_edges05 == $g1->_edges05; my %d0; $d0{ $g0->in_degree($_) }{ $g0->out_degree($_) }++ for &vertices; my %d1; $d1{ $g1->in_degree($_) }{ $g1->out_degree($_) }++ for $g1->vertices; return 0 unless keys %d0 == keys %d1; for my $da (keys %d0) { return 0 unless exists $d1{$da} && keys %{ $d0{$da} } == keys %{ $d1{$da} }; return 0 if grep !(exists $d1{$da}{$_} && $d0{$da}{$_} == $d1{$da}{$_}), keys %{ $d0{$da} }; } for my $da (keys %d0) { return 0 if grep $d1{$da}{$_} != $d0{$da}{$_}, keys %{ $d0{$da} }; delete $d1{$da}; } return 0 unless keys %d1 == 0; my $f = 1; for my $da (keys %d0) { $f *= _factorial(abs($d0{$da}{$_})) for keys %{ $d0{$da} }; } return $f; } ### # Analysis functions. sub subgraph_by_radius { $_[0]->subgraph([ @_[1..$#_-1], &reachable_by_radius ]); } sub clustering_coefficient { my ($g) = @_; return unless my @v = $g->vertices; require Set::Object; my %clustering; my $gamma = 0; for my $n (@v) { my $gamma_v = 0; my @neigh = $g->successors($n); my $c = Set::Object->new; for my $u (@neigh) { for my $v (grep +(!$c->contains("$u-$_") && $g->has_edge($u, $_)), @neigh) { $gamma_v++; $c->insert("$u-$v"); $c->insert("$v-$u"); } } if (@neigh > 1) { $clustering{$n} = $gamma_v/(@neigh * (@neigh - 1) / 2); $gamma += $gamma_v/(@neigh * (@neigh - 1) / 2); } else { $clustering{$n} = 0; } } $gamma /= @v; return wantarray ? ($gamma, %clustering) : $gamma; } sub betweenness { my $g = shift; my @V = $g->vertices(); my %Cb; # C_b{w} = 0 @Cb{@V} = (); for my $s (@V) { my @S; # stack (unshift, shift) my %P; # P{w} = empty list $P{$_} = [] for @V; my %sigma; # \sigma{t} = 0 $sigma{$_} = 0 for @V; $sigma{$s} = 1; my %d; # d{t} = -1; $d{$_} = -1 for @V; $d{$s} = 0; my @Q; # queue (push, shift) push @Q, $s; while (@Q) { my $v = shift @Q; unshift @S, $v; for my $w ($g->successors($v)) { # w found for first time if ($d{$w} < 0) { push @Q, $w; $d{$w} = $d{$v} + 1; } # Shortest path to w via v if ($d{$w} == $d{$v} + 1) { $sigma{$w} += $sigma{$v}; push @{ $P{$w} }, $v; } } } my %delta; $delta{$_} = 0 for @V; while (@S) { my $w = shift @S; $delta{$_} += $sigma{$_}/$sigma{$w} * (1 + $delta{$w}) for @{ $P{$w} }; $Cb{$w} += $delta{$w} if $w ne $s; } } return %Cb; } sub connected_subgraphs { my $g = shift; require Set::Object; my @subgraphs = ( [ map { Set::Object->new($_) } $g->vertices ] ); for (2..scalar $g->vertices) { my %seen; for my $subgraph (@{$subgraphs[-1]}) { for my $neighbour ((Set::Object->new( map { $g->neighbours($_) } $subgraph->members ) - $subgraph)->members) { my $new_subgraph = Set::Object->new($subgraph->members, $neighbour); my $key = join '|', @$new_subgraph; next if exists $seen{$key}; $seen{$key} = $new_subgraph; } } push @subgraphs, [values %seen]; } return map { $g->subgraph([$_->members]) } map { @$_ } @subgraphs; } 1; Graph-0.9735/README0000644000175000017500000000217713743337306013510 0ustar osboxesosboxesREADME for Perl module Graph This is Graph, a Perl module for dealing with graphs, the abstract data structures. (If you were looking for pie charts, I'm sorry.) This is a full rewrite of the Graph module 0.2xx series as discussed in the book "Mastering Algorithms with Perl", written by Jarkko Hietaniemi (the undersigned), John Macdonald, and Jon Orwant, and published by O'Reilly and Associates. This rewrite is not fully compatible with the 0.2xx series, simply because I did not want to carry over all the design flaws and bugs. Test cases of all sizes to keep me honest are warmly welcomed. For the changes, read Changes. For the release notes, read RELEASE. If you find bugs, please distill your test case to the absolute minimum and report it, preferably via http://rt.cpan.org/NoAuth/Bugs.html?Dist=Graph This module was written by Jarkko Hietaniemi jhi@iki.fi Jarkko has moved onto other things, so now I'm maintaining this distribution. I have no great plans for it, but will try to address any bugs reported, and follow all modern conventions, etc. Pull requests and patches most welcome :-) Neil Bowers neil@cpan.org Graph-0.9735/MANIFEST0000644000175000017500000000415614771263365013765 0ustar osboxesosboxesChanges DESIGN lib/Graph.pm lib/Graph.pod lib/Graph/AdjacencyMap.pm lib/Graph/AdjacencyMap/Light.pm lib/Graph/AdjacencyMatrix.pm lib/Graph/Attribute.pm lib/Graph/BitMatrix.pm lib/Graph/Directed.pm lib/Graph/Matrix.pm lib/Graph/MSTHeapElem.pm lib/Graph/SPTHeapElem.pm lib/Graph/TransitiveClosure.pm lib/Graph/TransitiveClosure/Matrix.pm lib/Graph/Traversal.pm lib/Graph/Traversal/BFS.pm lib/Graph/Traversal/DFS.pm lib/Graph/Undirected.pm lib/Graph/UnionFind.pm Makefile.PL MANIFEST This list of files README RELEASE t/00-report-prereqs.t t/00_use.t t/01_isa.t t/02_trap.t t/03_derived.t t/04_dgraph.t t/05_ugraph.t t/06_new.t t/07_gen.t t/08_stringify.t t/09_eq.t t/10_has_vertices.t t/11_vertices.t t/12_has_vertex.t t/13_add_vertex.t t/14_delete_vertex.t t/16_edges.t t/20_countvertexed.t t/21_multivertexed.t t/22_refvertexed.t t/24_mixvertexed.t t/25_countedged.t t/26_multiedged.t t/30_mixedged.t t/33_hyperedge.t t/39_edges_at.t t/40_edges_from.t t/41_edges_to.t t/42_add_path.t t/45_add_cycle.t t/48_get_vertex_count.t t/49_get_edge_count.t t/50_vertex_attributes.t t/51_multivertex_attributes.t t/53_multiedge_attributes.t t/56_neighbourhood.t t/57_degree.t t/58_connections.t t/59_dfs.t t/60_bfs.t t/61_connected.t t/62_bcc.t t/63_scc.t t/64_mst.t t/65_ref.t t/66_simple.t t/67_copy.t t/71_spt.t t/72_transitive.t t/73_diameter.t t/74_random.t t/75_attribute_array.t t/76_attribute_hash.t t/77_adjacency.t t/78_expect.t t/79_unionfind.t t/80_isomorphic.t t/82_cycle.t t/83_bitmatrix.t t/84_all_cessors.t t/85_subgraph.t t/86_bipartite.t t/87_planar.t t/88_max_cliq.t t/89_connected_subgraphs.t t/99_misc.t t/MyDGraph.pm t/MyGraph.pm t/MyUGraph.pm t/simple.pl t/u_at1.t t/u_at2.t t/u_at3.t t/u_bb_rv.t t/u_bf.t t/u_bill.t t/u_bo_ap1.t t/u_bo_ap2.t t/u_bo_apx.t t/u_cd_rv.t t/u_dl_uf.t t/u_jh_va.t t/u_mn_va.t t/u_ng_mst.t t/u_ng_path.t t/u_ng_scc.t t/u_rb_cc.t t/u_re_sd.t t/u_ro_ra.t t/u_sc_me.t t/u_te_ea.t t/u_te_me.t TODO util/cover.sh util/grand.pl util/size.pl util/srand.sh META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Graph-0.9735/Makefile.PL0000644000175000017500000000377514755472612014613 0ustar osboxesosboxesuse strict; use warnings; use ExtUtils::MakeMaker; require 5.006; my $mm_ver = $ExtUtils::MakeMaker::VERSION; if ($mm_ver =~ /_/) { # dev version $mm_ver = eval $mm_ver; die $@ if $@; } my %PREREQ_PM = ( 'List::Util' => '1.45', 'Scalar::Util' => 0, 'Heap' => '0.80', 'Set::Object' => '1.40', ); if ($] >= 5.008) { $PREREQ_PM{'Storable'} = '2.05'; $PREREQ_PM{'Safe' } = 0, } my $repo = 'graphviz-perl/Graph'; WriteMakefile( NAME => 'Graph', VERSION_FROM => 'lib/Graph.pm', PREREQ_PM => \%PREREQ_PM, AUTHOR => 'Jarkko Hietaniemi ', ($mm_ver >= 6.31 ? (LICENSE => 'perl') : () ), ($mm_ver >= 6.48 ? (MIN_PERL_VERSION => 5.006) : () ), ($mm_ver <= 6.45 ? () : (META_MERGE => { 'meta-spec' => { version => 2 }, resources => { bugtracker => { web => "https://github.com/$repo/issues" }, repository => { type => 'git', web => "https://github.com/$repo", url => "git://github.com/$repo.git", }, }, prereqs => { develop => { requires => { 'Test::Pod::Coverage' => '1.00', 'Test::Pod' => '1.00', }, }, test => { requires => { 'Test::More' => '0.82', # explain 'Math::Complex' => 0, }, recommends => { 'App::Prove' => '3.00', # prove -j4 }, }, }, }) ), ); # Some Debian distributions have a broken List::Util (see rt.cpan.org #9568) eval 'require Scalar::Util; import Scalar::Util qw(weaken)'; if ($@) { die <<__EOF__; $@ You do not have Scalar::Util::weaken, cannot continue, aborting. __EOF__ } Graph-0.9735/util/0000755000175000017500000000000014771263365013603 5ustar osboxesosboxesGraph-0.9735/util/size.pl0000644000175000017500000000206214741033474015103 0ustar osboxesosboxesuse lib 'lib'; use strict; use warnings; use Graph; use Devel::Size qw(size total_size); my $N = 65536; my $fmt = "%5s %8s %9s\n"; my $fmr = "%5d %8d %9.1f\n"; for ([0, 0], [0, 1], [1, 0], [1, 1]) { my ($countv, $counte) = @$_; my %args = ( countvertexed => $countv, countedged => $counte, ); printf $fmt, "V", "S", "S/N"; my $g0 = Graph->new(%args); my $s0 = total_size($g0); printf $fmr, 0, $s0, 0; my $vr; for (my $n = 1; $n <= $N; $n *= 16) { my $g0 = Graph->new(%args); $g0->add_vertices(1..$n); my $s = total_size($g0); $vr = ($s - $s0) / $n; printf $fmr, $n, $s, $vr; } printf "Vertices(countvertexed=$countv) / MB = %8.1f\n", 1048576/$vr; printf $fmt, "E", "S", "S/N"; my $g1 = Graph->new; printf $fmr, 0, $s0, 0; my $er; for (my $n = 1; $n <= $N; $n *= 16) { my $g1 = Graph->new(%args); $g1->add_edges(map [0, $_], 1..$n); my $s = total_size($g1); $er = ($s - $s0 - $n * $vr) / $n; printf $fmr, $n, $s, $er; } printf "Edges(countedged=$counte) / MB = %8.1f\n", 1048576/$er; } Graph-0.9735/util/srand.sh0000644000175000017500000000007713774262024015243 0ustar osboxesosboxes#!/bin/sh env PERL_HASH_SEED= PERL_HASH_SEED_DEBUG=1 make test Graph-0.9735/util/grand.pl0000644000175000017500000000557114741033474015234 0ustar osboxesosboxesuse strict; use warnings; use Graph; use Time::HiRes qw(time); use Getopt::Long; my %OPT = (seed => 42, test => 'apsp', fill => 0.50, V => 20, directed => 1, unionfind => 0); my %TEST2METHOD = ( apsp => 'APSP_Floyd_Warshall', mstk => 'MST_Kruskal', mstp => 'MST_Prim', sptd => 'SPT_Dijkstra', sptb => 'SPT_Bellman_Ford', cc => 'connected_components', bcc => 'biconnected_components', scc => 'strongly_connected_components', succ => sub { my $g = shift; $g->successors($_) for $g->vertices }, ef => sub { my $g = shift; $g->edges_from($_) for $g->vertices }, ea => sub { my $g = shift; $g->edges_at($_) for $g->vertices }, ); my %WTEST; @WTEST{qw(apsp mstp mstk sptd sptb)} = (); my %UTEST; @UTEST{qw(mstk mstp cc bcc)} = (); my %DTEST; @DTEST{qw(scc)} = (); sub usage { die <<__EOF__; $0: Usage: $0 [--seed=n] [--test=@{[join('|', sort keys %TEST2METHOD)]}] [--directed=d] [--fill=f] [V] Default values:@{[ map qq{\n$_ = $OPT{$_}}, sort keys %OPT ]} __EOF__ } $| = 1; usage() unless GetOptions( 'seed=n' => \$OPT{seed}, 'test=s' => \$OPT{test}, 'directed=n' => \$OPT{directed}, 'fill=f' => \$OPT{fill}, 'uf=n' => \$OPT{unionfind}, ); $OPT{V} = shift if @ARGV; usage() if @ARGV; usage() unless $TEST2METHOD{$OPT{test}}; print "Running $OPT{test}...\n"; srand($OPT{seed}); if (exists $UTEST{$OPT{test}} && $OPT{directed}) { $OPT{directed} = 0; print "($OPT{test} needs undirected, fixed)\n"; } elsif (exists $DTEST{$OPT{test}} && !$OPT{directed}) { $OPT{directed} = 1; print "($OPT{test} needs directed, fixed)\n"; } if ($OPT{fill} < 0.0 || $OPT{fill} > 1.0) { $OPT{fill} = 0.5; print "($OPT{fill} must be between 0.0 and 1.0, fixed to be 0.5)\n"; } # Thanks to Devel::DProf and List::Util breakage. # my $g = Graph->random_graph(vertices => $OPT{V}, # directed => $OPT{directed}, # edges_fill => $OPT{fill}); my $E = int(($OPT{V} * ($OPT{V} - 1) * $OPT{fill}) / ($OPT{directed} ? 1 : 2)); my $g = Graph->new(map +($_ => $OPT{$_}), qw(directed unionfind)); my $e = $E; my (%v1_v2, @edges); my $t0_edge = time(); while (1) { my $u = int(rand($OPT{V})); my $v = int(rand($OPT{V})); if ($u ne $v && !exists $v1_v2{$u}{$v}) { push @edges, [$u, $v]; $v1_v2{$u}{$v} = undef; last unless --$e; } } if (exists $WTEST{$OPT{test}}) { push @$_, rand() for @edges; $g->add_weighted_edges(map @$_, @edges); } else { $g->add_edges(@edges); } my $t1_edge = time(); printf "%d vertices, %d edges - set up %.2f\n", $OPT{V}, $E, $t1_edge - $t0_edge; my $t0 = time(); my ($u0, $s0) = times(); () = $g->${ \$TEST2METHOD{$OPT{test}} }; my $t1 = time(); my ($u1, $s1) = times(); my $u = $u1 - $u0; my $s = $s1 - $s0; my $c = $u + $s; printf "real %.2f user %.2f system %.2f cpu %.2f\n", $t1 - $t0, $u, $s, $c; exit(0); Graph-0.9735/util/cover.sh0000755000175000017500000000030313743337306015247 0ustar osboxesosboxes#!/bin/sh set -ex perl -MDevel::Cover -e 1 || exit 1 cover -delete env HARNESS_PERL_SWITCHES=-MDevel::Cover make test cover perl -wIlib -MPod::Coverage=Graph -e1 | tee podcoverage.out exit 0 Graph-0.9735/DESIGN0000644000175000017500000001072213743337306013517 0ustar osboxesosboxes *** NOTE THAT THE INTERNALS OF GRAPH 0.50 ARE COMPLEX *** *** AND (ALMOST INTENTIONALLY) UNDERDOCUMENTED. *** *** YOU ARE NOT SUPPOSED TO BE ABLE TO ACCESS *** *** THE INTERNALS DIRECTLY. *** The design goals of Graph 0.5 were flexibility and being able to represent even the more unusual graphs like graphs with reference-counted edges and vertices, multi(edge or vertex)graphs (an edge or vertex can "be present" more than once), hyper(edge)graphs (an edge can join more than two edges), and hypervertexgraphs (vertices of more than one, errm, vertex). As you can see (or rather, not see) being fast was not a design goal. Note that while the underlying data structures can do the above (and even a little bit beyond those), the common graph algorithms don't either (at best) understand at all the more esoteric graphs, or (at worst) break horribly, either by producing wrong results, crashing, or looping infinitely (isn't it nice to have options?). It is hoped that the people needing algorithms on the more esoteric graphs will write their own algorithms or enhance the current ones to cope better. While the data structures (into which we will get in a moment) are flexible, extra care was taken to optimize the common case (your usual non-counted non-hyper graphs) so that too much time memory isn't wasted in being overly general. Some waste does happen, so in general the code is 2-4 times slower than the previous generation, Graph 0.2xxx. Another complicating factor not really stemming from graph theory but from Perl itself was that some people wanted to be able to have Perl objects that have stringify overload as graph vertices. Also this is now possible (the "refvertexed" parameter), at least based on very light testing. It is very likely, though, that in some corners of the code this will still not work (it requires an extra step in handling vertices, and I quite probably forgot some spots). The most basic data structure of Graphs is a Map. A map consists of zero or more 'coordinates' and a data item that can be found by following the set of coordinates. The data item can also be missing which means that the set of coordinates exists. The set of coordinates can be ordered or unordered, and it can also be "uniquefying" or not on the coordinates. For the vertices the coordinates are strings, but there is a mapping from those strings to integers, and the edge coordinates use those integers. Maps come in different complexities: light, vertex, and heavy. A 'light' map is used if the elements have nothing fancy like for example attributes (it is basically just using a hash for the vertices and a hash of hashes for the edges), a 'heavy' map is used if they do. A 'vertex' map is a simplified version of a 'heavy' map used only for 'normal' (non-hyper) vertices. A vertex is an AdjacencyMap of one coordinate, an edge is a AdjacencyMap of two coordinates. (If we are talking about non-hyper cases.) Therefore an ordinary Graph is at its heart a tuple of AdjacencyMaps or in familiar terms (V, E). The rather complex design means that one is not really able (not without considerable and future-fragile effort) to derive from Graph and expect to be able to directly access and manipulate the internal data structures. Multiplicity in its most basic form is handled by having an additional counter for an (AdjacencyMap) item and then incrementing and decrementing that appropriately. When the counter goes to zero, a full delete takes place: this is called countvertexed/countedged. To be really "multi" each vertex or edge needs to have its own identity and existence and to be able to store its own data: this is called multivertexed/multiedged. The hyperness is handled by having separate slots for each AdjacencyMap item arity: zero, one (for vertices), two (for edges), and so forth. Both the multiplicity (count/multi) and hyperness are set up on demand when those features are requested at Graph creation, in the normal case the data structures are as simple as possible. The implementation is done by switching the internal implementation between ::Light, ::Vertex, and ::Heavy classes. This is all done automatically and internally AND ONE IS NOT SUPPOSED TO USE THOSE CLASSES DIRECTLY. Attributes are part of (non-'light') AdjacencyMaps, this means that each vertex and edge can have its own attributes. Also Graphs can have attributes, but unfortunately Graph attributes do not currently use the AdjacencyMap abstraction for storing their attributes.