File Coverage

blib/lib/Graph/AdjacencyMap.pm
Criterion Covered Total %
statement 286 288 99.3
branch 174 190 91.5
condition 71 81 87.6
subroutine 57 58 98.2
pod 19 19 100.0
total 607 636 95.4


line stmt bran cond sub pod time code
1             package Graph::AdjacencyMap;
2              
3 80     49119   462 use strict;
  80         124  
  80         2046  
4 80     80   330 use warnings;
  80         120  
  80         9753  
5              
6             # $SIG{__DIE__ } = \&Graph::__carp_confess;
7             # $SIG{__WARN__} = \&Graph::__carp_confess;
8              
9             my $empty = {};
10 82     82   522 sub _empty () { $empty }
11              
12             my (@FLAGS, %FLAG_COMBOS, %FLAG2I, @FIELDS);
13             BEGIN {
14 80     80   371 @FLAGS = qw(_COUNT _MULTI _UNORD _REF _UNIONFIND _LIGHT _STR);
15 80         473 %FLAG_COMBOS = (
16             _COUNTMULTI => [qw(_COUNT _MULTI)],
17             _REFSTR => [qw(_REF _STR)],
18             );
19             # Next id, Flags, Arity, Index to path, path to index,
20             # successors, predecessors: 2-level hashes to array-ref of path IDs
21             # attributes - two-level for MULTI, node/multi count
22 80         231 @FIELDS = qw(_n _f _arity _i _pi _s _p _attr _count);
23 80         282 for my $i (0..$#FLAGS) {
24 560         925 my $n = $FLAGS[$i];
25 560         717 my $f = 1 << $i;
26 560         972 $FLAG2I{$n} = $f;
27 80     80   508 no strict 'refs';
  80         134  
  80         11174  
28 560         3619 *$n = sub () { $f };
  0         0  
29 560     70689   2143 *{"_is$n"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f
  560     66593   2235  
  70689         192123  
30             }
31 80         309 for my $k (keys %FLAG_COMBOS) {
32 160         251 my $f = 0;
33 160         238 $f |= $_ for map $FLAG2I{$_}, @{ $FLAG_COMBOS{$k} };
  160         641  
34 80     80   521 no strict 'refs';
  80         1490  
  80         7825  
35 160     1325   965 *$k = sub () { return $f }; # return to dodge pointless 5.22 stricture
  1325         4152  
36 160     1   493 *{"_is$k"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f
  160     0   709  
  1         6  
37             }
38 80         334 for my $i (0..$#FIELDS) {
39 80     80   520 no strict 'refs';
  80         163  
  80         5008  
40 720         2991 *{ $FIELDS[$i] }= sub () { $i };
  720         11569  
  0         0  
41             }
42             }
43              
44             sub _new {
45 1792     1792   4628 my ($class, $flags, $arity) = @_;
46 1792         2493 my $hyper = !$arity;
47 1792         2425 my $need_s = $arity != 1;
48 1792   100     5981 my $need_p = $need_s && !($flags & _UNORD);
49 1792 100       9216 bless [
    100          
50             0, $flags, $arity, [], {},
51             ($need_s ? {} : undef), ($need_p ? {} : undef),
52             [], [],
53             ], $class;
54             }
55              
56             require Exporter;
57 80     80   561 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
  80         142  
  80         314813  
58             @ISA = qw(Exporter);
59             %EXPORT_TAGS =
60             (flags => [@FLAGS, keys %FLAG_COMBOS, qw(_GEN_ID)],
61             fields => \@FIELDS);
62             @EXPORT_OK = map @$_, values %EXPORT_TAGS;
63              
64             my $_GEN_ID = 0;
65              
66 168     168   691 sub _GEN_ID () { \$_GEN_ID }
67              
68             sub stringify {
69 24     24 1 3000 my ($f, $arity, $m) = (@{ $_[0] }[ _f, _arity ], $_[0]);
  24         67  
70 24         56 my ($multi, @rows) = $f & _MULTI;
71 24         55 my @p = $m->paths;
72             @p = $arity == 1 ? sort @p :
73 24 100 100     238 map $_->[0], sort { $a->[1] cmp $b->[1] }
  138 100       164  
74             ($arity == 0 && !($f & _UNORD))
75             ? map [$_, join '|', map "@$_", @$_], @p
76             : map [$_,"@$_"], @p; # use the Schwartz
77 24 100       84 if ($arity == 2) {
78 10         642 require Set::Object;
79 10         6863 my ($pre, $suc, @s) = (Set::Object->new(map $_->[0], @p), Set::Object->new(map $_->[1], @p));
80             @rows = ([ 'to:', @s = sort $suc->members ], map {
81 10         83 my $p = $_;
  24         35  
82             [ $p, map {
83 24 100       35 my $text = defined(my $id = $m->has_path([$p, $_])) ? 1 : '';
  93         230  
84 93 100       229 my $attrs = !$text ? undef :
    100          
85             $multi ? $m->[ _attr ][$id] : $m->_get_path_attrs([$p, $_]);
86 93 100       229 defined $attrs ? $m->_dumper($attrs) : $text;
87             } @s ];
88             } sort $pre->members);
89             } else {
90             @rows = map {
91 14 100       27 my $attrs = $multi
  33         190  
92             ? $m->[ _attr ][ $m->has_path($_) ] : $m->_get_path_attrs($_);
93 33 100       73 [ $m->_dumper($_),
94             ($m->get_ids_by_paths([ $_ ], 0))[0].
95             (!defined $attrs ? '' : ",".$m->_dumper($attrs)) ];
96             } @p;
97             }
98 24         194 join '',
99             map "$_\n",
100 24         103 "@{[ref $m]} arity=$arity flags: @{[_stringify_fields($m->[ _f ])]}",
  24         63  
101             map join(' ', map sprintf('%4s', $_), @$_),
102             @rows;
103             }
104              
105             sub _stringify_fields {
106 38 100   38   380 return '0' if !$_[0];
107 30         536 join '|', grep $_[0] & $FLAG2I{$_}, @FLAGS;
108             }
109              
110             sub _dumper {
111 58     58   138 my (undef, $got) = @_;
112 58 100 66     260 return $got if defined $got and !ref $got;
113 31         1391 require Data::Dumper;
114 31         11978 my $dumper = Data::Dumper->new([$got]);
115 31         993 $dumper->Indent(0)->Terse(1);
116 31 50       610 $dumper->Sortkeys(1) if $dumper->can("Sortkeys");
117 31         196 $dumper->Dump;
118             }
119              
120             sub has_any_paths {
121 90     90 1 10910 scalar keys %{ $_[0]->[ _pi ] };
  90         398  
122             }
123              
124             sub _set_path_attr_common {
125 125     125   262 push @_, 0;
126 125         225 my ($i) = &__set_path;
127 125         229 my $attr = (my $m = $_[0])->[ _attr ];
128 125 100       745 ($m->[ _f ] & _MULTI) ? \$attr->[ $i ]{ $_[2] } : \$attr->[ $i ];
129             }
130              
131             sub _set_path_attrs {
132 1129     1129   7453 ${ &{ $_[0]->can('_set_path_attr_common') } } = $_[-1];
  1129         1214  
  1129         3091  
133             }
134              
135             sub _set_path_attr {
136 4714     4714   12510 ${ &{ $_[0]->can('_set_path_attr_common') } }->{ $_[-2] } = $_[-1];
  4714         5015  
  4714         13563  
137             }
138              
139             sub set_paths {
140 333     333 1 993 map +($_[0]->__set_path($_, 1))[0], @_[1..$#_];
141             }
142              
143             sub set_path_by_multi_id {
144 112     112 1 207 push @_, 1;
145 112         325 goto &__set_path;
146             }
147              
148             sub __set_path {
149 556     556   823 my $inc_if_exists = pop;
150 556         1025 &__arg;
151 556         725 my ($f, $a, $map_i, $pi, $map_s, $map_p, $m, $k, $id) = (@{ $_[0] }[ _f, _arity, _i, _pi, _s, _p ], @_);
  556         1262  
152 556         793 my $is_multi = $f & _MULTI;
153 556         664 my $k_orig = $k;
154 556 100 100     1773 $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k);
      100        
155 556 100 100     1970 my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
156 556 100       1122 if (exists $pi->{ $l }) {
157 189 100 100     702 return ($pi->{ $l }) if !($inc_if_exists and ($f & _COUNTMULTI));
158 53         173 my $nc = \$m->[ _count ][ my $i = $pi->{ $l } ];
159 53 100       180 $$nc++, return ($i) if !$is_multi;
160 32         53 my $na = $m->[ _attr ][ $i ];
161 32 100       66 if ($id eq _GEN_ID) {
162 17         63 $$nc++ while exists $na->{ $$nc };
163 17         34 $id = $$nc;
164             }
165 32         80 $na->{ $id } = { };
166 32         115 return ($i, $id);
167             }
168 367         958 $map_i->[ $pi->{ $l } = my $i = $m->[ _n ]++ ] = $k_orig;
169 367 100       736 $m->[ _attr ][ $i ] = { ($id = ($id eq _GEN_ID) ? 0 : $id) => {} } if $is_multi;
    100          
170 367 100       669 $m->[ _count ][ $i ] = $is_multi ? 0 : 1 if ($f & _COUNTMULTI);
    100          
171 367 100       828 _successors_add($f, $a, $map_s, $map_p, $i, $k) if $map_s; # dereffed
172 367         1217 ($i, $id);
173             }
174              
175             sub _successors_add {
176 117     117   245 my ($f, $a, $map_s, $map_p, $id, $path) = @_;
177 117         333 my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path);
178 117         257 push @{ $map_s->{ $_->[0] }{ $_->[1] } }, $id for @$pairs;
  240         680  
179 117 100       304 return if !$map_p;
180 78         132 push @{ $map_p->{ $_->[1] }{ $_->[0] } }, $id for @$pairs;
  124         376  
181             }
182              
183             sub _successors_del {
184 24     24   65 my ($f, $a, $map_s, $map_p, $id, $path) = @_;
185 24         85 my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path);
186 24         59 for (@$pairs) {
187 61         108 my ($p, $s) = @$_;
188 61         71 my @new = grep $_ != $id, @{ $map_s->{ $p }{ $s } };
  61         159  
189 61 100       120 if (@new) {
190 2         4 $map_s->{ $p }{ $s } = \@new;
191 2 50       5 $map_p->{ $s }{ $p } = \@new if $map_p;
192 2         4 next;
193             }
194 59         102 delete $map_s->{ $p }{ $s };
195 59 100       66 delete $map_s->{ $p } if !keys %{ $map_s->{ $p } };
  59         138  
196 59 100       122 next if !$map_p;
197 27         51 delete $map_p->{ $s }{ $p };
198 27 100       29 delete $map_p->{ $s } if !keys %{ $map_p->{ $s } };
  27         95  
199             }
200             }
201              
202             sub _successors_cartesian {
203 26776     26776   34638 my ($unord, $hyper, $seq) = @_;
204 26776 100 100     65878 return [ $seq ] if !$unord and !$hyper;
205 11869 100 100     30231 return [] if $unord and $hyper and !@$seq;
      100        
206 11864         15801 my ($allow_self, $p_s, $s_s, @pairs);
207 11864 100       16073 if ($unord) {
208 11848         60912 require Set::Object;
209 11848         291478 my @a = Set::Object->new(@$seq)->members;
210 11848         50364 ($allow_self, $p_s, $s_s) = (@a < 2, \@a, \@a);
211             } else {
212 16         33 ($allow_self, $p_s, $s_s) = (1, @$seq);
213             }
214 11864         20797 for my $p (@$p_s) {
215 23479 100       87308 push @pairs, map [$p, $_], $allow_self ? @$s_s : grep $p != $_, @$s_s;
216             }
217 11864         28574 \@pairs;
218             }
219              
220             sub _get_path_count {
221 217 100   217   5300 return 0 unless my ($i) = &__has_path;
222 194         385 my $f = (my $m = $_[0])->[ _f ];
223             return
224             ($f & _COUNT) ? $m->[ _count ][ $i ] :
225 194 100       662 ($f & _MULTI) ? scalar keys %{ $m->[ _attr ][ $i ] } : 1;
  90 100       402  
226             }
227              
228             sub has_path {
229 706     706 1 11448 ( &__has_path )[0];
230             }
231              
232             sub has_path_by_multi_id {
233 268 100   268 1 3055 return undef unless my ($i) = &__has_path;
234 235         853 return exists $_[0]->[ _attr ][ $i ]{ $_[2] };
235             }
236              
237             sub del_path {
238 208 100   208 1 890 return unless my ($i, $l) = &__has_path;
239 207 100 100     376 return 1 if &_is_COUNT and --$_[0][ _count ][ $i ] > 0;
240 195         527 $_[0]->_sequence_del($i, $l);
241 195         313 1;
242             }
243              
244             sub del_path_by_multi_id {
245 17 50   17 1 61 return unless my ($i, $l) = &__has_path;
246 17         46 delete((my $attrs = (my $m = $_[0])->[ _attr ][ $i ])->{ $_[2] });
247 17 100       43 return 1 if keys %$attrs;
248 9         27 $m->_sequence_del($i, $l);
249 9         17 1;
250             }
251              
252             sub get_multi_ids {
253 51 100 66 51 1 177 return unless ((my $m = $_[0])->[ _f ] & _MULTI) and my ($i) = &__has_path;
254 49         72 keys %{ $m->[ _attr ][ $i ] };
  49         233  
255             }
256              
257             sub rename_path {
258 32     32 1 1977 my ($m, $from, $to) = @_;
259 32 50       91 return 1 if $m->[ _arity ] != 1; # all integers, no names
260 32 50       68 return unless my ($i, $l) = $m->__has_path($from);
261 32         57 $m->[ _i ][ $i ] = $to;
262 32 100 66     82 $to = __strval($to, $m->[ _f ]) if ref($to) and ($m->[ _f ] & _REF);
263 32         71 $m->[ _pi ]{ $to } = delete $m->[ _pi ]{ $l };
264 32         79 return 1;
265             }
266              
267             sub _del_path_attrs {
268 38 50   38   90 return unless my ($i) = &__has_path;
269 38         79 my $attr = (my $m = $_[0])->[ _attr ];
270 38 100       139 return $attr->[ $i ]{ $_[2] } = undef, 1 if ($m->[ _f ] & _MULTI);
271 27         85 delete $attr->[ $i ];
272             }
273              
274             sub __has_path {
275 1892     1892   3787 &__arg;
276 1892         2265 my ($f, $a, $pi, $k) = (@{ $_[0] }[ _f, _arity, _pi ], $_[1]);
  1892         3199  
277 1892 100 100     6652 $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k);
      100        
