File Coverage

blib/lib/Graph/ChainBuilder.pm
Criterion Covered Total %
statement 62 73 84.9
branch 22 24 91.6
condition n/a
subroutine 14 16 87.5
pod 4 4 100.0
total 102 117 87.1


line stmt bran cond sub pod time code
1             package Graph::ChainBuilder;
2             $VERSION = v0.0.2;
3              
4 2     2   34748 use warnings;
  2         5  
  2         72  
5 2     2   14 use strict;
  2         5  
  2         73  
6 2     2   24 use Carp;
  2         4  
  2         348  
7              
8             =head1 NAME
9              
10             Graph::ChainBuilder - build directed 2-regular cyclic graphs
11              
12             =head1 SYNOPSIS
13              
14             This object collects data into a set of ordered chains, allowing you to
15             organize e.g. edges AB,CD,AD,CB into the circular sequence AB,BC,CD,DA
16             while keeping track of the directionality of the input data.
17              
18             my $graph = Graph::ChainBuilder->new;
19              
20             while(whatever) {
21             ...
22             $graph->add($p0, $p1, $data);
23             }
24              
25             An edge is defined by the strings $p0 and $p1. The $data is whatever
26             you want to associate with an edge.
27              
28             foreach my $loop ($graph->loops) {
29             foreach my $edge (@$loop) {
30             ...
31             $edge->data;
32             }
33             }
34              
35             =head1 Limitations
36              
37             This code will identify multiple independent loops in an arbitrary set
38             of unordered edges, but assumes that all loops are closed and that no
39             stray edges exist. The result is undefined if your input contains
40             duplicate or dangling edges.
41              
42             =cut
43              
44 2     2   2132 use Class::Accessor::Classy;
  2         14003  
  2         17  
45             with 'new';
46             lo 'loops';
47 2     2   431 no Class::Accessor::Classy;
  2         5  
  2         10  
