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 59 98.3
pod 20 20 100.0
total 612 642 95.3


line stmt bran cond sub pod time code
1             package Graph::AdjacencyMap;
2              
3 80     52791   558 use strict;
  80         167  
  80         2419  
4 80     80   390 use warnings;
  80         174  
  80         12188  
5              
6             # $SIG{__DIE__ } = \&Graph::__carp_confess;
7             # $SIG{__WARN__} = \&Graph::__carp_confess;
8              
9             my $empty = {};
10 82     82   599 sub _empty () { $empty }
11              
12             my (@FLAGS, %FLAG_COMBOS, %FLAG2I, @FIELDS);
13             BEGIN {
14 80     80   985 @FLAGS = qw(_COUNT _MULTI _UNORD _REF _UNIONFIND _LIGHT _STR);
15 80         481 %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         283 @FIELDS = qw(_n _f _arity _i _pi _s _p _attr _count);
23 80         438 for my $i (0..$#FLAGS) {
24 560         1138 my $n = $FLAGS[$i];
25 560         898 my $f = 1 << $i;
26 560         1248 $FLAG2I{$n} = $f;
27 80     80   652 no strict 'refs';
  80         178  
  80         13463  
28 560         4451 *$n = sub () { $f };
  0         0  
29 560     71030   2659 *{"_is$n"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f
  560     67958   3170  
  71030         228294  
30             }
31 80         375 for my $k (keys %FLAG_COMBOS) {
32 160         307 my $f = 0;
33 160         283 $f |= $_ for map $FLAG2I{$_}, @{ $FLAG_COMBOS{$k} };
  160         877  
34 80     80   647 no strict 'refs';
  80         1977  
  80         9472  
35 160     1328   1074 *$k = sub () { return $f }; # return to dodge pointless 5.22 stricture
  1328         4265  
36 160     1   622 *{"_is$k"} = sub { $_[0]->[ 1 ] & $f }; # 1 = _f
  160     0   939  
  1         5  
37             }
38 80         366 for my $i (0..$#FIELDS) {
39 80     80   679 no strict 'refs';
  80         275  
  80         6131  
40 720         3478 *{ $FIELDS[$i] }= sub () { $i };
  720         14179  
  0         0  
41             }
42             }
43              
44             sub _new {
45 1798     1798   5453 my ($class, $flags, $arity) = @_;
46 1798         2802 my $hyper = !$arity;
47 1798         2840 my $need_s = $arity != 1;
48 1798   100     5226 my $need_p = $need_s && !($flags & _UNORD);
49 1798 100       10104 bless [
    100          
50             0, $flags, $arity, [], {},
51             ($need_s ? {} : undef), ($need_p ? {} : undef),
52             [], [],
53             ], $class;
54             }
55              
56             require Exporter;
57 80     80   661 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
  80         239  
  80         373319  
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   677 sub _GEN_ID () { \$_GEN_ID }
67              
68             sub stringify {
69 24     24 1 5595 my ($f, $arity, $m) = (@{ $_[0] }[ _f, _arity ], $_[0]);
  24         84  
70 24         61 my ($multi, @rows) = $f & _MULTI;
71 24         56 my @p = $m->paths;
72             @p = $arity == 1 ? sort @p :
73 24 100 100     305 map $_->[0], sort { $a->[1] cmp $b->[1] }
  138 100       198  
74             ($arity == 0 && !($f & _UNORD))
75             ? map [$_, join '|', map "@$_", @$_], @p
76             : map [$_,"@$_"], @p; # use the Schwartz
77 24 100       88 if ($arity == 2) {
78 10         727 require Set::Object;
79 10         8788 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         107 my $p = $_;
  24         36  
82             [ $p, map {
83 24 100       47 my $text = defined(my $id = $m->has_path([$p, $_])) ? 1 : '';
  93         278  
84 93 100       274 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       32 my $attrs = $multi
  33         214  
92             ? $m->[ _attr ][ $m->has_path($_) ] : $m->_get_path_attrs($_);
93 33 100       89 [ $m->_dumper($_),
94             ($m->get_ids_by_paths([ $_ ], 0))[0].
95             (!defined $attrs ? '' : ",".$m->_dumper($attrs)) ];
96             } @p;
97             }
98 24         239 join '',
99             map "$_\n",
100 24         124 "@{[ref $m]} arity=$arity flags: @{[_stringify_fields($m->[ _f ])]}",
  24         65  
101             map join(' ', map sprintf('%4s', $_), @$_),
102             @rows;
103             }
104              
105             sub _stringify_fields {
106 38 100   38   383 return '0' if !$_[0];
107 30         586 join '|', grep $_[0] & $FLAG2I{$_}, @FLAGS;
108             }
109              
110             sub _dumper {
111 58     58   141 my (undef, $got) = @_;
112 58 100 66     311 return $got if defined $got and !ref $got;
113 31         1675 require Data::Dumper;
114 31         14724 my $dumper = Data::Dumper->new([$got]);
115 31         945 $dumper->Indent(0)->Terse(1);
116 31 50       660 $dumper->Sortkeys(1) if $dumper->can("Sortkeys");
117 31         183 $dumper->Dump;
118             }
119              
120             sub has_any_paths {
121 90     90 1 15467 scalar keys %{ $_[0]->[ _pi ] };
  90         436  
122             }
123              
124             sub _set_path_attr_common {
125 125     125   277 push @_, 0;
126 125         228 my ($i) = &__set_path;
127 125         245 my $attr = (my $m = $_[0])->[ _attr ];
128 125 100       847 ($m->[ _f ] & _MULTI) ? \$attr->[ $i ]{ $_[2] } : \$attr->[ $i ];
129             }
130              
131             sub _set_path_attrs {
132 1128     1128   9937 ${ &{ $_[0]->can('_set_path_attr_common') } } = $_[-1];
  1128         1442  
  1128         3364  
133             }
134              
135             sub _set_path_attr {
136 4751     4751   15576 ${ &{ $_[0]->can('_set_path_attr_common') } }->{ $_[-2] } = $_[-1];
  4751         6343  
  4751         16122  
137             }
138              
139             sub set_paths {
140 333     333 1 1188 map +($_[0]->__set_path($_, 1))[0], @_[1..$#_];
141             }
142              
143             sub set_path_by_multi_id {
144 112     112 1 244 push @_, 1;
145 112         309 goto &__set_path;
146             }
147              
148             sub __set_path {
149 556     556   832 my $inc_if_exists = pop;
150 556         1275 &__arg;
151 556         929 my ($f, $a, $map_i, $pi, $map_s, $map_p, $m, $k, $id) = (@{ $_[0] }[ _f, _arity, _i, _pi, _s, _p ], @_);
  556         1458  
152 556         926 my $is_multi = $f & _MULTI;
153 556         791 my $k_orig = $k;
154 556 100 100     2149 $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k);
      100        
155 556 100 100     2236 my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
156 556 100       1315 if (exists $pi->{ $l }) {
157 189 100 100     769 return ($pi->{ $l }) if !($inc_if_exists and ($f & _COUNTMULTI));
158 53         193 my $nc = \$m->[ _count ][ my $i = $pi->{ $l } ];
159 53 100       218 $$nc++, return ($i) if !$is_multi;
160 32         70 my $na = $m->[ _attr ][ $i ];
161 32 100       71 if ($id eq _GEN_ID) {
162 17         112 $$nc++ while exists $na->{ $$nc };
163 17         42 $id = $$nc;
164             }
165 32         84 $na->{ $id } = { };
166 32         140 return ($i, $id);
167             }
168 367         1176 $map_i->[ $pi->{ $l } = my $i = $m->[ _n ]++ ] = $k_orig;
169 367 100       797 $m->[ _attr ][ $i ] = { ($id = ($id eq _GEN_ID) ? 0 : $id) => {} } if $is_multi;
    100          
170 367 100       668 $m->[ _count ][ $i ] = $is_multi ? 0 : 1 if ($f & _COUNTMULTI);
    100          
171 367 100       984 _successors_add($f, $a, $map_s, $map_p, $i, $k) if $map_s; # dereffed
172 367         1418 ($i, $id);
173             }
174              
175             sub _successors_add {
176 117     117   275 my ($f, $a, $map_s, $map_p, $id, $path) = @_;
177 117         356 my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path);
178 117         348 push @{ $map_s->{ $_->[0] }{ $_->[1] } }, $id for @$pairs;
  240         770  
179 117 100       337 return if !$map_p;
180 78         151 push @{ $map_p->{ $_->[1] }{ $_->[0] } }, $id for @$pairs;
  124         473  
181             }
182              
183             sub _successors_del {
184 24     24   64 my ($f, $a, $map_s, $map_p, $id, $path) = @_;
185 24         73 my $pairs = _successors_cartesian(($f & _UNORD), $a == 0, $path);
186 24         106 for (@$pairs) {
187 61         129 my ($p, $s) = @$_;
188 61         90 my @new = grep $_ != $id, @{ $map_s->{ $p }{ $s } };
  61         177  
189 61 100       140 if (@new) {
190 2         5 $map_s->{ $p }{ $s } = \@new;
191 2 50       5 $map_p->{ $s }{ $p } = \@new if $map_p;
192 2         5 next;
193             }
194 59         128 delete $map_s->{ $p }{ $s };
195 59 100       82 delete $map_s->{ $p } if !keys %{ $map_s->{ $p } };
  59         165  
196 59 100       160 next if !$map_p;
197 27         64 delete $map_p->{ $s }{ $p };
198 27 100       40 delete $map_p->{ $s } if !keys %{ $map_p->{ $s } };
  27         116  
199             }
200             }
201              
202             sub _successors_cartesian {
203 27040     27040   42181 my ($unord, $hyper, $seq) = @_;
204 27040 100 100     81642 return [ $seq ] if !$unord and !$hyper;
205 11905 100 100     36660 return [] if $unord and $hyper and !@$seq;
      100        
206 11900         17057 my ($allow_self, $p_s, $s_s, @pairs);
207 11900 100       20113 if ($unord) {
208 11884         70399 require Set::Object;
209 11884         335365 my @a = Set::Object->new(@$seq)->members;
210 11884         58903 ($allow_self, $p_s, $s_s) = (@a < 2, \@a, \@a);
211             } else {
212 16         47 ($allow_self, $p_s, $s_s) = (1, @$seq);
213             }
214 11900         24219 for my $p (@$p_s) {
215 23547 100       105525 push @pairs, map [$p, $_], $allow_self ? @$s_s : grep $p != $_, @$s_s;
216             }
217 11900         34313 \@pairs;
218             }
219              
220             sub _get_path_count {
221 217 100   217   6482 return 0 unless my ($i) = &__has_path;
222 194         411 my $f = (my $m = $_[0])->[ _f ];
223             return
224             ($f & _COUNT) ? $m->[ _count ][ $i ] :
225 194 100       771 ($f & _MULTI) ? scalar keys %{ $m->[ _attr ][ $i ] } : 1;
  90 100       498  
226             }
227              
228             sub has_path {
229 778     778 1 14379 ( &__has_path )[0];
230             }
231              
232             sub has_path_by_multi_id {
233 268 100   268 1 3866 return undef unless my ($i) = &__has_path;
234 235         1006 return exists $_[0]->[ _attr ][ $i ]{ $_[2] };
235             }
236              
237             sub del_path {
238 208 100   208 1 6014 return unless my ($i, $l) = &__has_path;
239 207 100 100     501 return 1 if &_is_COUNT and --$_[0][ _count ][ $i ] > 0;
240 195         544 $_[0]->_sequence_del($i, $l);
241 195         357 1;
242             }
243              
244             sub del_path_by_multi_id {
245 17 50   17 1 1176 return unless my ($i, $l) = &__has_path;
246 17         75 delete((my $attrs = (my $m = $_[0])->[ _attr ][ $i ])->{ $_[2] });
247 17 100       57 return 1 if keys %$attrs;
248 9         31 $m->_sequence_del($i, $l);
249 9         22 1;
250             }
251              
252             sub get_multi_ids {
253 51 100 66 51 1 209 return unless ((my $m = $_[0])->[ _f ] & _MULTI) and my ($i) = &__has_path;
254 49         90 keys %{ $m->[ _attr ][ $i ] };
  49         291  
255             }
256              
257             sub rename_path {
258 32     32 1 2480 my ($m, $from, $to) = @_;
259 32 50       78 return 1 if $m->[ _arity ] != 1; # all integers, no names
260 32 50       71 return unless my ($i, $l) = $m->__has_path($from);
261 32         81 $m->[ _i ][ $i ] = $to;
262 32 100 66     88 $to = __strval($to, $m->[ _f ]) if ref($to) and ($m->[ _f ] & _REF);
263 32         173 $m->[ _pi ]{ $to } = delete $m->[ _pi ]{ $l };
264 32         108 return 1;
265             }
266              
267             sub _del_path_attrs {
268 38 50   38   78 return unless my ($i) = &__has_path;
269 38         81 my $attr = (my $m = $_[0])->[ _attr ];
270 38 100       140 return $attr->[ $i ]{ $_[2] } = undef, 1 if ($m->[ _f ] & _MULTI);
271 27         87 delete $attr->[ $i ];
272             }
273              
274             sub __has_path {
275 1964     1964   4435 &__arg;
276 1964         3080 my ($f, $a, $pi, $k) = (@{ $_[0] }[ _f, _arity, _pi ], $_[1]);
  1964         3912  
277 1964 100 100     8379 $k = __strval($k, $f) if $a == 1 && ($f & _REF) && ref($k);
      100        
278 1964 100 100     7600 my $l = ($a == 0 && !($f & _UNORD)) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
279 1964         3511 my $id = $pi->{ $l };
280 1964 100       8199 (defined $id ? $id : return, $l);
281             }
282              
283             sub _get_path_attrs {
284 355 100   355   2871 return unless my ($i) = &__has_path;
285 346         707 my $attrs = (my $m = $_[0])->[ _attr ][ $i ];
286 346 100       2458 ($m->[ _f ] & _MULTI) ? $attrs->{ $_[2] } : $attrs;
287             }
288              
289             sub _has_path_attrs {
290 82 100   82   7615 keys %{ &{ $_[0]->can('_get_path_attrs') } || return undef } ? 1 : 0;
  82 100       119  
  82         325  
291             }
292              
293             sub _has_path_attr {
294 62   100 62   124 exists(( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] });
295             }
296              
297             sub _get_path_attr {
298 11514   100 11514   15809 ( &{ $_[0]->can('_get_path_attrs') } || return )->{ $_[-1] };
299             }
300              
301             sub _get_path_attr_names {
302 78 100   78   202 keys %{ &{ $_[0]->can('_get_path_attrs') } || return };
  78         152  
  78         292  
303             }
304              
305             sub _get_path_attr_values {
306 26 100   26   50 values %{ &{ $_[0]->can('_get_path_attrs') } || return };
  26         48  
  26         119  
307             }
308              
309             sub _del_path_attr {
310 40 100   40   9744 return unless my $attrs = &{ $_[0]->can('_get_path_attrs') };
  40         181  
311 36 50       125 return 0 unless exists $attrs->{ my $attr = $_[-1] };
312 36         98 delete $attrs->{$attr};
313 36 100       121 return 1 if keys %$attrs;
314 15         31 &{ $_[0]->can('_del_path_attrs') };
  15         71  
315 15         42 1;
316             }
317              
318             sub _sequence_del {
319 204     204   440 my ($m, $id, $l) = @_;
320 204         485 my ($f, $a, $map_i, $pi, $map_s, $map_p) = @$m[ _f, _arity, _i, _pi, _s, _p ];
321 204         411 delete $pi->{ $l };
322 204         615 delete $m->[ $_ ][ $id ] for _count, _attr;
323 204         346 my $path = delete $map_i->[ $id ];
324 204 100       443 _successors_del($f, $a, $map_s, $map_p, $id, $path) if $map_s;
325 204         321 return 1;
326             }
327              
328             sub get_paths_by_ids {
329 6179     6179 1 15711 my ($i, undef, $list, $deep) = ( @{ $_[0] }[ _i ], @_ );
  6179         15887  
330 6179 100       102199 $deep ? map [ map [ @$i[ @$_ ] ], @$_ ], @$list : map [ @$i[ @$_ ] ], @$list;
331             }
332              
333             sub paths {
334 3526 50   3526 1 5077 grep defined, @{ $_[0]->[ _i ] || Graph::_empty_array() };
  3526         23983  
335             }
336              
337             sub ids {
338 302 50   302 1 498 values %{ $_[0]->[ _pi ] || Graph::_empty_array() };
  302         2666  
339             }
340              
341             sub get_ids_by_paths {
342 1230     1230 1 17133 my ($f, $a, $pi, $m, $list, $ensure, $deep) = ( @{ $_[0] }[ _f, _arity, _pi ], @_ );
  1230         3200  
343 1230   100     4535 $deep ||= 0;
344 1230         3993 my ($is_multi, $is_ref, $is_unord) = (map $f & $_, _MULTI, _REF, _UNORD);
345             return map { # Fast path
346 1230 100 100     4697 my @ret = map {
      100        
347 121 100       262 my $id = $pi->{ $a != 1 ? "@$_" : $_ };
  154 100       365  
348 154 100       448 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 108 100       443 $deep ? \@ret : @ret;
353             } @$list if $a and !$is_ref and $deep < 2;
354             map {
355 1106         1883 my @ret = map {
356 1166 100       2311 my @ret2 = map {
357 1277 100       2334 my $k = $_;
  1290         2129  
358 1290 100 100     6070 $k = __strval($k, $f) if $a == 1 && $is_ref && ref($k);
      100        
359 1290 100 100     4424 my $l = ($a == 0 && !$is_unord) ? join '|', map join(' ', sort @$_), @$k : $a == 1 ? "$k" : "@$k";
    100          
360 1290         2204 my $id = $pi->{ $l };
361 1290 50       4326 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       2766 $deep > 1 ? \@ret2 : @ret2;
366             } $deep ? @$_ : $_;
367 1005 100       3887 $deep ? \@ret : @ret;
368             } @$list;
369             }
370              
371             sub _paths_fromto {
372 46     46   85 my $offset = pop;
373 46         75 my ($i, $map_x, @v) = ( @{ $_[0] }[ _i, $offset ], @_[1..$#_] );
  46         163  
374 46 50       230 Graph::__carp_confess("undefined vertex") if grep !defined, @v;
375 46         260 require Set::Object;
376 46 100       103 map $i->[ $_ ], Set::Object->new(map @$_, map values %{ $map_x->{ $_ } || _empty }, @v)->members;
  52         656  
377             }
378 31     31 1 7866 sub paths_from { push @_, _s; goto &_paths_fromto }
  31         111  
379 15     15 1 3773 sub paths_to { push @_, _p; goto &_paths_fromto }
  15         52  
380              
381             sub _cessors {
382 181     181   272 my $offset = pop;
383 181         298 my ($map_x, @v) = ( @{ $_[0] }[ $offset ], @_[1..$#_] );
  181         497  
384 181 50       518 Graph::__carp_confess("undefined vertex") if grep !defined, @v;
385 181         1379 require Set::Object;
386 181 100       8443 Set::Object->new(map keys %{ $map_x->{ $_ } || _empty }, @v)->members;
  187         1539  
387             }
388 140     140 1 6226 sub successors { push @_, _s; goto &_cessors }
  140         349  
389 41     41 1 4755 sub predecessors { push @_, _p; goto &_cessors }
  41         126  
390              
391             sub has_successor {
392 72     72 1 8646 my ($map_s, $u, $v) = ( @{ $_[0] }[ _s ], @_[1, 2] );
  72         229  
393 72 50       281 Graph::__carp_confess("undefined vertex") if grep !defined, $u, $v;
394 72 100       101 exists ${ $map_s->{ $u } || _empty }{ $v };
  72         381  
395             }
396              
397             sub __strval {
398 2151     2151   3688 my ($k, $f) = @_;
399 2151 50 33     6380 return $k unless ref $k && ($f & _REF);
400 2151 100       4067 return "$k" if ($f & _STR);
401 2147         9726 require Scalar::Util;
402 2147         5960 Scalar::Util::refaddr($k);
403             }
404              
405             sub __arg {
406 2520     2520   3544 my ($f, $a, $m, $k) = (@{ $_[0] }[ _f, _arity ], @_[0, 1]);
  2520         6607  
407 2520 50 66     7653 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 540 my ($f, $a, $i2p, $m) = (@{ $_[0] }[ _f, _arity, _i ], $_[0]);
  3         14  
414 3   33     22 my $is_ref = $a == 1 && ($f & _REF);
415 3         11 my $pi = $m->[ _pi ] = {};
416 3         7 for my $i ( 0..$#{ $i2p } ) {
  3         13  
417 4 50       28 next if !defined(my $k = $i2p->[ $i ]); # deleted
418 4 50 33     35 $k = __strval($k, $f) if $is_ref && ref($k);
419 4         33 $pi->{ $k } = $i;
420             }
421             }
422              
423             1;
424             __END__