278 1892 100 100     5742 my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
279 1892         2676 my $id = $pi->{ $l };
280 1892 100       6491 (defined $id ? $id : return, $l);
281             }
282              
283             sub _get_path_attrs {
284 355 100   355   2292 return unless my ($i) = &__has_path;
285 346         647 my $attrs = (my $m = $_[0])->[ _attr ][ $i ];
286 346 100       1918 ($m->[ _f ] & _MULTI) ? $attrs->{ $_[2] } : $attrs;
287             }
288              
289             sub _has_path_attrs {
290 82 100   82   5937 keys %{ &{ $_[0]->can('_get_path_attrs') } || return undef } ? 1 : 0;
  82 100       117  
  82         325  
291             }
292              
293             sub _has_path_attr {
294 62   100 62   120 exists(( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] });
295             }
296              
297             sub _get_path_attr {
298 11429   100 11429   13483 ( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] };
299             }
300              
301             sub _get_path_attr_names {
302 78 100   78   175 keys %{ &{ $_[0]->can('_get_path_attrs') } || return };
  78         102  
  78         265  
303             }
304              
305             sub _get_path_attr_values {
306 26 100   26   49 values %{ &{ $_[0]->can('_get_path_attrs') } || return };
  26         46  
  26         145  
307             }
308              
309             sub _del_path_attr {
310 40 100   40   9072 return unless my $attrs = &{ $_[0]->can('_get_path_attrs') };
  40         152  
311 36 50       111 return 0 unless exists $attrs->{ my $attr = $_[-1] };
312 36         71 delete $attrs->{$attr};
313 36 100       121 return 1 if keys %$attrs;
314 15         27 &{ $_[0]->can('_del_path_attrs') };
  15         79  
315 15         36 1;
316             }
317              
318             sub _sequence_del {
319 204     204   360 my ($m, $id, $l) = @_;
320 204         412 my ($f, $a, $map_i, $pi, $map_s, $map_p) = @$m[ _f, _arity, _i, _pi, _s, _p ];
321 204         363 delete $pi->{ $l };
322 204         525 delete $m->[ $_ ][ $id ] for _count, _attr;
323 204         306 my $path = delete $map_i->[ $id ];
324 204 100       455 _successors_del($f, $a, $map_s, $map_p, $id, $path) if $map_s;
325 204         298 return 1;
326             }
327              
328             sub get_paths_by_ids {
329 23452     23452 1 30833 my ($i, undef, $list, $deep) = ( @{ $_[0] }[ _i ], @_ );
  23452         43785  
330 23452 100       150720 $deep ? map [ map [ @$i[ @$_ ] ], @$_ ], @$list : map [ @$i[ @$_ ] ], @$list;
331             }
332              
333             sub paths {
334 3917 50   3917 1 4940 grep defined, @{ $_[0]->[ _i ] || Graph::_empty_array() };
  3917         27568  
335             }
336              
337             sub get_ids_by_paths {
338 1230     1230 1 13592 my ($f, $a, $pi, $m, $list, $ensure, $deep) = ( @{ $_[0] }[ _f, _arity, _pi ], @_ );
  1230         2676  
339 1230   100     3801 $deep ||= 0;
340 1230         3328 my ($is_multi, $is_ref, $is_unord) = (map $f & $_, _MULTI, _REF, _UNORD);
341             return map { # Fast path
342 1230 100 100     3908 my @ret = map {
      100        
343 121 100       234 my $id = $pi->{ $a != 1 ? "@$_" : $_ };
  154 100       296  
344 154 100       391 defined $id ? $id :
    100          
    100          
345             !$ensure ? return :
346             ($is_multi ? $m->set_path_by_multi_id($_, _GEN_ID) : $m->set_paths($_))[0];
347             } $deep ? @$_ : $_;
348 108 100       371 $deep ? \@ret : @ret;
349             } @$list if $a and !$is_ref and $deep < 2;
350             map {
351 1106         1475 my @ret = map {
352 1165 100       1837 my @ret2 = map {
353 1276 100       1828 my $k = $_;
  1289         1626  
354 1289 100 100     4696 $k = __strval($k, $f) if $a == 1 && $is_ref && ref($k);
      100        
355 1289 100 100     3379 my $l = ($a == 0 && !$is_unord) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
356 1289         1733 my $id = $pi->{ $l };
357 1289 50       3547 defined $id ? $id :
    100          
    100          
358             !$ensure ? return :
359             ($is_multi ? $m->set_path_by_multi_id($_, _GEN_ID) : $m->set_paths($_))[0];
360             } $deep > 1 ? @$_ : $_;
361 1115 100       2251 $deep > 1 ? \@ret2 : @ret2;
362             } $deep ? @$_ : $_;
363 1004 100       3020 $deep ? \@ret : @ret;
364             } @$list;
365             }
366              
367             sub _paths_fromto {
368 46     46   69 my $offset = pop;
369 46         65 my ($i, $map_x, @v) = ( @{ $_[0] }[ _i, $offset ], @_[1..$#_] );
  46         145  
370 46 50       159 Graph::__carp_confess("undefined vertex") if grep !defined, @v;
371 46         222 require Set::Object;
372 46 100       85 map $i->[ $_ ], Set::Object->new(map @$_, map values %{ $map_x->{ $_ } || _empty }, @v)->members;
  52         680  
373             }
374 31     31 1 6037 sub paths_from { push @_, _s; goto &_paths_fromto }
  31         95  
375 15     15 1 2926 sub paths_to { push @_, _p; goto &_paths_fromto }
  15         46  
376              
377             sub _cessors {
378 181     181   237 my $offset = pop;
379 181         228 my ($map_x, @v) = ( @{ $_[0] }[ $offset ], @_[1..$#_] );
  181         481  
380 181 50       449 Graph::__carp_confess("undefined vertex") if grep !defined, @v;
381 181         1198 require Set::Object;
382 181 100       6803 Set::Object->new(map keys %{ $map_x->{ $_ } || _empty }, @v)->members;
  187         1261  
383             }
384 140     140 1 4965 sub successors { push @_, _s; goto &_cessors }
  140         306  
385 41     41 1 3735 sub predecessors { push @_, _p; goto &_cessors }
  41         104  
386              
387             sub has_successor {
388 72     72 1 6720 my ($map_s, $u, $v) = ( @{ $_[0] }[ _s ], @_[1, 2] );
  72         184  
389 72 50       245 Graph::__carp_confess("undefined vertex") if grep !defined, $u, $v;
390 72 100       98 exists ${ $map_s->{ $u } || _empty }{ $v };
  72         298  
391             }
392              
393             sub __strval {
394 2079     2079   2827 my ($k, $f) = @_;
395 2079 50 33     5009 return $k unless ref $k && ($f & _REF);
396 2079 100       3168 return "$k" if ($f & _STR);
397 2075         7713 require Scalar::Util;
398 2075         4180 Scalar::Util::refaddr($k);
399             }
400              
401             sub __arg {
402 2448     2448   2874 my ($f, $a, $m, $k) = (@{ $_[0] }[ _f, _arity ], @_[0, 1]);
  2448         5149  
403 2448 50 66     6616 Graph::__carp_confess(sprintf "arguments %d (%s) expected %d for\n".$m->stringify,
404             scalar @$k, "@$k", $a)
405             if $a > 1 and @$k != $a;
406             }
407              
408             sub reindex {
409 3     3 1 487 my ($f, $a, $i2p, $m) = (@{ $_[0] }[ _f, _arity, _i ], $_[0]);
  3         13  
410 3   33     19 my $is_ref = $a == 1 && ($f & _REF);
411 3         9 my $pi = $m->[ _pi ] = {};
412 3         6 for my $i ( 0..$#{ $i2p } ) {
  3         11  
413 4 50       13 next if !defined(my $k = $i2p->[ $i ]); # deleted
414 4 50 33     23 $k = __strval($k, $f) if $is_ref && ref($k);
415 4         15 $pi->{ $k } = $i;
416             }
417             }
418              
419             1;
420             __END__