48              
49             =head2 new
50              
51             my $graph = Graph::ChainBuilder->new;
52              
53             =cut
54              
55             sub new {
56 3     3 1 902 my $self = shift->SUPER::new();
57 3         30 $self->{ep} = {};
58 3         6 $self->{loops} = [];
59 3         8 return($self);
60             } # end subroutine new definition
61             ########################################################################
62              
63             =head2 add
64              
65             Adds an edge to the graph. The nodes $p0 and $p1 will be connected (if
66             possible) to the existing loops.
67              
68             $graph->add($p0, $p1, $data);
69              
70             Attempting to add an edge with a point which has already been connected
71             will throw an error.
72              
73             =cut
74              
75             sub add {
76 14     14 1 88 my $self = shift;
77 14         18 my ($p0, $p1, $data) = @_;
78              
79 14         34 my $edge = Graph::ChainBuilder::edge->new($p0, $p1, 0, $data);
80              
81 14         20 my $ep = $self->{ep};
82 14 100       34 if(my $where = delete($ep->{$p0})) {
    100          
83 6         9 my ($chain, $end) = @$where;
84             # warn "insert $p0|$p1 on $chain $end";
85 6 100       25 $edge->reverse unless($end);
86 6 100       13 splice(@$chain, $end ? scalar(@$chain) : 0, 0, $edge);
87 6 50       14 if(my $and = delete($ep->{$p1})) {
88             # warn "unravelling needed at $p1";
89             # {local $ep->{$p1} = $and; warn join("\n", $self->stringify, '');}
90 6 100       14 if($and->[0] eq $chain) { # closed!
91 3         4 push(@{$self->{loops}}, $chain);
  3         14  
92             }
93             else {
94 3         8 $self->_unravel([$chain, $end], $and);
95             }
96             }
97             else {
98 0         0 $ep->{$p1} = [$chain, $end];
99             }
100             }
101             elsif($where = delete($ep->{$p1})) {
102 2         4 my ($chain, $end) = @$where;
103             # warn "insert $p1|$p0 on $chain $end";
104 2 100       8 $edge->reverse if($end);
105 2 100       6 splice(@$chain, $end ? scalar(@$chain) : 0, 0, $edge);
106 2         8 $ep->{$p0} = [$chain, $end];
107             }
108             else {
109             # start a new chain
110 6         8 my $chain = [$edge];
111 6         14 $ep->{$p0} = [$chain, 0];
112 6         23 $ep->{$p1} = [$chain, 1];
113             }
114             } # end subroutine add definition
115             ########################################################################
116              
117             =begin nothing
118              
119             =head2 open_ends
120              
121             =head2 stringify
122              
123             =end nothing
124              
125             =cut
126              
127             sub open_ends {
128 0     0 1 0 my $self = shift;
129              
130 0         0 my %once = map({$_ => $_} map {$_->[0]} values %{$self->{ep}});
  0         0  
  0         0  
  0         0  
131 0         0 return(values %once);
132             }
133             sub stringify {
134 0     0 1 0 my $self = shift;
135              
136 0         0 return map({join(" ", map({join("|", $_->p0, $_->p1)} @$_))}
  0         0  
  0         0  
137             $self->open_ends);
138             }
139              
140             # recursively check/close connected subchains
141             sub _unravel {
142 3     3   3 my $self = shift;
143 3         5 my ($where, $and) = @_;
144              
145 3         4 my $ep = $self->{ep};
146              
147 3         4 my $chain = $where->[0];
148 3         3 my $end = $where->[1];
149              
150 3         3 my $subchain = $and->[0];
151              
152 3 100       21 if($end == $and->[1]) { # reverse direction
153 1         492 @$subchain = reverse(@$subchain);
154 1         4 $_->reverse for(@$subchain);
155             }
156              
157 3 100       10 splice(@$chain, $end ? scalar(@$chain) : 0, 0, @$subchain);
158              
159             # the opposite end of that chain is now this end of this chain
160 3         5 my $which_node = 'p' . $end;
161 3 100       8 my $last = $subchain->[$end ? $#$subchain : 0]->$which_node;
162 3 50       7 $ep->{$last} or die "that's unexpected";
163 3         13 $ep->{$last} = $where;
164             } # end subroutine _unravel definition
165             ########################################################################
166              
167             {
168             package Graph::ChainBuilder::edge;
169              
170             =head2 new
171              
172             my $e = Graph::ChainBuilder::edge->new($p0, $p1, $rev, $data);
173              
174             =cut
175              
176 14     14   15 sub new { my $class = shift; bless([@_], $class); }
  14         45  
177 17     17   2017 sub p0 {shift->[0]};
178 8     8   18 sub p1 {shift->[1]};
179 6     6   20 sub reversed {shift->[2]};
180 8     8   29 sub data {shift->[3]};
181              
182 4     4   5 sub reverse { my $e = shift; $e->[2] ^= 1; @$e[0,1] = @$e[1,0]; }
  4         9  
  4         15  
183             }
184             ########################################################################
185              
186              
187             =head1 AUTHOR
188              
189             Eric Wilhelm @
190              
191             http://scratchcomputing.com/
192              
193             =head1 BUGS
194              
195             If you found this module on CPAN, please report any bugs or feature
196             requests through the web interface at L. I will be
197             notified, and then you'll automatically be notified of progress on your
198             bug as I make changes.
199              
200             If you pulled this development version from my /svn/, please contact me
201             directly.
202              
203             =head1 COPYRIGHT
204              
205             Copyright (C) 2009 Eric L. Wilhelm, All Rights Reserved.
206              
207             =head1 NO WARRANTY
208              
209             Absolutely, positively NO WARRANTY, neither express or implied, is
210             offered with this software. You use this software at your own risk. In
211             case of loss, no person or entity owes you anything whatsoever. You
212             have been warned.
213              
214             =head1 LICENSE
215              
216             This program is free software; you can redistribute it and/or modify it
217             under the same terms as Perl itself.
218              
219             =cut
220              
221             # vi:ts=2:sw=2:et:sta
222             1;