File Coverage

blib/lib/Graph/AdjacencyMap.pm
Criterion Covered Total %
statement 288 290 99.3
branch 175 192 91.1
condition 71 81 87.6
subroutine 58 58 100.0
pod 20 20 100.0
total 612 641 95.4


line stmt bran cond sub pod time code
1             package Graph::AdjacencyMap;
2              
3 84     93828   550 use strict;
  84         218  
  84         2985  
4 84     84   392 use warnings;
  84         163  
  84         21692  
5              
6             # $SIG{__DIE__ } = \&Graph::__carp_confess;
7             # $SIG{__WARN__} = \&Graph::__carp_confess;
8              
9             my $empty = {};
10 90     90   859 sub _empty () { $empty }
11              
12             my (@FLAGS, %FLAG_COMBOS, %FLAG2I, @FIELDS);
13             BEGIN {
14 84     84   439 @FLAGS = qw(_COUNT _MULTI _UNORD _REF _UNIONFIND _LIGHT _STR);
15 84         441 %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 84         371 @FIELDS = qw(_n _f _arity _i _pi _s _p _attr _count);
23 84         389 for my $i (0..$#FLAGS) {
24 588         1130 my $n = $FLAGS[$i];
25 588         913 my $f = 1 << $i;
26 588         1409 $FLAG2I{$n} = $f;
27 84     84   563 no strict 'refs';
  84         170  
  84         15322  
28 588         4409 *$n = sub () { $f };
  0         0  
29 588     147357   1601 *{"_is$n"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f
  588     146731   2489  
  147357         940017  
30             }
31 84         359 for my $k (keys %FLAG_COMBOS) {
32 168         298 my $f = 0;
33 168         307 $f |= $_ for map $FLAG2I{$_}, @{ $FLAG_COMBOS{$k} };
  168         842  
34 84     84   630 no strict 'refs';
  84         222  
  84         13684  
35 168     2826   2802 *$k = sub () { return $f }; # return to dodge pointless 5.22 stricture
  2826         18645  
36 168     1   542 *{"_is$k"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f
  168         723  
  1         9  
37             }
38 84         445 for my $i (0..$#FIELDS) {
39 84     84   714 no strict 'refs';
  84         260  
  84         8272  
40 756         3141 *{ $FIELDS[$i] }= sub () { $i };
  756         14901  
  0         0  
41             }
42             }
43              
44             sub _new {
45 4580     4580   13540 my ($class, $flags, $arity) = @_;
46 4580         8772 my $hyper = !$arity;
47 4580         9933 my $need_s = $arity != 1;
48 4580   100     14395 my $need_p = $need_s && !($flags & _UNORD);
49 4580 100       29645 bless [
    100          
50             0, $flags, $arity, [], {},
51             ($need_s ? {} : undef), ($need_p ? {} : undef),
52             [], [],
53             ], $class;
54             }
55              
56             require Exporter;
57 84     84   699 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
  84         267  
  84         445735  
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 299     299   1351 sub _GEN_ID () { \$_GEN_ID }
67              
68             sub stringify {
69 24     24 1 5284 my ($f, $arity, $m) = (@{ $_[0] }[ _f, _arity ], $_[0]);
  24         72  
70 24         58 my ($multi, @rows) = $f & _MULTI;
71 24         63 my @p = $m->paths;
72             @p = $arity == 1 ? sort @p :
73 24 100 100     265 map $_->[0], sort { $a->[1] cmp $b->[1] }
  138 100       145  
74             ($arity == 0 && !($f & _UNORD))
75             ? map [$_, join '|', map "@$_", @$_], @p
76             : map [$_,"@$_"], @p; # use the Schwartz
77 24 100       71 if ($arity == 2) {
78 10         797 require Set::Object;
79 10         7366 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         108 my $p = $_;
  24         34  
82             [ $p, map {
83 24 100       32 my $text = defined(my $id = $m->has_path([$p, $_])) ? 1 : '';
  93         206  
84 93 100       198 my $attrs = !$text ? undef :
    100          
85             $multi ? $m->[ _attr ][$id] : $m->_get_path_attrs([$p, $_]);
86 93 100       281 defined $attrs ? $m->_dumper($attrs) : $text;
87             } @s ];
88             } sort $pre->members);
89             } else {
90             @rows = map {
91 14 100       24 my $attrs = $multi
  33         183  
92             ? $m->[ _attr ][ $m->has_path($_) ] : $m->_get_path_attrs($_);
93 33 100       71 [ $m->_dumper($_),
94             ($m->get_ids_by_paths([ $_ ], 0))[0].
95             (!defined $attrs ? '' : ",".$m->_dumper($attrs)) ];
96             } @p;
97             }
98 24         203 join '',
99             map "$_\n",
100 24         73 "@{[ref $m]} arity=$arity flags: @{[_stringify_fields($m->[ _f ])]}",
  24         83  
101             map join(' ', map sprintf('%4s', $_), @$_),
102             @rows;
103             }
104              
105             sub _stringify_fields {
106 38 100   38   390 return '0' if !$_[0];
107 30         592 join '|', grep $_[0] & $FLAG2I{$_}, @FLAGS;
108             }
109              
110             sub _dumper {
111 58     58   150 my (undef, $got) = @_;
112 58 100 66     244 return $got if defined $got and !ref $got;
113 31         1519 require Data::Dumper;
114 31         13445 my $dumper = Data::Dumper->new([$got]);
115 31         1129 $dumper->Indent(0)->Terse(1);
116 31 50       668 $dumper->Sortkeys(1) if $dumper->can("Sortkeys");
117 31         213 $dumper->Dump;
118             }
119              
120             sub has_any_paths {
121 90     90 1 18205 scalar keys %{ $_[0]->[ _pi ] };
  90         471  
122             }
123              
124             sub _set_path_attr_common {
125 162     162   419 push @_, 0;
126 162         1445 my ($i) = &__set_path;
127 162         321 my $attr = (my $m = $_[0])->[ _attr ];
128 162 100       1350 ($m->[ _f ] & _MULTI) ? \$attr->[ $i ]{ $_[2] } : \$attr->[ $i ];
129             }
130              
131             sub _set_path_attrs {
132 1170     1170   7719 ${ &{ $_[0]->can('_set_path_attr_common') } } = $_[-1];
  1170         1600  
  1170         4610  
133             }
134              
135             sub _set_path_attr {
136 4793     4793   15752 ${ &{ $_[0]->can('_set_path_attr_common') } }->{ $_[-2] } = $_[-1];
  4793         8234  
  4793         26223  
137             }
138              
139             sub set_paths {
140 315     315 1 1512 map +($_[0]->__set_path($_, 1))[0], @_[1..$#_];
141             }
142              
143             sub set_path_by_multi_id {
144 219     219 1 528 push @_, 1;
145 219         669 goto &__set_path;
146             }
147              
148             sub __set_path {
149 700     700   1096 my $inc_if_exists = pop;
150 700         1615 &__arg;
151 700         1161 my ($f, $a, $map_i, $pi, $map_s, $map_p, $m, $k, $id) = (@{ $_[0] }[ _f, _arity, _i, _pi, _s, _p ], @_);
  700         2209  
152 700         1126 my $is_multi = $f & _MULTI;
153 700         1067 my $k_orig = $k;
154 700 100 100     3470 $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k);
      100        
155 700 100 100     3216 my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
156 700 100       1734 if (exists $pi->{ $l }) {
157 228 100 100     973 return ($pi->{ $l }) if !($inc_if_exists and ($f & _COUNTMULTI));
158 55         187 my $nc = \$m->[ _count ][ my $i = $pi->{ $l } ];
159 55 100       228 $$nc++, return ($i) if !$is_multi;
160 34         97 my $na = $m->[ _attr ][ $i ];
161 34 100       87 if ($id eq _GEN_ID) {
162 17         102 $$nc++ while exists $na->{ $$nc };
163 17         45 $id = $$nc;
164             }
165 34         133 $na->{ $id } = { };
166 34         209 return ($i, $id);
167             }
168 472         1794 $map_i->[ $pi->{ $l } = my $i = $m->[ _n ]++ ] = $k_orig;
169 472 100       1203 $m->[ _attr ][ $i ] = { ($id = ($id eq _GEN_ID) ? 0 : $id) => {} } if $is_multi;
    100          
170 472 100       1033 $m->[ _count ][ $i ] = $is_multi ? 0 : 1 if ($f & _COUNTMULTI);
    100          
171 472 100       1305 _successors_add($f, $a, $map_s, $map_p, $i, $k) if $map_s; # dereffed
172 472         1782 ($i, $id);
173             }
174              
175             sub _successors_add {
176 174     174   533 my ($f, $a, $map_s, $map_p, $id, $path) = @_;
177 174         585 my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path);
178 174         433 push @{ $map_s->{ $_->[0] }{ $_->[1] } }, $id for @$pairs;
  313         1252  
