File Coverage

blib/lib/Array/DeepUtils.pm
Criterion Covered Total %
statement 256 260 98.4
branch 105 150 70.0
condition 27 63 42.8
subroutine 20 20 100.0
pod 15 15 100.0
total 423 508 83.2


line stmt bran cond sub pod time code
1             package Array::DeepUtils;
2            
3 1     1   29977 use strict;
  1         3  
  1         33  
4 1     1   5 use warnings;
  1         3  
  1         31  
5            
6 1     1   5 use Carp;
  1         7  
  1         76  
7 1     1   7 use Storable qw/dclone/;
  1         1  
  1         10309  
8            
9             require Exporter;
10             our @ISA = qw(Exporter);
11             our @EXPORT_OK = qw/
12             binary collapse dcopy idx
13             purge remove reshape rotate scatter shape subscript
14             transpose unary value_by_path vector_iterator
15             /;
16             our %EXPORT_TAGS = (
17             'all' => [ @EXPORT_OK ],
18             );
19            
20             our $VERSION = 0.2;
21             our $DEBUG = 0;
22             our $LastError = '';
23            
24             my $NaV = bless(\my $dummy, 'NaV');
25            
26            
27             =pod
28            
29             =head1 NAME
30            
31             Array::DeepUtils - utilities for the manipulation of nested arrays
32            
33             =head1 VERSION
34            
35             This document refers to version 0.1 of Array::DeepUtils
36            
37             =head1 SYNOPSIS
38            
39            
40             use Array::DeepUtils qw/:all/;
41            
42             binary(
43             [1,2,3,4,5,6,7,8],
44             [[1,1][2,2][3,3][4,4]],
45             sub { $_[0] + $_[1] }
46             );
47            
48             yields:
49            
50             [
51             [ 2, 3 ],
52             [ 5, 6 ],
53             [ 8, 9 ],
54             [ 11, 12 ],
55             ]
56            
57             A more complex example:
58            
59             my $x = [1..9];
60            
61             my $y = reshape($x, [3,3,3,3], $x);
62            
63             $y is now:
64            
65             [
66             [
67             [[1,2,3],[4,5,6],[7,8,9]],
68             [[1,2,3],[4,5,6],[7,8,9]],
69             [[1,2,3],[4,5,6],[7,8,9]],
70             ],
71             [
72             [[1,2,3],[4,5,6],[7,8,9]],
73             [[1,2,3],[4,5,6],[7,8,9]],
74             [[1,2,3],[4,5,6],[7,8,9]],
75             ],
76             [
77             [[1,2,3],[4,5,6],[7,8,9]],
78             [[1,2,3],[4,5,6],[7,8,9]],
79             [[1,2,3],[4,5,6],[7,8,9]],
80             ]
81             ];
82            
83            
84             my $z = dcopy($y, [[1,1,1,1],[2,2,2,2]]);
85            
86             $z is now:
87            
88             [
89             [
90             [[5,6],[8,9]],
91             [[5,6],[8,9]],
92             ],
93             [
94             [[5,6],[8,9]],
95             [[5,6],[8,9]],
96             ]
97             ];
98            
99             my $c = reshape([], [2,2], collapse($z));
100            
101             resulting in $c being:
102            
103             [[5,6],[8,9]]
104            
105            
106             =head1 DESCRIPTION
107            
108             This module is a collection of subroutines for the manipulation of
109             deeply nested arrays. It provides routines for iterating along
110             coordinates and for setting, retrieving and deleting values.
111             The functions binary and unary are provided for applying arbitrary
112             operators as code references to deeply nested arrays. With shape() and
113             reshape() there are methods to determine and change the dimensions.
114            
115             By default nothing is exported. The subroutines can be imported all at
116             once via the ':all' tag.
117            
118             =head2 Subroutine short description
119            
120             L - appply a binary operator between two nested arrays
121            
122             L - flatten a nested array to a one dimensional vector
123            
124             L - extract part of a nested array between two vectors
125            
126             L - build an index vector for values of another vector
127            
128             L - remove elements by value from a nested array
129            
130             L - remove elements by index
131            
132             L - transform nested array by dimension vector
133            
134             L - rotate a data structure along its axes
135            
136             L - build a new data structure with data and index vector.
137            
138             L - get nested array dimension vector
139            
140             L - extract nested array values by index vector
141            
142             L - transpose a nested array
143            
144             L - appply a unary operator to all values of a nested array
145            
146             L - extract nested array values by coordinate vector
147            
148             L - creates a subroutine for iterating between two coordinates
149            
150             =cut
151            
152            
153             =pod
154            
155             =head1 SUBROUTINES
156            
157             =head2 binary()
158            
159             B
160            
161             Recursively apply a binary operator represented by a subroutine
162             reference to all elements of two nested data structures given in $aref1
163             and $aref2 and set the resulting values in $aref2. $aref2 will also be
164             returned.
165            
166             If these structures differ in shape they will be reshaped according to
167             the larger structure. The value of $neutral_element will be used if one
168             of the operands is undefined or does not exist ($neutral_element can
169             also be a subroutine reference; it will be called on value retrieval and
170             given $aref1 respectively $aref2 as only parameter). To be able to use
171             methods as subroutines $object will be passed to the subroutine as first
172             parameter when specified. Since binary() calls reshape() a given
173             $fill_aref will be passed as the third parameter to reshape().
174            
175             A simple example, after:
176            
177             my $v1 = [1,2,3];
178             my $v2 = [9,8,7];
179             my $func = sub { $_[0] * $_[1] }
180             binary($v1, $v2, $func);
181            
182             $v2 will have a value of
183            
184             [9, 16, 21]
185            
186             Making it a bit more complicated:
187            
188             my $v1 = [1,2,3,4,5,6];
189             my $v2 = [9,8,7];
190             my $func = sub { $_[0] * $_[1] }
191             binary($v1, $v2, $func);
192            
193             results in:
194            
195             [9,16,21,36,40,42]
196            
197             because missing values will be filled with the flattened structure
198             repeated as often as it is needed, so the above is exactly the same as:
199            
200             my $v1 = [1,2,3,4,5,6];
201             my $v2 = [9,8,7,9,8,7];
202             my $func = sub { $_[0] * $_[1] }
203             binary($v1, $v2, $func);
204            
205             Using the fill parameter gives the opportunity to assign the values
206             used for filling. It will also be repeated when necessary.
207            
208             my $v1 = [1,2,3,4,5,6];
209             my $v2 = [9,8,7];
210             my $fill = [1,2];
211             my $func = sub { $_[0] * $_[1] };
212             binary($v1, $v2, $func, 1, undef, $fill);
213            
214             results in:
215            
216             [9,16,21,4,10,6];
217            
218             because $v2 will have been reshaped to [9,8,7,1,2,1] before the
219             multiplication.
220            
221             This works for vectors of arbitrary depth, so that:
222            
223             my $v1 = [[1,2,3], [4,5,6], [7,8,9]];
224             my $v2 = [[11,12], [13,14]];
225             my $fill = [1, -1];
226             my $func = sub { $_[0] * $_[1] };
227             binary($v1, $v2, $func, 1, undef, $fill);
228            
229             yields:
230            
231             [[11,24,3], [52,70,-6], [7,-8,9]]
232            
233             =cut
234            
235             sub binary {
236 1     1 1 720 my($func, $neutral, $obj, $fill) = @_[2..5];
237            
238             # param checks
239 1 50       6 croak $LastError = 'binary: not a code ref'
240             unless ref($func) eq 'CODE';
241 1 50 33     6 croak $LastError = 'binary: not an object'
242             if $obj and !ref($obj);
243            
244             # determine the "bigger" vector
245             # (run 'shape '* reduce' and compare)
246 1         2 my @dims;
247             my @inner;
248 1         4 for my $i ( 0 .. 1 ) {
249 2         4 $dims[$i] = shape($_[$i]);
250 2 50       3 $dims[$i] = [1] unless @{ $dims[$i] };
  2         7  
251 2         4 $inner[$i] = 1;
252 2         2 $inner[$i] *= $_ for @{ $dims[$i] };
  2         12  
253             }
254            
255 1 50       7 my $reshape_dim = $inner[0] >= $inner[1] ? $dims[0] : $dims[1];
256            
257             # reshape both with reshape_dim vector
258 1         4 for my $i ( 0 .. 1 ) {
259 2 50       8 $_[$i] = [$_[$i]] unless ref($_[$i]) eq 'ARRAY';
260 2 50       10 $_[$i] = reshape($_[$i], $reshape_dim, $fill ? $fill : ());
261             }
262            
263             # create start and end vector
264 1         2 my $start = [ map { 0 } @$reshape_dim ];
  2         4  
265 1         3 my $end = [ map { $_ - 1 } @$reshape_dim ];
  2         3  
266            
267             # shortcut for empty arrays
268 1 50 33     29 if ( !@$start or !@$end ) {
269 0         0 $_[1] = [];
270 0         0 return $_[1];
271             }
272            
273             # iterate over the arrays, call function and store
274             # the value in second array
275 1         3 my $iterator = vector_iterator($start, $end);
276            
277 1         3 while ( my ($vec) = $iterator->() ) {
278            
279             # get values with value_by_path()
280 9         7 my @vals;
281 9         12 for my $i ( 0 .. 1 ) {
282 18         33 $vals[$i] = value_by_path($_[$i], $vec);
283 18 0 33     73 $vals[$i] = (ref($neutral) eq 'CODE' ? $neutral->($_[$i]) : $neutral)
    50          
284             if !defined($vals[$i]) or ref($vals[$i]) eq 'NaV';
285             }
286            
287             # call fuction and set value
288             value_by_path(
289 9 50       27 $_[1],
290             $vec,
291             $func->($obj ? ($obj, @vals) : @vals),
292             );
293             }
294            
295 1         9 return $_[1];
296             }
297            
298            
299             =pod
300            
301             =head2 collapse()
302            
303             B
304            
305             Collapse the referenced array of arrays of arbitrary depth, i.e
306             flatten it to a simple array and return a reference to it.
307            
308             Example:
309            
310             collapse([[1,2,3],4,[5,[6,7,8,[9,0]]]]);
311            
312             will return:
313            
314             [1,2,3,4,5,6,7,8,9,0]
315            
316             =cut
317            
318             sub collapse {
319 5     5 1 148 my($struct) = @_;
320            
321 5 50       18 croak $LastError = 'collapse: not an array reference'
322             unless ref($struct) eq 'ARRAY';
323            
324 5         7 my @result;
325            
326             # simply travel the array iteratively and store
327             # every value in @result
328            
329             # element and index stack
330 5         8 my @estack = ( $struct );
331 5         8 my @istack = ( 0 );
332            
333 5         10 while ( @estack ) {
334            
335             # always opereate on the top of the stacks
336 89         89 my $e = $estack[-1];
337 89         84 my $i = $istack[-1];
338            
339 89 100       137 if ( $i <= $#$e ) {
340            
341             # in currrent array, if value is array ref
342             # push next reference and a new index onto stacks
343 78 100       129 if ( ref($e->[$i]) eq 'ARRAY' ) {
344 26         26 push @estack, $e->[$i];
345 26         26 push @istack, 0;
346 26         44 next;
347             }
348            
349             # push value into result array
350 52         64 push @result, $e->[$i];
351             }
352            
353             # after last item, pop last item and last index from stacks
354 63 100       117 if ( $i >= $#$e ) {
355 31         25 pop @estack;
356 31         33 pop @istack;
357             }
358            
359             # increment index for next fetch
360 63 100       144 $istack[-1]++ if @istack;
361             }
362            
363 5         15 return \@result;
364             }
365            
366            
367             =pod
368            
369             =head2 dcopy()
370            
371             B
372            
373             Extract a part of an deeply nested array between two vectors given in
374             the array referenced by $coord_ref. This is done via an iterator
375             generated with vector_iterator() running from the first to the second
376             coordinate given.
377            
378             Example:
379            
380             dcopy([[1,2,3], [4,5,6], [7,8,9]], [[1,0], [2,1]]);
381            
382             will return
383            
384             [ [4,5], [7,8] ]
385            
386             This will work in either direction, so:
387            
388             dcopy([[1,2,3], [4,5,6], [7,8,9]], [[2,1], [1,0]]);
389            
390             will give:
391            
392             [ [8,7], [5,4] ]
393            
394             as expected.
395            
396             =cut
397            
398             sub dcopy {
399 2     2 1 2322 my($struct, $coord) = @_;
400            
401             # param checks
402 2 50 33     15 croak $LastError = 'dcopy: not an array ref'
403             unless ref($struct) eq 'ARRAY' and ref($coord) eq 'ARRAY';
404            
405 2 50       7 croak $LastError = 'dcopy: coordinate vector with element count != 2!'
406             unless @$coord == 2;
407            
408 2         5 croak $LastError = 'dcopy: coordinate vector elements have different length!'
409 2 50       3 unless @{$coord->[0]} == @{$coord->[1]};
  2         6  
410            
411             # simply iterate and set values in $dest
412 2 50       13 my $iterator = vector_iterator(
    50          
413             ref($coord->[0]) eq 'ARRAY' ? $coord->[0] : [$coord->[0]],
414             ref($coord->[1]) eq 'ARRAY' ? $coord->[1] : [$coord->[1]]
415             );
416 2         5 my $dest = [];
417 2         4 while ( my ($svec, $dvec) = $iterator->() ) {
418 22         38 value_by_path(
419             $dest,
420             $dvec,
421             value_by_path($struct, $svec)
422             );
423             }
424            
425 2         16 return $dest;
426             }
427            
428            
429             =pod
430            
431             =head2 idx()
432            
433             B
434            
435             Return an index vector that contains the indices of the elements of the
436             first argument vector with respect to the second index vector.
437            
438             Example:
439            
440             idx([[1,3],[4,5]], [[1,2,3], [4,5,6], [7,8,9]]);
441            
442             will return:
443            
444             [[[0,0],[0,2]],[[1,0],[1,1]]]
445            
446             =cut
447            
448             sub idx {
449 1     1 1 809 my ($aref1, $aref2) = @_;
450            
451             # param checks
452 1 50 33     10 croak $LastError = 'idx: not an array ref'
453             unless ref($aref1) eq 'ARRAY' and ref($aref2) eq 'ARRAY';
454            
455 1         4 my ($dim1, $dim2) = (shape($aref1), shape($aref2));
456 1         3 my ($start1, $end1) = ([ map { 0 } @$dim1 ], [ map { $_ - 1 } @$dim1 ]);
  2         6  
  2         6  
457 1         3 my ($start2, $end2) = ([ map { 0 } @$dim2 ], [ map { $_ - 1 } @$dim2 ]);
  2         5  
  2         5  
458 1         4 my ($iterator1, $iterator2) = (vector_iterator($start1, $end1),
459             vector_iterator($start2, $end2));
460            
461 1 50       5 return [] unless scalar @$aref1;
462            
463             # Create a hash with indices of the elements of $aref2, making sure
464             # that multiple occurrences of an element don't destroy the first
465             # index of this element:
466 1         2 my %lookup;
467 1         3 while ( my($index) = $iterator2->() ) {
468 9         16 my $value = value_by_path($aref2, $index);
469 9 50 33     69 $lookup{$value} = $index if $value and !$lookup{$value};
470             }
471            
472             # Now lookup every single element from $aref1 in the lookup hash:
473 1         4 while ( my($index) = $iterator1->() ) {
474 4         8 my $position = $lookup{value_by_path($aref1, $index)};
475 4 50       10 value_by_path($aref1, $index, $position ? $position : []);
476             }
477            
478 1         42 return $aref1;
479             }
480            
481            
482             =pod
483            
484             =head2 purge()
485            
486             B
487            
488             Remove all values from the array referenced by $aref that equal $what in
489             a string comparison.
490            
491             Example:
492            
493             $v = [1,0,1,0,1,0,1,0];
494             purge($v, '0');
495            
496             will have $v reduced to:
497            
498             [1,1,1,1]
499            
500             =cut
501            
502             sub purge {
503 4     4 1 1000 my $what = pop;
504            
505 4 50       9 croak $LastError = 'purge: not an array ref'
506             unless ref($_[0]) eq 'ARRAY';
507            
508 4         9 my @estack = ($_[0]);
509 4         5 my @istack = ( $#{ $estack[-1] } );
  4         9  
510            
511 4         9 while ( @estack ) {
512            
513 51         55 my $e = $estack[-1];
514 51         54 my $i = $istack[-1];
515            
516 51 100       72 if ( $i >= 0 ) {
517            
518             # push next reference and a new index onto stacks
519 38 100       79 if ( ref($e->[$i]) eq 'ARRAY' ) {
520 9         11 push @estack, $e->[$i];
521 9         9 push @istack, $#{ $e->[$i] };
  9         15  
522 9         20 next;
523             }
524            
525 29 100       84 splice(@$e, $i, 1) if $e->[$i] eq $what;
526            
527             } else {
528            
529 13         13 pop @estack;
530 13         14 pop @istack;
531            
532             }
533            
534 42 100       122 $istack[-1]-- if @istack;
535            
536             }
537             }
538            
539            
540             =pod
541            
542             =head2 remove()
543            
544             B
545            
546             Remove all values with indices or coordinates given by $index or by the
547             array referenced by $coordinate_aref from an array referenced by $aref.
548            
549             Example:
550            
551             my $v = [1,2,3,4,5,6,7,8,9,0];
552             remove($v, [1,2,3]);
553            
554             will have $v reduced to:
555            
556             [1,5,6,7,8,9,0]
557            
558             and:
559            
560             my $aref = [[1,2,3],[4,5,6],[7,8,9]];
561            
562             remove($aref, [[0,1], [1,2], 2]);
563            
564             will leave:
565            
566             [[1,3],[4,5]]
567            
568             in $aref.
569            
570             =cut
571            
572             sub remove {
573 3     3 1 2606 my $coords = pop;
574            
575 3 50       12 croak $LastError = 'remove: not an array ref'
576             unless ref($_[0]) eq 'ARRAY';
577            
578 3 100       8 $coords = [$coords]
579             unless ref($coords) eq 'ARRAY';
580            
581 3         7 for ( @$coords ) {
582 6 100       17 $_ = [$_] unless ref($_) eq 'ARRAY';
583 6 50       18 value_by_path($_[0], $_, $NaV)
584             unless ref(value_by_path($_[0], $_)) eq 'NaV';
585             }
586            
587 3         8 purge($_[0], $NaV);
588             }
589            
590            
591             =pod
592            
593             =head2 reshape()
594            
595             B
596            
597             Create an array with the dimension vector given in $dims_aref and take
598             the values from $aref provided there is a value at the given position.
599             Additional values will be taken from the array referenced by $fill_aref
600             or - if it is not provided - from a flattened (call to collapse())
601             version of the original array referenced by $aref. If the fill source is
602             exhausted, reshape will start from index 0 again. This will be repeated
603             until the destination array is filled.
604            
605             Example:
606            
607             reshape([[1,2,3]], [3, 3]);
608            
609             will return:
610            
611             [ [1,2,3], [1,2,3], [1,2,3] ]
612            
613             and:
614            
615             reshape([[1,2,3]], [3, 3], ['x']);
616            
617             will return:
618            
619             [ [1,2,3], ['x','x','x'], ['x','x','x'] ]
620            
621             =cut
622            
623             sub reshape {
624 9     9 1 990 my($struct, $dims, $fill) = @_;
625            
626 9 50 33     71 if (
      66        
      33        
627             ref($struct) ne 'ARRAY' or
628             ref($dims) ne 'ARRAY' or
629             ( $fill and ref($fill) ne 'ARRAY' )
630             ) {
631 0         0 $LastError = "usage: reshape(AREF, AREF[, AREF])";
632 0         0 croak $LastError;
633             }
634            
635 9 50       19 return undef unless @$dims;
636 9 50       18 return [] if $dims->[0] == 0;
637            
638             # get a flattened copy of the source
639 9 100       91 $fill = collapse(dclone($struct))
640             unless $fill;
641 9 50       21 @$fill = ( undef ) unless @$fill;
642            
643 9         12 my $start = [ map { 0 } @$dims ];
  19         33  
644 9         12 my $end = [ map { $_ - 1 } @$dims ];
  19         31  
645            
646 9         17 my $iterator = vector_iterator($start, $end);
647            
648 9         12 my $i = 0;
649 9         10 my $dest = [];
650 9         19 while ( my ($vec) = $iterator->() ) {
651 146         209 my $val = value_by_path($struct, $vec);
652 146 100 66     822 value_by_path(
653             $dest,
654             $vec,
655             ( ($val and ref($val) eq 'NaV') or ref($val) eq 'ARRAY' )
656             ? $fill->[$i++ % @$fill]
657             : $val,
658             1,
659             );
660             }
661            
662 9         75 return $dest;
663             }
664            
665            
666             =pod
667            
668             =head2 rotate()
669            
670             B
671            
672             Rotate a data structure along its axes. It is possible to perform more
673             than one rotation at once, so rotating a two dimensional matrix along
674             its x- and y-axes by +1 and -1 positions is no problem.
675            
676             Example:
677            
678             rotate([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [1, -1]);
679            
680             will return:
681            
682             [[8,9,7],[2,3,1],[5,6,4]]
683            
684             Using the optional third parameter it is possible to fill previously
685             empty array elements with a given value via L.
686            
687             =cut
688            
689             sub rotate {
690 2     2 1 2337 my($struct, $rotvec, $fill) = @_;
691            
692             # param checks
693 2 50 33     17 croak $LastError = 'rotate: not an array ref'
694             unless ref($struct) eq 'ARRAY' and ref($rotvec) eq 'ARRAY';
695            
696 2         7 my $dim = shape($struct);
697            
698 2 50       7 croak "rotate: rotation vector does not fit array dimensions"
699             unless @$rotvec == @$dim;
700            
701 2         5 $struct = reshape($struct, $dim, $fill);
702            
703 2         4 my $start = [ map { 0 } @$dim ];
  4         8  
704 2         3 my $end = [ map { $_ - 1 } @$dim ];
  4         7  
705            
706 2         5 my $iterator = vector_iterator($start, $end);
707            
708 2         3 my $dest = [];
709 2         6 while ( my($svec) = $iterator->() ) {
710 36         71 my $dvec = [ map {
711 18         29 ( $svec->[$_] + $rotvec->[$_] ) % $dim->[$_]
712             } 0 .. $#$svec ];
713 18         34 value_by_path($dest, $dvec, value_by_path($struct, $svec));
714             }
715            
716 2         19 return $dest;
717             }
718            
719            
720             =pod
721            
722             =head2 scatter()
723            
724             B
725            
726             This function behaves inverse to subscript. While subscript selects
727             values from a nested data structure, controlled by an index vector,
728             scatter will distribute elements into a new data structure, controlled
729             by an index vector.
730            
731             Example:
732            
733             scatter([1, 2, 3, 4, 5, 6, 7], [[0,0], [0,1], [1,0], [1,1]]);
734            
735             will return:
736            
737             [[1, 2], [3, 4]]
738            
739             =cut
740            
741             sub scatter {
742 1     1 1 949 my ($aref, $struct) = @_;
743            
744             # param checks
745 1 50 33     10 croak $LastError = 'scatter: not an array ref'
746             unless ref($aref) eq 'ARRAY' and ref($struct) eq 'ARRAY';
747            
748             # Make sure that the raw data to be scattered will not be exhausted
749             # by the indices contained in $struct:
750 1         5 my $source = reshape($aref, [scalar @$struct], $aref);
751            
752             # Built new data structure (possibly containing empty elements):
753 1         3 my $result = [];
754 1         3 for my $position (@$struct) {
755 4 50       10 $position = [$position] unless ref($position) eq 'ARRAY';
756 4 50 33     21 value_by_path($result, $position, shift(@$source))
757             if ref($position) eq 'ARRAY' and ref($position->[0]) ne 'NaV';
758             }
759            
760 1         4 return $result;
761             }
762            
763            
764             =pod
765            
766             =head2 shape()
767            
768             B
769            
770             Determine the dimensions of an array and return it as
771             a vector (an array reference)
772            
773             Example:
774            
775             shape([[1,2,3], [4,5,6], [7,8,9]]);
776            
777             will return:
778            
779             [3,3]
780            
781             and:
782            
783             shape([[1,2,3],4,[5,[6,7,8,[9,0]]]]);
784            
785             will return:
786            
787             [3,3,4,2]
788            
789             A combination of shape() and reshape() will effectively turn an
790             "irregular" array into a regular one.
791            
792             For example:
793            
794             $aref = [[1,2,3],4,[5,6],[7,8,9]];
795            
796             reshape($aref, shape($aref), [0]);
797            
798             will return:
799            
800             [[1,2,3],[0,0,0],[5,6,0],[7,8,9]]
801            
802             =cut
803            
804             sub shape {
805 10     10 1 1049 my($struct) = @_;
806            
807 10 50       29 return [] unless ref($struct) eq 'ARRAY';
808            
809 10         19 my @out = ( 0 );
810 10         16 my @idx = ( 0 );
811 10         15 my @vstack = ( $struct );
812            
813 10         12 my $depth = 0;
814 10         22 while ( $depth >= 0 ) {
815            
816             # get the top reference from the stack
817 144         135 my $aref = $vstack[-1];
818            
819 144 100       331 if ( ref($aref->[$idx[$depth]]) eq 'ARRAY') {
    100          
820            
821             # found a reference push it on the stack and increase depth
822 27         37 push @vstack, $aref->[$idx[$depth++]];
823             # push a new index on the index stack
824 27         27 push @idx, 0;
825             # initialize the counter on the new level on first entry
826 27 100       81 $out[$depth] = 0 unless defined $out[$depth];
827            
828             } elsif ( $idx[$depth] <= $#$aref ) {
829            
830             # no reference and we still have elements in the array
831             # --> increase index for the current level
832 80         151 ++$idx[$depth];
833            
834             } else {
835            
836             # reached the end of the array
837             # --> remove it from the stack
838 37         31 pop @vstack;
839            
840             # remove last index from the index stack
841 37         42 pop @idx;
842            
843             # save the number of elements of the level
844             # if it is bigger than before
845 37 100       78 $out[$depth] = @$aref if @$aref > $out[$depth];
846            
847             # decrease the current level
848 37         37 $depth--;
849            
850             # increase the index for the current level
851 37 100       103 ++$idx[$depth] if $depth >= 0;
852            
853             }
854             }
855            
856 10         29 return \@out;
857             }
858            
859            
860             =pod
861            
862             =head2 subscript()
863            
864             B
865            
866             Retrieve and return values of a deeply nested array for a single index a
867             list of indices or a list of coordinate vectors.
868            
869             Example:
870            
871             my $aref = [[1,2,3],[4,5,6],[7,8,9]];
872            
873             subscript($aref, 1);
874            
875             returns:
876            
877             [[4,5,6]]
878            
879             whereas:
880            
881             subscript($aref, [[0,1], [1,2], 2]);
882            
883             returns:
884            
885             [2,6,[7,8,9]]
886            
887             =cut
888            
889             sub subscript {
890 3     3 1 2032 my($struct, $coords) = @_;
891            
892 3 50       9 croak $LastError = 'subscript: not an array ref'
893             unless ref($_[0]) eq 'ARRAY';
894            
895 3 100       7 $coords = [$coords]
896             unless ref($coords) eq 'ARRAY';
897            
898 3         6 for ( @$coords ) {
899 6 100       16 $_ = [$_] unless ref($_) eq 'ARRAY';
900             }
901            
902 3         3 my @out;
903 3         4 for my $position (@$coords) {
904 6 50 33     32 push @out, value_by_path($struct, $position)
905             if ref($position) eq 'ARRAY' and ref($position->[0]) ne 'NaV';
906             }
907            
908 3         10 return \@out;
909             }
910            
911            
912             =pod
913            
914             =head2 transpose()
915            
916             B
917            
918             Transpose a nested data structure. In the easiest two-dimensional case
919             this is the traditional transposition operation.
920            
921             Example:
922            
923             transpose([[1,2,3], [4,5,6], [7,8,9]], 1);
924            
925             will return:
926            
927             [[1,4,7],[2,5,8],[3,6,9]]
928            
929             Using the optional third parameter, it is possible to fill previously
930             empty array elements with a given value via L.
931            
932             =cut
933            
934             sub transpose {
935 1     1 1 928 my($struct, $control, $fill) = @_;
936            
937 1 50       5 croak $LastError = 'transpose: not an array ref'
938             unless ref($struct) eq 'ARRAY';
939            
940 1         3 my $dim = shape($struct);
941            
942 1         9 $struct = reshape($struct, $dim, $fill);
943            
944 1         2 my $start = [ map { 0 } @$dim ];
  2         4  
945 1         2 my $end = [ map { $_ - 1 } @$dim ];
  2         5  
946            
947 1         3 my $iterator = vector_iterator($start, $end);
948            
949 1         2 my $dest = [];
950 1         3 while ( my($svec) = $iterator->() ) {
951 18         34 my $dvec = [
952             map {
953 9         14 $svec->[($_ + $control) % scalar(@$svec)]
954             } 0 .. $#$svec
955             ];
956 9         18 value_by_path($dest, $dvec, value_by_path($struct, $svec));
957             }
958            
959 1         10 return $dest;
960             }
961            
962            
963             =pod
964            
965             =head2 unary()
966            
967             B
968            
969             Recursively apply a unary operator represented by a subroutine
970             reference to all elements of a nested data structure given in $aref
971             and set the resulting values in the referenced array itself.
972             The reference will also be returned.
973            
974             The value of $neutral_element will be used if the original is
975             undefined or does not exist. To be able to use methods as subroutines
976             $object will be passed to the subroutine as first parameter when
977             specified.
978            
979             A simple example, after:
980            
981             my $v = [1,0,2,0,3,[1,0,3]];
982             my $func = sub { ! $_[0] + 0 };
983            
984             unary($v, $func);
985            
986             will return:
987            
988             [1,0,2,0,3,[0,1,0]]
989            
990             =cut
991            
992             sub unary {
993 1     1 1 1085 my($func, $neutral, $obj) = @_[1..3];
994            
995             # param checks
996 1 50       10 croak $LastError = 'unary: not a code ref'
997             unless ref($func) eq 'CODE';
998 1 50 33     4 croak $LastError = 'unary: not an object'
999             if $obj and !ref($obj);
1000            
1001 1         4 return $_[0]
1002 1 50 33     4 if ref($_[0]) eq 'ARRAY' and @{ $_[0] } == 0;
1003            
1004 1         3 my $dim = shape($_[0]);
1005            
1006 1         3 my $start = [ map { 0 } @$dim ];
  2         4  
1007 1         2 my $end = [ map { $_ - 1 } @$dim ];
  2         5  
1008            
1009 1         3 my $iterator = vector_iterator($start, $end);
1010            
1011 1         4 while ( my ($vec) = $iterator->() ) {
1012 18         30 my $val = value_by_path($_[0], $vec);
1013 18 50 66     98 value_by_path(
    50          
    100          
1014             $_[0],
1015             $vec,
1016             (!defined($val) or ref($val) eq 'NaV')
1017             ? (ref($neutral) eq 'CODE' ? $neutral->($_[0]) : $neutral)
1018             : $func->($obj ? ($obj, $val) : $val),
1019             );
1020             }
1021            
1022 1         10 return($_[0]);
1023             }
1024            
1025            
1026             =pod
1027            
1028             =head2 value_by_path()
1029            
1030             B
1031            
1032             Get or set a value in a deeply nested array by a coordinate vector.
1033            
1034             Example:
1035            
1036             my $vec = [[1,2,3], [4,5,6], [7,8,9]];
1037            
1038             value_by_path($vec, [1,1], 99);
1039            
1040             will give:
1041            
1042             [[1,2,3], [4,99,6], [7,8,9]];
1043            
1044             in $vec. This is not spectacular since one could easily write:
1045            
1046             $vec->[1][1] = 99;
1047            
1048             but value_by_path() will be needed if the coordinate vector is created
1049             dynamically and can be of arbitrary length.
1050             If you explicitly want to set an undefined value, you have to set
1051             $force to a true value.
1052            
1053             =cut
1054            
1055             sub value_by_path {
1056 496     496 1 599 my($aref, $coordinate, $value, $force) = @_;
1057            
1058 496 50       879 croak $LastError = 'value_by_path: not an array ref'
1059             unless ref($aref) eq 'ARRAY';
1060            
1061 496         451 my $vref = $aref;
1062 496 50       768 my $vec = ref($coordinate) eq 'ARRAY'
1063             ? $coordinate
1064             : [$coordinate];
1065            
1066 496         568 my $end = @$vec - 1;
1067            
1068 496         435 my $i = 0;
1069 496         814 while ( $i < $end ) {
1070            
1071 702 100       893 if ( defined($value) ) {
1072 407 100 66     1483 $vref->[$vec->[$i]] = []
1073             unless defined($vref->[$vec->[$i]])
1074             and
1075             ref($vref->[$vec->[$i]]) eq 'ARRAY';
1076             } else {
1077 295 100       690 return $NaV unless ref($vref->[$vec->[$i]]) eq 'ARRAY';
1078             }
1079            
1080 581         1151 $vref = $vref->[$vec->[$i++]];
1081             }
1082            
1083 375 100 66     942 if ( defined($value) or $force ) {
1084 221 100       989 $vref->[$vec->[$i]]
1085             = ref($value) eq 'ARRAY'
1086             ? dclone($value)
1087             : $value;
1088             } else {
1089 154 100       290 return $NaV
1090             if $vec->[$i] > $#$vref;
1091             return(
1092 151 100       575 ref($vref->[$vec->[$i]]) eq 'ARRAY'
1093             ? dclone($vref->[$vec->[$i]])
1094             : $vref->[$vec->[$i]]
1095             );
1096             }
1097             }
1098            
1099            
1100             =pod
1101            
1102             =head2 vector_iterator()
1103            
1104             B
1105            
1106             This routine returns a subroutine reference to an iterator which
1107             is used to generate successive coordinate vectors starting with the
1108             coordinates in $from_aref to those in $to_aref.
1109            
1110             The resulting subroutine will return a pair of coordinate vectors on
1111             each successive call or an empty list if the iterator has reached the
1112             last coordinate. The first coordinate returned is related to the given
1113             coordinate pair, the second one to a corresponding zero based array.
1114            
1115             Example:
1116            
1117             my $aref = [[1,2,3], [4,5,6], [7,8,9]];
1118            
1119             my $iterator = vector_iterator([0,1], [1,2]);
1120            
1121             while ( my($svec, $dvec) = $iterator->() ) {
1122             my $val = value_by_path($aref, $svec);
1123             print "[$svec->[0] $svec->[1]] [$dvec->[0] $dvec->[1]] -> $val\n";
1124             }
1125            
1126             will print:
1127            
1128             [0 1] [0 0] -> 2
1129             [0 2] [0 1] -> 3
1130             [1 1] [1 0] -> 5
1131             [1 2] [1 1] -> 6
1132            
1133             =cut
1134            
1135             sub vector_iterator {
1136 20     20 1 2543 my($from, $to) = @_;
1137            
1138 20 50 33     88 croak $LastError = 'value_by_path: not an array ref'
1139             unless ref($from) eq 'ARRAY' and ref($to) eq 'ARRAY';
1140            
1141 20         39 my @start = @$from;
1142 20         28 my @current = @$from;
1143 20         27 my @end = @$to;
1144 20         33 my @dir = map { $end[$_] <=> $start[$_] } 0 .. $#end;
  43         74  
1145 20         35 my @diff = map { abs($end[$_] - $start[$_]) + 1 } 0 .. $#end;
  43         69  
1146 20         29 my @dvec = map { 0 } 0 .. $#end;
  43         58  
1147            
1148 20         24 my $end_reached = 0;
1149            
1150             return sub {
1151            
1152 263 100   263   482 return if $end_reached;
1153            
1154 243         235 $end_reached = 1;
1155 243         388 for my $i ( 0 .. $#end ) {
1156 342   66     1063 $end_reached &&= $current[$i] == $end[$i];
1157 342 100       575 last unless $end_reached;
1158             }
1159            
1160 243         417 my $sretvec = [ @current ];
1161 243         338 my $dretvec = [ @dvec ];
1162            
1163 243         419 for my $i ( reverse 0 .. $#end ) {
1164            
1165 347         387 $current[$i] += $dir[$i];
1166 347         302 $dvec[$i]++;
1167 347 100       588 if ( $current[$i] == $end[$i] + $dir[$i] ) {
1168 124         118 $current[$i] = $start[$i];
1169 124         124 $dvec[$i] = 0;
1170             }
1171            
1172 347 100       624 last if $current[$i] != $start[$i];
1173             }
1174            
1175 243         613 return($sretvec, $dretvec);
1176 20         103 };
1177             }
1178            
1179            
1180             =pod
1181            
1182             =head1 SEE ALSO
1183            
1184             Array::DeepUtils was developed during the implementation of lang5 a
1185             stack based array language. The source will be maintained in the source
1186             repository of lang5.
1187            
1188             =head2 Links
1189            
1190             =over
1191            
1192             =item *
1193            
1194             L.
1195            
1196             =item *
1197            
1198             L.
1199            
1200             =back
1201            
1202             =head2 Bug Reports and Feature Requests
1203            
1204             =over
1205            
1206             =item *
1207            
1208             L
1209            
1210             =item *
1211            
1212             L
1213            
1214             =back
1215            
1216             =head1 AUTHOR
1217            
1218             Thomas Kratz Etomk@cpan.orgE
1219            
1220             Bernd Ulmann Eulmann@vaxman.deE
1221            
1222             =head1 COPYRIGHT
1223            
1224             Copyright (C) 2011 by Thomas Kratz, Bernd Ulmann
1225            
1226             This library is free software; you can redistribute it and/or
1227             modify it under the same terms as Perl itself, either Perl version
1228             5.8.8 or, at your option, any later version of Perl 5 you may
1229             have available.
1230            
1231             =cut
1232            
1233             1;