179 174 100       560 return if !$map_p;
180 119         1046 push @{ $map_p->{ $_->[1] }{ $_->[0] } }, $id for @$pairs;
  165         601  
181             }
182              
183             sub _successors_del {
184 26     26   108 my ($f, $a, $map_s, $map_p, $id, $path) = @_;
185 26         100 my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path);
186 26         81 for (@$pairs) {
187 63         144 my ($p, $s) = @$_;
188 63         80 my @new = grep $_ != $id, @{ $map_s->{ $p }{ $s } };
  63         175  
189 63 100       151 if (@new) {
190 2         7 $map_s->{ $p }{ $s } = \@new;
191 2 50       6 $map_p->{ $s }{ $p } = \@new if $map_p;
192 2         9 next;
193             }
194 61         110 delete $map_s->{ $p }{ $s };
195 61 100       83 delete $map_s->{ $p } if !keys %{ $map_s->{ $p } };
  61         271  
196 61 100       214 next if !$map_p;
197 29         62 delete $map_p->{ $s }{ $p };
198 29 100       86 delete $map_p->{ $s } if !keys %{ $map_p->{ $s } };
  29         129  
199             }
200             }
201              
202             sub _successors_cartesian {
203 52779     52779   105137 my ($unord, $hyper, $seq) = @_;
204 52779 100 100     142305 return [ $seq ] if !$unord and !$hyper;
205 37084 100 100     129563 return [] if $unord and $hyper and !@$seq;
      100        
206 37079         57110 my ($allow_self, $p_s, $s_s, @pairs);
207 37079 100       67062 if ($unord) {
208 37063         205581 require Set::Object;
209 37063         668078 my @a = Set::Object->new(@$seq)->members;
210 37063         203026 ($allow_self, $p_s, $s_s) = (@a < 2, \@a, \@a);
211             } else {
212 16         38 ($allow_self, $p_s, $s_s) = (1, @$seq);
213             }
214 37079         82373 for my $p (@$p_s) {
215 73908 100       366737 push @pairs, map [$p, $_], $allow_self ? @$s_s : grep $p != $_, @$s_s;
216             }
217 37079         127379 \@pairs;
218             }
219              
220             sub _get_path_count {
221 420 100   420   9571 return 0 unless my ($i) = &__has_path;
222 397         902 my $f = (my $m = $_[0])->[ _f ];
223             return
224             ($f & _COUNT) ? $m->[ _count ][ $i ] :
225 397 100       1486 ($f & _MULTI) ? scalar keys %{ $m->[ _attr ][ $i ] } : 1;
  293 100       1683  
226             }
227              
228             sub has_path {
229 778     778 1 15787 ( &__has_path )[0];
230             }
231              
232             sub has_path_by_multi_id {
233 549 100   549 1 3805 return undef unless my ($i) = &__has_path;
234 454         10192 return exists $_[0]->[ _attr ][ $i ]{ $_[2] };
235             }
236              
237             sub del_path {
238 209 100   209 1 7332 return unless my ($i, $l) = &__has_path;
239 208 100 100     560 return 1 if &_is_COUNT and --$_[0][ _count ][ $i ] > 0;
240 196         858 $_[0]->_sequence_del($i, $l);
241 196         518 1;
242             }
243              
244             sub del_path_by_multi_id {
245 22 50   22 1 1132 return unless my ($i, $l) = &__has_path;
246 22         91 delete((my $attrs = (my $m = $_[0])->[ _attr ][ $i ])->{ $_[2] });
247 22 100       68 return 1 if keys %$attrs;
248 13         75 $m->_sequence_del($i, $l);
249 13         31 1;
250             }
251              
252             sub get_multi_ids {
253 311 100 66 311 1 1146 return unless ((my $m = $_[0])->[ _f ] & _MULTI) and my ($i) = &__has_path;
254 309         584 keys %{ $m->[ _attr ][ $i ] };
  309         14484  
255             }
256              
257             sub rename_path {
258 32     32 1 2655 my ($m, $from, $to) = @_;
259 32 50       94 return 1 if $m->[ _arity ] != 1; # all integers, no names
260 32 50       90 return unless my ($i, $l) = $m->__has_path($from);
261 32         70 $m->[ _i ][ $i ] = $to;
262 32 100 66     92 $to = __strval($to, $m->[ _f ]) if ref($to) and ($m->[ _f ] & _REF);
263 32         98 $m->[ _pi ]{ $to } = delete $m->[ _pi ]{ $l };
264 32         85 return 1;
265             }
266              
267             sub _del_path_attrs {
268 38 50   38   82 return unless my ($i) = &__has_path;
269 38         90 my $attr = (my $m = $_[0])->[ _attr ];
270 38 100       137 return $attr->[ $i ]{ $_[2] } = undef, 1 if ($m->[ _f ] & _MULTI);
271 27         87 delete $attr->[ $i ];
272             }
273              
274             sub __has_path {
275 2923     2923   6896 &__arg;
276 2923         3946 my ($f, $a, $pi, $k) = (@{ $_[0] }[ _f, _arity, _pi ], $_[1]);
  2923         6041  
277 2923 100 100     11657 $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k);
      100        
278 2923 100 100     11421 my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
279 2923         5475 my $id = $pi->{ $l };
280 2923 100       12851 (defined $id ? $id : return, $l);
281             }
282              
283             sub _get_path_attrs {
284 564 100   564   2746 return unless my ($i) = &__has_path;
285 555         1165 my $attrs = (my $m = $_[0])->[ _attr ][ $i ];
286 555 100       7767 ($m->[ _f ] & _MULTI) ? $attrs->{ $_[2] } : $attrs;
287             }
288              
289             sub _has_path_attrs {
290 82 100   82   6571 keys %{ &{ $_[0]->can('_get_path_attrs') } || return undef } ? 1 : 0;
  82 100       115  
  82         383  
291             }
292              
293             sub _has_path_attr {
294 62   100 62   143 exists(( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] });
295             }
296              
297             sub _get_path_attr {
298 11664   100 11664   17539 ( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] };
299             }
300              
301             sub _get_path_attr_names {
302 78 100   78   215 keys %{ &{ $_[0]->can('_get_path_attrs') } || return };
  78         99  
  78         267  
303             }
304              
305             sub _get_path_attr_values {
306 26 100   26   56 values %{ &{ $_[0]->can('_get_path_attrs') } || return };
  26         139  
  26         168  
307             }
308              
309             sub _del_path_attr {
310 40 100   40   8755 return unless my $attrs = &{ $_[0]->can('_get_path_attrs') };
  40         209  
311 36 50       142 return 0 unless exists $attrs->{ my $attr = $_[-1] };
312 36         66 delete $attrs->{$attr};
313 36 100       135 return 1 if keys %$attrs;
314 15         24 &{ $_[0]->can('_del_path_attrs') };
  15         77  
315 15         36 1;
316             }
317              
318             sub _sequence_del {
319 209     209   478 my ($m, $id, $l) = @_;
320 209         701 my ($f, $a, $map_i, $pi, $map_s, $map_p) = @$m[ _f, _arity, _i, _pi, _s, _p ];
321 209         436 delete $pi->{ $l };
322 209         765 delete $m->[ $_ ][ $id ] for _count, _attr;
323 209         378 my $path = delete $map_i->[ $id ];
324 209 100       592 _successors_del($f, $a, $map_s, $map_p, $id, $path) if $map_s;
325 209         372 return 1;
326             }
327              
328             sub get_paths_by_ids {
329 17871     17871 1 33129 my ($i, undef, $list, $deep) = ( @{ $_[0] }[ _i ], @_ );
  17871         45159  
330 17871 100       387691 $deep ? map [ map [ @$i[ @$_ ] ], @$_ ], @$list : map [ @$i[ @$_ ] ], @$list;
331             }
332              
333             sub paths {
334 4893 50   4893 1 7461 grep defined, @{ $_[0]->[ _i ] || Graph::_empty_array() };
  4893         40208  
335             }
336              
337             sub ids {
338 302 50   302 1 601 values %{ $_[0]->[ _pi ] || Graph::_empty_array() };
  302         4309  
339             }
340              
341             sub get_ids_by_paths {
342 1194     1194 1 20065 my ($f, $a, $pi, $m, $list, $ensure, $deep) = ( @{ $_[0] }[ _f, _arity, _pi ], @_ );
  1194         2958  
343 1194   100     4162 $deep ||= 0;
344 1194         3850 my ($is_multi, $is_ref, $is_unord) = (map $f & $_, _MULTI, _REF, _UNORD);
345             return map { # Fast path
346 1194 100 100     4913 my @ret = map {
      100        
347 183 100       467 my $id = $pi->{ $a != 1 ? "@$_" : $_ };
  223 100       616  
348 223 100       756 defined $id ? $id :
    100          
    100          
349             !$ensure ? return :
350             ($is_multi ? $m->set_path_by_multi_id($_, _GEN_ID) : $m->set_paths($_))[0];
351             } $deep ? @$_ : $_;
352 170 100       742 $deep ? \@ret : @ret;
353             } @$list if $a and !$is_ref and $deep < 2;
354             map {
355 1020         1450 my @ret = map {
356 1166 100       1903 my @ret2 = map {
357 1277 100       2256 my $k = $_;
  1290         1794  
358 1290 100 100     5595 $k = __strval($k, $f) if $a == 1 && $is_ref && ref($k);
      100        
359 1290 100 100     3595 my $l = ($a == 0 && !$is_unord) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
360 1290         1877 my $id = $pi->{ $l };
361 1290 50       5178 defined $id ? $id :
    100          
    100          
362             !$ensure ? return :
363             ($is_multi ? $m->set_path_by_multi_id($_, _GEN_ID) : $m->set_paths($_))[0];
364             } $deep > 1 ? @$_ : $_;
365 1116 100       2385 $deep > 1 ? \@ret2 : @ret2;
366             } $deep ? @$_ : $_;
367 1005 100       4029 $deep ? \@ret : @ret;
368             } @$list;
369             }
370              
371             sub _paths_fromto {
372 44     44   79 my $offset = pop;
373 44         83 my ($i, $map_x, @v) = ( @{ $_[0] }[ _i, $offset ], @_[1..$#_] );
  44         173  
374 44 50       177 Graph::__carp_confess("undefined vertex") if grep !defined, @v;
375 44         248 require Set::Object;
376 44 100       91 map $i->[ $_ ], Set::Object->new(map @$_, map values %{ $map_x->{ $_ } || _empty }, @v)->members;
  50         604  
377             }
378 27     27 1 14142 sub paths_from { push @_, _s; goto &_paths_fromto }
  27         97  
379 17     17 1 3151 sub paths_to { push @_, _p; goto &_paths_fromto }
  17         51  
380              
381             sub _cessors {
382 234     234   424 my $offset = pop;
383 234         404 my ($map_x, @v) = ( @{ $_[0] }[ $offset ], @_[1..$#_] );
  234         807  
384 234 50       721 Graph::__carp_confess("undefined vertex") if grep !defined, @v;
385 234         2085 require Set::Object;
386 234 100       18751 Set::Object->new(map keys %{ $map_x->{ $_ } || _empty }, @v)->members;
  240         2446  
387             }
388 169     169 1 5999 sub successors { push @_, _s; goto &_cessors }
  169         530  
389 65     65 1 3834 sub predecessors { push @_, _p; goto &_cessors }
  65         255  
390              
391             sub has_successor {
392 72     72 1 7628 my ($map_s, $u, $v) = ( @{ $_[0] }[ _s ], @_[1, 2] );
  72         208  
393 72 50       241 Graph::__carp_confess("undefined vertex") if grep !defined, $u, $v;
394 72 100       87 exists ${ $map_s->{ $u } || _empty }{ $v };
  72         346  
395             }
396              
397             sub __strval {
398 2151     2151   3827 my ($k, $f) = @_;
399 2151 50 33     5569 return $k unless ref $k && ($f & _REF);
400 2151 100       3307 return "$k" if ($f & _STR);
401 2147         8643 require Scalar::Util;
402 2147         3582 Scalar::Util::refaddr($k);
403             }
404              
405             sub __arg {
406 3623     3623   5122 my ($f, $a, $m, $k) = (@{ $_[0] }[ _f, _arity ], @_[0, 1]);
  3623         9635  
407 3623 50 66     11952 Graph::__carp_confess(sprintf "arguments %d (%s) expected %d for\n".$m->stringify,
408             scalar @$k, "@$k", $a)
409             if $a > 1 and @$k != $a;
410             }
411              
412             sub reindex {
413 3     3 1 592 my ($f, $a, $i2p, $m) = (@{ $_[0] }[ _f, _arity, _i ], $_[0]);
  3         11  
414 3   33     18 my $is_ref = $a == 1 && ($f & _REF);
415 3         11 my $pi = $m->[ _pi ] = {};
416 3         5 for my $i ( 0..$#{ $i2p } ) {
  3         9  
417 4 50       12 next if !defined(my $k = $i2p->[ $i ]); # deleted
418 4 50 33     26 $k = __strval($k, $f) if $is_ref && ref($k);
419 4         16 $pi->{ $k } = $i;
420             }
421             }
422              
423             1;
424             __END__