File Coverage

blib/lib/Array/2D.pm
Criterion Covered Total %
statement 33 717 4.6
branch 2 206 0.9
condition 4 103 3.8
subroutine 10 92 10.8
pod 70 70 100.0
total 119 1188 10.0


line stmt bran cond sub pod time code
1             ## no critic (ProhibitExcessMainComplexity)
2             # it thinks all the code in $x = sub {...} is in the main module
3             package Array::2D;
4 20     20   351791 use 5.008001;
  20         76  
5 20     20   105 use strict;
  20         40  
  20         378  
6 20     20   89 use warnings;
  20         98  
  20         1259  
7              
8             our $VERSION = '0.001_003';
9             $VERSION = eval $VERSION; ## no critic (BuiltinFunctions::ProhibitStringyEval)
10              
11             ## no critic (RequirePodAtEnd)
12              
13             =encoding utf8
14              
15             =head1 NAME
16              
17             Array::2D - Methods for simple array-of-arrays data structures
18              
19             =head1 VERSION
20              
21             This documentation refers to version 0.001_003
22              
23             =head2 NOTICE
24              
25             This is alpha software. Method names and behaviors are subject to change.
26             The test suite has significant omissions.
27              
28             =head1 SYNOPSIS
29              
30             use Array::2D;
31             my $array2d = Array::2D->new( [ qw/a b c/ ] , [ qw/w x y/ ] );
32              
33             # $array2d contains
34              
35             # a b c
36             # w x y
37              
38             $array2d->push_col (qw/d z/);
39              
40             # a b c d
41             # w x y z
42              
43             say $array2d->[0][1];
44             # prints "b"
45              
46             =head1 DESCRIPTION
47              
48             Array::2D is a module that adds useful methods to Perl's
49             standard array of arrays ("AoA") data structure, as described in
50             L and
51             L. That is, an array that
52             contains other arrays:
53              
54             [
55             [ 1, 2, 3 ] ,
56             [ 4, 5, 6 ] ,
57             ]
58              
59             This module provides methods for using that standard construction.
60              
61             Most of the time, it's good practice to avoid having programs that
62             use a module know about the internal construction of an object.
63             However, this module is not like that. It assumes that the data
64             structure I accessible outside the module's code, and may be
65             altered by other code. The module will never change the data
66             structure to include anything else. Therefore, it is perfectly
67             reasonable to use the normal reference syntax to access items inside
68             the array. A construction like C<< $array2d->[0][1] >> for accessing
69             a single element, or C<< @{$array2d} >> to get the list of rows,
70             is perfectly appropriate. This module exists because the reference-based
71             implementation of multidimensional arrays in Perl makes it difficult
72             to access, for example, a single column, or a two-dimensional slice,
73             without writing lots of extra code.
74              
75             Array::2D uses "row" for the first dimension, and "column" or
76             "col" for the second dimension. This does mean that the order
77             of (row, column) is the opposite of the usual (x,y) algebraic order.
78              
79             Because this object is just an array of arrays, most of the methods
80             referring to rows are here mainly for completeness, and aren't
81             much more useful than the native Perl construction (e.g., C<<
82             $array2d->last_row() >> is just a slower way of doing C<< $#{$array2d}
83             >>.) They will also typically be much slower.
84              
85             On the other hand, most of the methods referring to columns are useful,
86             since there's no simple way of fetching a column or columns in Perl.
87              
88             =head2 PADDING
89              
90             Because it is intended that the structure can be altered by standard
91             Perl constructions, there is no guarantee that the object is either
92             completely padded out so that every value within the structure's
93             height and width has a value (undefined or not), alternatively
94             completely pruned so that there are as few undefined values as
95             possible. The only padding that must exist is padding to ensure that
96             the row and column indexes are correct for all defined values.
97              
98             Other Perl code could change the padding state at any time, or leave
99             it in an intermediate state (where some padding exists, but the
100             padding is not complete).
101              
102             For example, the following would be valid:
103              
104             $array2d = [
105             [ undef, 1, 2 ],
106             3 ],
107             [ 4, 6, ],
108             ];
109              
110             The columns would be returned as (undef, 3, 4), (1, undef, 6), and (2).
111              
112             There are methods to set padding -- the C method
113             will eliminate padding, and the C method will pad out
114             the array to the highest row and column with a defined value.
115              
116             Methods that retrieve data will prune the data before returning it.
117              
118             Methods that delete rows or columns (del_*, shift_*, pop_*, and in void
119             context, slice) will prune not only the returned data but also the
120             array itself.
121              
122             =cut
123              
124             # core modules
125 20     20   137 use Carp;
  20         58  
  20         1367  
126 20     20   126 use List::Util(qw/max min/);
  20         50  
  20         1777  
127 20     20   8942 use POSIX (qw/floor ceil/);
  20         113148  
  20         109  
128 20     20   25536 use Scalar::Util(qw/blessed reftype/);
  20         41  
  20         1489  
129              
130             # non-core modules
131 20     20   9518 use List::MoreUtils 0.28 (qw/natatime any all none/);
  20         142953  
  20         297  
132 20     20   26773 use Params::Validate(qw/validate ARRAYREF HASHREF/);
  20         154911  
  20         3329  
133              
134             ### Test for Ref::Util and if present, use it
135             BEGIN {
136             my $impl = $ENV{PERL_ARRAY_2D_NO_REF_UTIL}
137 20   66 20   380 || our $NO_REF_UTIL;
138              
139 20 100 66     96 if ( !$impl && eval { require Ref::Util; 1 } ) {
  18         7768  
  18         23849  
140 18         116024 Ref::Util->import(qw/is_arrayref is_plain_arrayref/);
141             # There is a possibility that Ref::Util will change the meaning
142             # of is_arrayref to "is_plain_arrayref" and create a new
143             # is_any_arrayref that means what is_arrayref means now.
144             # Changes will have to be made in that event.
145             }
146             else {
147 2     0   9 *is_plain_arrayref = sub { ref( $_[0] ) eq 'ARRAY' };
  0         0  
148 2     0   13368 *is_arrayref = sub { reftype( $_[0] ) eq 'ARRAY' };
  0            
149             }
150             }
151              
152             ### Test for Unicode::GCString and if present, use it
153              
154             ### First, the variable $text_columns_cr is declared.
155             ### Then, it is set to a reference to code that
156             ### a) determines what the future text_columns code should be,
157             ### b) sets the variable $text_column_cr to point to that new code, and
158             ### c) then jumps to that new code.
159              
160             ### Thus the first time it's run, it basically redefines itself
161             ### to be the proper routine (either one with or without Unicode::GCString).
162              
163             my $text_columns_cr;
164             $text_columns_cr = sub {
165              
166             my $impl = $ENV{PERL_ARRAY_2D_NO_GCSTRING}
167             || our $NO_GCSTRING;
168              
169             if ( !$impl && eval { require Unicode::GCString; 1 } ) {
170             $text_columns_cr = sub {
171              
172             return 0 unless defined $_[0];
173             my $cols = Unicode::GCString->new("$_[0]")->columns;
174             return $cols;
175              
176             # explicit stringification is necessary
177             # since Unicode::GCString doesn't automatically
178             # stringify numbers
179             };
180             }
181             else {
182             $text_columns_cr = sub {
183             return 0 unless defined $_[0];
184             return length( $_[0] );
185             };
186             }
187             goto $text_columns_cr;
188              
189             };
190              
191             =head1 METHODS
192              
193             Some general notes:
194              
195             =over
196              
197             =item *
198              
199             Except for constructor methods, all methods can be called as an object
200             method on a blessed Array::2D object:
201              
202             $array_obj->clone();
203              
204             Or as a class method, if one supplies the array of arrays as the first
205             argument:
206              
207             Array::2D->clone($array);
208              
209             In the latter case, the array of arrays need not be blessed (and will not
210             be blessed by Array::2D).
211              
212             =item *
213              
214             In all cases where an array of arrays is specified as an argument
215             (I), this can be either an Array::2D object or a regular
216             array of arrays data structure that is not an object.
217              
218             =item *
219              
220             Where rows are columns are removed from the array (as with any of the
221             C, C, C methods), time-consuming assemblage of
222             return values is ommitted in void context.
223              
224             =item *
225              
226             Some care is taken to ensure that rows are not autovivified. Normally, if the
227             highest row in an arrayref-of-arrayrefs is 2, and a program
228             attempts to read the value of $aoa->[3]->[$anything], Perl will create
229             an empty third row. This module avoids autovification from just reading data.
230             This is the only advantage of methods like C, C, etc. compared
231             to regular Perl constructions.
232              
233             =item *
234              
235             It is assumed that row and column indexes passed to the methods are integers.
236             If they are negative, they will count from the end instead of
237             the beginning, as in regular Perl array subscripts. Specifying a negative
238             index that is off the beginning of the array (e.g., specifying column -6
239             on an array whose width is 5) will cause an exception to be thrown.
240             This is different than specifying an index is off the end of the array --
241             reading column #5 of a three-column array will return an empty column,
242             and trying to write to tha column will pad out the intervening columns
243             with undefined values.
244              
245             The behavior of the module when anything other than an integer is
246             passed in (strings, undef, floats, NaN, objects, etc.) is unspecified.
247             Don't do that.
248              
249             =back
250              
251             =head2 BASIC CONSTRUCTOR METHODS
252              
253             =over
254              
255             =item B)>
256              
257             =item B )>
258              
259             Returns a new Array::2D object. It accepts a list of array
260             references as arguments, which become the rows of the object.
261              
262             If it receives only one argument, and that argument is an array of
263             arrays -- that is, a reference to an unblessed array, and in turn
264             that array only contains references to unblessed arrays -- then the
265             arrayrefs contained in that structure are made into the rows of a new
266             Array::2D object.
267              
268             If you want it to bless an existing arrayref-of-arrayrefs, use
269             C. If you don't want to reuse the existing arrayrefs as
270             the rows inside the object, use C.
271              
272             If you think it's possible that the detect-an-AoA-structure could
273             give a false positive (you want a new object that might have only one row,
274             where each entry in that row is an reference to an unblessed array),
275             use C<< Array::2D->bless ( [ @your_rows ] ) >>.
276              
277             =cut
278              
279             sub new {
280              
281 0 0 0 0 1   if ( 2 == @_
      0        
282             and is_plain_arrayref( $_[1] )
283 0     0     and all { is_plain_arrayref($_) } @{ $_[1] } )
  0            
284             {
285 0           my $class = shift;
286 0           my $aoa = shift;
287              
288 0           my $self = [ @{$aoa} ];
  0            
289 0           CORE::bless $self, $class;
290 0           return $self;
291             }
292              
293 0           goto &bless;
294              
295             }
296              
297             =item B)>
298              
299             =item B)>
300              
301             Just like new(), except that if passed a single arrayref which contains
302             only other arrayrefs, it will bless the outer arrayref and return it.
303             This saves the time and memory needed to copy the rows.
304              
305             Note that this blesses the original array, so any other references to
306             this data structure will become a reference to the object, too.
307              
308             =cut
309              
310             ## no critic (RequireTrailingCommaAtNewline)
311             # eliminates a PPI false positive -- it thinks bless { ... } is a hashref
312              
313             sub bless { ## no critic (Subroutines::ProhibitBuiltInHomonyms)
314              
315 0     0 1   my $class = shift;
316              
317 0           my @rows = @_;
318              
319 0 0         if ( 0 == @rows ) { # if no arguments, new anonymous AoA
320 0           return $class->empty;
321             }
322              
323 0 0         if ( 1 == @rows ) {
324 0           my $blessing = blessed( $rows[0] );
325 0 0 0       if ( defined($blessing) and $blessing eq $class ) {
326             # already an object
327 0           return $rows[0];
328             }
329              
330 0 0 0       if ( is_plain_arrayref( $rows[0] )
331 0     0     and all { is_plain_arrayref($_) } @{ $rows[0] } )
  0            
332             {
333 0           return CORE::bless $rows[0], $class;
334             }
335             }
336              
337 0 0   0     if ( any { not is_plain_arrayref($_) } @rows ) {
  0            
338 0           croak "Arguments to $class->new or $class->blessed "
339             . 'must be unblessed arrayrefs (rows)';
340             }
341              
342 0           return CORE::bless [@rows], $class;
343              
344             } ## tidy end: sub bless
345              
346             ## use critic
347              
348             =item B
349              
350             Returns a new, empty Array::2D object.
351              
352             =cut
353              
354             sub empty {
355 0     0 1   my $class = shift;
356 0           return CORE::bless [], $class;
357             }
358              
359             =item B)>
360              
361             Takes a flat list and returns it as an Array::2D object,
362             where each row has the number of elements specified. So, for example,
363              
364             Array::2D->new_across (3, qw/a b c d e f g h i j/)
365              
366             returns
367              
368             [
369             [ a, b, c] ,
370             [ d, e, f] ,
371             [ g, h, i] ,
372             [ j ],
373             ]
374              
375             =cut
376              
377             sub new_across {
378 0     0 1   my $class = shift;
379              
380 0           my $quantity = shift;
381 0           my @values = @_;
382              
383 0           my $self;
384 0           my $it = natatime( $quantity, @values );
385 0           while ( my @vals = $it->() ) {
386 0           push @{$self}, [@vals];
  0            
387             }
388              
389 0           CORE::bless $self, $class;
390 0           return $self;
391              
392             }
393              
394             =item B)>
395              
396             Takes a flat list and returns it as an Array::2D object,
397             where each column has the number of elements specified. So, for
398             example,
399              
400             Array::2D->new_down (3, qw/a b c d e f g h i j/)
401              
402             returns
403              
404             [
405             [ a, d, g, j ] ,
406             [ b, e, h ] ,
407             [ c, f, i ] ,
408             ]
409              
410             =cut
411              
412             sub new_down {
413 0     0 1   my $class = shift;
414              
415 0           my $quantity = shift;
416 0           my @values = @_;
417              
418 0           my $self;
419 0           my $it = natatime( $quantity, @values );
420              
421 0           while ( my @vals = $it->() ) {
422 0           for my $i ( 0 .. $#vals ) {
423 0           push @{ $self->[$i] }, $vals[$i];
  0            
424             }
425             }
426              
427 0           CORE::bless $self, $class;
428 0           return $self;
429              
430             }
431              
432             =item B
433              
434             A combination of C and C. Takes three named
435             arguments:
436              
437             =over
438              
439             =item array => I
440              
441             A one-dimensional list of scalars.
442              
443             =item separator => I
444              
445             A scalar to be passed to ->tabulate_equal_width(). The default is
446             a single space.
447              
448             =item width => I
449              
450             The width of the terminal. If not specified, defaults to 80.
451              
452             =back
453              
454             The method determines the number of text columns required, creates an
455             Array::2D object of that number of text columns using new_down, and then
456             returns first the object and then the results of ->tabulate_equal_width()
457             on that object.
458              
459             See L
460             below for information on how the widths of text in text columns
461             are determined.
462              
463             =cut
464              
465             sub new_to_term_width {
466              
467 0     0 1   my $class = shift;
468 0           my %params = validate(
469             @_,
470             { array => { type => ARRAYREF },
471             width => { default => 80 },
472             separator => { default => q[ ] },
473             },
474             );
475              
476 0           my $array = $params{array};
477              
478 0           my $separator = $params{separator};
479 0           my $sepwidth = $text_columns_cr->($separator);
480 0           my $colwidth = $sepwidth + max( map { $text_columns_cr->($_) } @$array );
  0            
481 0   0       my $cols = floor( ( $params{width} + $sepwidth ) / ($colwidth) ) || 1;
482              
483             # add sepwidth there to compensate for the fact that we don't actually
484             # print the separator at the end of the line
485              
486 0           my $rows = ceil( @$array / $cols );
487              
488 0           my $array2d = $class->new_down( $rows, @$array );
489              
490 0           my $tabulated = $array2d->tabulate_equal_width($separator);
491              
492 0           return $array2d, $tabulated;
493              
494             } ## tidy end: sub new_to_term_width
495              
496             =item B<<< new_from_tsv(I) >>>
497              
498             Returns a new object from a string containing tab-separated values.
499             The string is first split into lines (delimited by carriage returns,
500             line feeds, a CR/LF pair, or other characters matching Perl's \R) and
501             then split into values by tabs.
502              
503             If multiple strings are provided, they will be considered additional
504             lines. So, if one has already read a TSV file, one can pass the entire contents,
505             the series of lines in the TSV file, or a combination of two.
506              
507             Note that this is not a routine that reads TSV I, just TSV
508             I, which may or may not have been read from a file. See
509             C()> for a method that reads TSV
510             files (and other kinds).
511              
512             =cut
513              
514             sub new_from_tsv {
515 0     0 1   my $class = shift;
516 0           my @lines = map { split(/\R/) } @_;
  0            
517 0           my $self = [ map { [ split(/\t/) ] } @lines ];
  0            
518              
519 0           CORE::bless $self, $class;
520 0           return $self;
521             }
522              
523             =back
524              
525             =head2 CONSTRUCTOR METHODS THAT READ FILES
526              
527             =over
528              
529             =item B<<< new_from_xlsx(I) >>>
530              
531             This method requires that L
532             be installed on the local system.
533              
534             Returns a new object from a worksheet in an Excel XLSX file, consisting
535             of the rows and columns of that sheet. The I parameter
536             is passed directly to the C<< ->worksheet >> method of
537             C, which accepts a name or an index. If nothing
538             is passed, it requests sheet 0 (the first sheet).
539              
540             =cut
541              
542             sub new_from_xlsx {
543 0     0 1   my $class = shift;
544 0           my $xlsx_filespec = shift;
545 0   0       my $sheet_requested = shift || 0;
546              
547             # || handles empty strings
548              
549 0 0         croak 'No file specified in ' . __PACKAGE__ . '->new_from_xlsx'
550             unless $xlsx_filespec;
551              
552 0           require Spreadsheet::ParseXLSX; ### DEP ###
553              
554 0           my $parser = Spreadsheet::ParseXLSX->new;
555 0           my $workbook = $parser->parse($xlsx_filespec);
556              
557 0 0         if ( !defined $workbook ) {
558 0           croak $parser->error();
559             }
560              
561 0           my $sheet = $workbook->worksheet($sheet_requested);
562              
563 0 0         if ( !defined $sheet ) {
564 0           croak "Sheet $sheet_requested not found in $xlsx_filespec in "
565             . __PACKAGE__
566             . '->new_from_xlsx';
567             }
568              
569 0           my ( $minrow, $maxrow ) = $sheet->row_range();
570 0           my ( $mincol, $maxcol ) = $sheet->col_range();
571              
572 0           my @rows;
573              
574 0           foreach my $row ( $minrow .. $maxrow ) {
575              
576 0           my @cells = map { $sheet->get_cell( $row, $_ ) } ( $mincol .. $maxcol );
  0            
577              
578 0           foreach (@cells) {
579 0 0         if ( defined $_ ) {
580 0           $_ = $_->value;
581             }
582             else {
583 0           $_ = q[];
584             }
585             }
586              
587 0           push @rows, \@cells;
588              
589             }
590              
591 0           return $class->bless( \@rows );
592              
593             } ## tidy end: sub new_from_xlsx
594              
595             =item B<<< new_from_file(I, I) >>>
596              
597             Returns a new object from a file on disk, specified as I.
598              
599             If I is present, then it must be either 'xlsx' or 'tsv', and it
600             will read the file assuming it is of that type.
601              
602             If no I is present, it will attempt to use the file's
603             extension to determine the proper filetype. Any file whose extension is
604             '.xlsx' will be treated as type 'xlsx', and any file whose extension is
605             either '.tab' or '.tsv' will be treated as type 'tsv'.
606              
607             For the moment, it will also assume that a file whose extension is '.txt'
608             is of type 'tsv'. It should be assumed that future versions
609             may attempt to determine whether the file is more likely to be a comma-separated
610             values file instead. To ensure that the file will be treated as tab-separated,
611             pass in a filetype explicitly.
612              
613             If the file type is 'xlsx', this method
614             passes that file on to C and requests the first worksheet.
615              
616             If the file type is 'tsv',
617             it slurps the file in memory and passes the result to C.
618             This uses L, which mus be installed on the system.
619              
620             =cut
621              
622             my $filetype_from_ext_r = sub {
623             my $filespec = shift;
624             return unless $filespec;
625              
626             my ($ext) = $filespec =~ m[
627             [.] # a dot
628             ([^.]+) # one or more non-dot characters
629             \z # end of the string
630             ]x;
631              
632             my $fext = fc($ext);
633              
634             if ( $fext eq fc('xlsx') ) {
635             return 'xlsx';
636             }
637              
638             if ( any { $fext eq fc($_) } qw/tsv tab txt/ ) {
639             return 'tsv';
640             }
641              
642             return;
643              
644             };
645              
646             sub new_from_file {
647 0     0 1   my $class = shift;
648 0           my $filespec = shift;
649 0   0       my $filetype = shift || $filetype_from_ext_r->($filespec);
650              
651 0 0         croak "Cannot determine type of $filespec in "
652             . __PACKAGE__
653             . '->new_from_file'
654             unless $filetype;
655              
656 0 0         if ( $filetype eq 'xlsx' ) {
657 0           return $class->new_from_xlsx($filespec);
658             }
659              
660 0 0         if ( $filetype eq 'tsv' ) {
661 0           require File::Slurper; ### DEP ###
662 0           my $tsv = File::Slurper::read_text($filespec);
663 0           return $class->new_from_tsv($tsv);
664             }
665              
666 0           croak "File type $filetype unrecognized in "
667             . __PACKAGE__
668             . '->new_from_file';
669              
670             } ## tidy end: sub new_from_file
671              
672             ################################################################
673             ### shim allowing being called as either class or object method
674              
675             my $invocant_cr = sub {
676             my $invocant = shift;
677             my $blessing = blessed $invocant;
678              
679             return ( $blessing, $invocant ) if defined $blessing;
680             # invocant is an object blessed into the $blessing class
681              
682             my $array2d = shift;
683             return ( $invocant, $array2d ) if is_arrayref($array2d);
684             # invocant is a class
685              
686             ## no critic (ProhibitMagicNumbers)
687             croak 'No array passed to ' . ( caller(1) )[3];
688              
689             };
690              
691             =back
692              
693             =head2 COPYING AND REARRANGING ARRAYS
694              
695             =over
696              
697             =item B
698              
699             Returns new object which has copies of the data in the 2D array object.
700             The 2D array will be different, but if any of the elements of the 2D
701             array are themselves references, they will refer to the same things as
702             in the original 2D array.
703              
704             =cut
705              
706             sub clone {
707 0     0 1   my ( $class, $self ) = &$invocant_cr;
708 0           my $new = [ map { [ @{$_} ] } @{$self} ];
  0            
  0            
  0            
709 0           CORE::bless $new, $class;
710 0           return $new;
711             }
712              
713             =item B
714              
715             Returns an unblessed array containing the same rows as the 2D
716             array object. If called as a class method and given an argument that is
717             already unblessed, will return the argument. Otherwise will create
718             a new, unblessed array.
719              
720             This is usually pointless, as Perl lets you ignore the object-ness of
721             any object and access the data inside, but sometimes certain modules
722             don't like to break object encapsulation, and this will allow getting
723             around that.
724              
725             Note that while modifying the elements inside the rows will modify the
726             original 2D array, modifying the outer arrayref will not (unless
727             that arrayref was not blessed in the first place). So:
728              
729             my $unblessed = $array2d->unblessed;
730              
731             $unblessed->[0][0] = 'Up in the corner';
732             # modifies original object
733              
734             $unblessed->[0] = [ 'Up in the corner ' , 'Yup'];
735             # does not modify original object
736              
737             This can be confusing, so it's best to avoid modifying the result of
738             C. Use C instead.
739              
740             =cut
741              
742             sub unblessed {
743 0     0 1   my ( $class, $self ) = &$invocant_cr;
744 0 0         return $self if not blessed $self;
745 0           my $new = [ @{$self} ];
  0            
746 0           return $new;
747             }
748              
749             =item B
750              
751             Returns a new, unblessed, array of arrays containing copies of the data
752             in the 2D array object.
753              
754             The array of arrays will be different, but if any of the elements of
755             the 2D array are themselves references, they will refer to the same
756             things as in the original 2D array.
757              
758             =cut
759              
760             sub clone_unblessed {
761 0     0 1   my ( $class, $self ) = &$invocant_cr;
762 0           my $new = [ map { [ @{$_} ] } @{$self} ];
  0            
  0            
  0            
763 0           return $new;
764             }
765              
766             =item B
767              
768             Transposes the array: the elements that used to be
769             in rows are now in columns, and vice versa.
770              
771             In void context, alters the original. Otherwise, creates a new
772             Array::2D object and returns that.
773              
774             The result of transpose() is pruned.
775              
776             =cut
777              
778             sub transpose {
779 0     0 1   my ( $class, $self ) = &$invocant_cr;
780              
781 0 0         unless ( @{$self} ) {
  0            
782 0 0         return $class->empty if defined wantarray;
783 0           return $self;
784             }
785              
786 0           my $new = [];
787              
788 0           foreach my $col ( 0 .. $class->last_col($self) ) {
789 0           push @{$new}, [ map { $_->[$col] } @{$self} ];
  0            
  0            
  0            
790             }
791              
792 0           $class->prune($new);
793              
794             # non-void context: return new object
795 0 0         if ( defined wantarray ) {
796 0           CORE::bless $new, $class;
797 0           return $new;
798             }
799              
800             # void context: alter existing array
801 0           @{$self} = @{$new};
  0            
  0            
802 0           return;
803              
804             } ## tidy end: sub transpose
805              
806             =item B
807              
808             Returns the array as a single, one-dimensional flat list of all the defined
809             values. Note that it does not flatten any arrayrefs that are deep inside
810             the 2D structure -- just the rows and columns of the structure itself.
811              
812             =cut
813              
814             sub flattened {
815 0     0 1   my ( $class, $self ) = &$invocant_cr;
816 0           my @flattened = map { @{$_} } @$self;
  0            
  0            
817 0           return grep { defined $_ } @flattened;
  0            
818             }
819              
820             =back
821              
822             =head2 DIMENSIONS OF THE ARRAY
823              
824             =over
825              
826             =item B
827              
828             Returns a true value if the array is empty, false otherwise.
829              
830             =cut
831              
832             sub is_empty {
833 0     0 1   my ( $class, $self ) = &$invocant_cr;
834 0           return not( scalar @$self );
835             }
836              
837             =item B
838              
839             Returns the number of rows in the array. The same as C.
840              
841             =cut
842              
843             sub height {
844 0     0 1   my ( $class, $self ) = &$invocant_cr;
845 0           return scalar @$self;
846             }
847              
848             =item B
849              
850             Returns the number of columns in the array. (The number of elements in
851             the longest row.)
852              
853             =cut
854              
855             sub width {
856 0     0 1   my ( $class, $self ) = &$invocant_cr;
857 0 0         return 0 unless @{$self};
  0            
858 0           return max( map { scalar @{$_} } @{$self} );
  0            
  0            
  0            
859             }
860              
861             =item B
862              
863             Returns the index of the last row of the array. If the array is
864             empty, returns -1. The same as C<$#{$array}>.
865              
866             =cut
867              
868             sub last_row {
869 0     0 1   my ( $class, $self ) = &$invocant_cr;
870 0           return $#{$self};
  0            
871             }
872              
873             =item B
874              
875             Returns the index of the last column of the array. (The index of the
876             last element in the longest row.) If the array is
877             empty, returns -1.
878              
879             =cut
880              
881             sub last_col {
882 0     0 1   my ( $class, $self ) = &$invocant_cr;
883 0 0         return -1 unless @{$self};
  0            
884 0           return max( map { $#{$_} } @{$self} );
  0            
  0            
  0            
885             }
886              
887             =back
888              
889             =head2 READING ELEMENTS, ROWS, COLUMNS, SLICES
890              
891             =over
892              
893             =item B)>
894              
895             Returns the element in the given row and column. A slower way of
896             saying C<< $array2d->[I][I] >>, except that it avoids
897             autovivification. Like that construct, it will return undef if the element
898             does not already exist.
899              
900             =cut
901              
902             sub element {
903             ## no critic (ProhibitExplicitReturnUndef)
904 0     0 1   my ( $class, $self ) = &$invocant_cr;
905              
906 0           my $row_idx = shift;
907             return undef
908 0 0 0       unless -@$self <= $row_idx and $row_idx <= $#{$self};
  0            
909 0           my $col_idx = shift;
910             return undef
911 0           unless -@{ $self->[$row_idx] } <= $col_idx
912 0 0 0       and $col_idx <= $#{ $self->[$row_idx] };
  0            
913 0           return $self->[$row_idx][$col_idx];
914             }
915              
916             =item B)>
917              
918             Returns the elements in the given row. A slower way of saying C<<
919             @{$array2d->[I]} >>, except that it avoids autovivification.
920              
921             =cut
922              
923             sub row {
924 0     0 1   my ( $class, $self ) = &$invocant_cr;
925 0           my $row_idx = shift;
926             return ()
927             unless -@$self <= $row_idx
928 0 0 0       and $row_idx <= $#{$self};
  0            
929             # if empty, will test (0 <= $col_idx and $col_idx <= -1) which is always false
930 0           my @row = @{ $self->[$row_idx] };
  0            
931 0   0       pop @row while @row and not defined $row[-1]; # prune
932 0           return @row;
933             }
934              
935             =item B)>
936              
937             Returns the elements in the given column.
938              
939             =cut
940              
941             sub col {
942 0     0 1   my ( $class, $self ) = &$invocant_cr;
943              
944 0           my $col_idx = shift;
945 0           my $width = $class->width($self);
946             return ()
947 0 0 0       unless -$width <= $col_idx
948             and $col_idx < $width;
949             # if empty, will test (0 <= $col_idx and $col_idx < 0) which is always false
950              
951 0 0         $col_idx += $width if $col_idx < 0;
952             # make into offset from beginning, not the end
953             # Must do this because otherwise, counts from end of *this row*, not end of
954             # whole array
955              
956             my @col
957 0 0 0       = map { ( 0 <= $col_idx && $col_idx <= $#{$_} ) ? $_->[$col_idx] : undef }
958 0           @{$self};
  0            
959             # the element if it's valid in that row, otherwise undef
960 0   0       pop @col while @col and not defined $col[-1]; # prune
961 0           return @col;
962             } ## tidy end: sub col
963              
964             =item B<< rows(I) >>
965              
966             Returns a new Array::2D object with all the columns of the
967             specified rows.
968              
969             Note that duplicates are not de-duplicated, so the result of
970             $obj->rows(1,1,1) will be three copies of the same row.
971              
972             =cut
973              
974             sub rows {
975 0     0 1   my ( $class, $self ) = &$invocant_cr;
976 0           my @row_indices = @_;
977              
978             my $rows
979             = $class->new(
980 0 0 0       map { ( -@$self <= $_ && $_ <= $#{$self} ) ? $self->[$_] : [] }
  0            
981             @row_indices );
982             # the row if it's a valid row idx, othewise an empty ref
983 0           $rows->prune();
984 0           return $rows;
985             }
986              
987             =item B, ...)>
988              
989             Returns a new Array::2D object with the specified columns. This is transposed
990             from the original array's order, so each column requested will be in its own
991             row.
992              
993             $array = [
994             [ qw/ a b c d / ],
995             [ qw/ j k l m / ],
996             [ qw/ w x y z / ],
997             ];
998             my $cols = Array::2D->cols($array, 1, 2);
999             # $cols = bless [ [ qw/ b k x / ] , [ qw/ c l y / ] ], 'Array::2D';
1000              
1001             Note that duplicates are not de-duplicated, so the result of
1002             $obj->cols(1,1,1) will retrieve three copies of the same column.
1003              
1004             =cut
1005              
1006             sub cols {
1007 0     0 1   my ( $class, $self ) = &$invocant_cr;
1008 0           my @col_indices = @_;
1009              
1010 0           my $cols = [ map { [ $class->col( $self, $_ ) ] } @col_indices ];
  0            
1011              
1012 0           CORE::bless $cols, $class;
1013 0           $cols->prune;
1014 0           return $cols;
1015             }
1016              
1017             =item B, ...)>
1018              
1019             Returns a new Array::2D object with the specified columns of each row.
1020             Unlike C, the result of this method is not transposed.
1021              
1022             $array = [
1023             [ qw/ a b c d / ],
1024             [ qw/ j k l m / ],
1025             [ qw/ w x y z / ],
1026             ];
1027             my $sliced_cols = Array::2D->slice_cols($array, 1, 2);
1028             # $sliced_cols = bless [
1029             # [ qw/ b c / ] ,
1030             # [ qw/ k l / ] ,
1031             # [ qw/ x y / ] ,
1032             # ], 'Array::2D';
1033              
1034             Note that duplicates are not de-duplicated, so the result of
1035             $obj->slice_cols(1,1,1) will retrieve three copies of the same column.
1036              
1037             =cut
1038              
1039             sub slice_cols {
1040 0     0 1   my ( $class, $self ) = &$invocant_cr;
1041 0           my @col_indices = @_;
1042 0           my $width = $class->width($self);
1043 0           for my $col_idx (@col_indices) {
1044 0 0         $col_idx += $width if $col_idx < 0;
1045             }
1046             # must adjust this to whole array width, not just row width
1047              
1048 0           my $return = [];
1049              
1050 0           foreach my $row_r (@$self) {
1051 0           my @new_row;
1052 0           foreach my $col_idx (@col_indices) {
1053 0 0 0       if ( -$width <= $col_idx and $col_idx < $width ) {
1054 0           push @new_row, $row_r->[$col_idx];
1055             }
1056             else {
1057 0           push @new_row, undef;
1058             }
1059             }
1060 0           push @$return, \@new_row;
1061             }
1062              
1063 0           CORE::bless $return, $class;
1064 0           $return->prune;
1065 0           return $return;
1066             } ## tidy end: sub slice_cols
1067              
1068             =item B)>
1069              
1070             Takes a two-dimensional slice of the array; like cutting a rectangle
1071             out of the array.
1072              
1073             In void context, alters the original array, which then will contain
1074             only the area specified; otherwise, creates a new Array::2D
1075             object and returns the object.
1076              
1077             Negative indicies are treated as though they mean that many from the end:
1078             the last item is -1, the second-to-last is -2, and so on.
1079              
1080             Slices are always returned in the order of the original array, so
1081             $obj->slice(0,1,0,1) is the same as $obj->slice(1,0,1,0).
1082              
1083             =cut
1084              
1085             sub slice {
1086 0     0 1   my ( $class, $self ) = &$invocant_cr;
1087              
1088 0           my ( $firstrow, $lastrow, $firstcol, $lastcol, ) = @_;
1089              
1090             ### adjust row indices
1091              
1092 0           my $self_lastrow = $class->last_row($self);
1093              
1094 0           foreach my $row_idx ( $firstrow, $lastrow ) {
1095 0 0         next unless $row_idx < 0;
1096 0           $row_idx += $self_lastrow + 1;
1097             }
1098              
1099             ### adjust col indices
1100              
1101 0           my $self_lastcol = $class->last_col($self);
1102              
1103 0           foreach my $col ( $firstcol, $lastcol ) {
1104 0 0         next unless $col < 0;
1105 0           $col += $self_lastcol + 1;
1106             }
1107              
1108             ### sort indices
1109              
1110 0 0         ( $firstrow, $lastrow ) = ( $lastrow, $firstrow )
1111             if $lastrow < $firstrow;
1112              
1113 0 0         ( $firstcol, $lastcol ) = ( $lastcol, $firstcol )
1114             if $lastcol < $firstcol;
1115              
1116             # if it's specifying an area entirely off the beginning or end
1117             # of the array, return empty
1118 0 0 0       if ( $lastrow < 0
      0        
      0        
1119             or $self_lastrow < $firstrow
1120             or $lastcol < 0
1121             or $self_lastcol < $firstcol )
1122             {
1123 0 0         return $class->empty() if defined wantarray;
1124 0           @{$self} = ();
  0            
1125 0           return;
1126             }
1127              
1128             # otherwise, since it's at least partially in the array, set the rows
1129             # to be within the array.
1130 0 0         $lastrow = $self_lastrow if $self_lastrow < $lastrow;
1131 0 0         $firstrow = 0 if $firstrow < 0;
1132              
1133 0           my $rows = $class->rows( $self, $firstrow .. $lastrow );
1134              
1135             # set the bounds to be within the column of these rows
1136 0 0         $firstcol = 0 if $firstcol < 0;
1137 0           my $rows_lastcol = $class->last_col($rows);
1138 0 0         $lastcol = $rows_lastcol if $rows_lastcol < $lastcol;
1139              
1140 0           my $new = $class->slice_cols( $rows, $firstcol .. $lastcol );
1141 0 0         return $new if defined wantarray;
1142 0           @{$self} = @{$new};
  0            
  0            
1143 0           return;
1144             } ## tidy end: sub slice
1145              
1146             =back
1147              
1148             =head2 SETTING ELEMENTS, ROWS, COLUMNS, SLICES
1149              
1150             None of these methods return anything. At some point it might
1151             be worthwhile to have them return the old values of whatever they changed
1152             (when not called in void context), but they don't do that yet.
1153              
1154             =over
1155              
1156             =item B)>
1157              
1158             Sets the element in the given row and column to the given value.
1159             Just a slower way of saying
1160             C<< $array2d->[I][I] = I >>.
1161              
1162             =cut
1163              
1164             sub set_element {
1165 0     0 1   my ( $class, $self ) = &$invocant_cr;
1166 0           my $row_idx = shift;
1167 0           my $col_idx = shift;
1168 0           $self->[$row_idx][$col_idx] = shift;
1169 0           return;
1170             }
1171              
1172             =item B)>
1173              
1174             Sets the given row to the given set of values.
1175             A slower way of saying C<< {$array2d->[I] = [ @values ] >>.
1176              
1177             =cut
1178              
1179             sub set_row {
1180 0     0 1   my ( $class, $self ) = &$invocant_cr;
1181 0   0       my $row_idx = shift || 0;
1182 0           my @elements = @_;
1183 0 0         return $#{$self} unless @elements;
  0            
1184 0           $self->[$row_idx] = \@elements;
1185 0           return;
1186             }
1187              
1188             =item B)>
1189              
1190             Sets the given column to the given set of values. If more values are given than
1191             there are rows, will add rows; if fewer values than there are rows, will set the
1192             entries in the remaining rows to C.
1193              
1194             =cut
1195              
1196             sub set_col {
1197 0     0 1   my ( $class, $self ) = &$invocant_cr;
1198 0           my $col_idx = shift;
1199 0           my @elements = @_;
1200              
1201             # handle negative col_idx
1202              
1203 0           my $width = $class->width($self);
1204 0 0         return $width unless @elements;
1205              
1206 0 0         if ( $col_idx < -$width ) {
1207 0           croak("$class->set_col: negative index off the beginning of the array");
1208             }
1209 0 0         $col_idx += $width if $col_idx < 0;
1210              
1211 0           for my $row_idx ( 0 .. max( $class->last_row($self), $#elements ) ) {
1212 0           $self->[$row_idx][$col_idx] = $elements[$row_idx];
1213             }
1214 0           return;
1215              
1216             } ## tidy end: sub set_col
1217              
1218             =item B<< set_rows(I) >>
1219              
1220             =item B<< set_rows(I) >>
1221              
1222             Sets the rows starting at the given start row index to the rows given.
1223             So, for example, $obj->set_rows(1, $row_ref_a, $row_ref_b) will set
1224             row 1 of the object to be the elements of $row_ref_a and row 2 to be the
1225             elements of $row_ref_b.
1226              
1227             The arguments after I are passed to C, so it accepts
1228             any of the arguments that C accepts.
1229              
1230             Returns the height of the array.
1231              
1232             =cut
1233              
1234             sub set_rows {
1235 0     0 1   my ( $class, $self ) = &$invocant_cr;
1236 0           my $self_start_row_idx = shift;
1237 0           my $given = $class->new(@_);
1238 0           my @given_rows = @{$given};
  0            
1239 0           for my $given_row_idx ( 0 .. $#given_rows ) {
1240 0           my @elements = @{ $given_rows[$given_row_idx] };
  0            
1241 0           $self->[ $self_start_row_idx + $given_row_idx ] = \@elements;
1242             }
1243 0           return;
1244             }
1245              
1246             =item B...)>
1247              
1248             Sets the columns starting at the given start column index to the columns given.
1249             So, for example, $obj->set_cols(1, $col_ref_a, $col_ref_b) will set
1250             column 1 of the object to be the elemnents of $col_ref_a and column 2 to be the
1251             elements of $col_ref_b.
1252              
1253             =cut
1254              
1255             sub set_cols {
1256 0     0 1   my ( $class, $self ) = &$invocant_cr;
1257 0           my $self_start_col_idx = shift;
1258 0           my @given_cols = @_;
1259 0           my $width;
1260              
1261 0           foreach my $given_col_idx ( 0 .. $#given_cols ) {
1262 0           my @given_elements = @{ $given_cols[$given_col_idx] };
  0            
1263 0           $width = $class->set_col( $self, $self_start_col_idx + $given_col_idx,
1264             @given_elements );
1265             }
1266 0           return;
1267             }
1268              
1269             =item B)>
1270              
1271             =item B)>
1272              
1273             Sets a rectangular segment of the object to have the values of the supplied
1274             rows or array of arrays, beginning at the supplied first row and first column.
1275             The arguments after the row and columns are passed to C, so it accepts
1276             any of the arguments that C accepts.
1277              
1278             =cut
1279              
1280             sub set_slice {
1281 0     0 1   my ( $class, $self ) = &$invocant_cr;
1282              
1283 0           my $class_firstrow = shift;
1284 0           my $class_firstcol = shift;
1285              
1286 0           my $slice = $class->new(@_);
1287 0           my $slice_last_row = $slice->last_row;
1288 0           my $slice_last_col = $slice->last_col;
1289              
1290 0           for my $row_idx ( 0 .. $slice_last_row ) {
1291 0           for my $col_idx ( 0 .. $slice_last_col ) {
1292 0           $self->[ $class_firstrow + $row_idx ][ $class_firstcol + $col_idx ]
1293             = $slice->[$row_idx][$col_idx];
1294             }
1295             }
1296              
1297 0           return;
1298              
1299             } ## tidy end: sub set_slice
1300              
1301             =back
1302              
1303             =head2 INSERTING ROWS AND COLUMNS
1304              
1305             All these methods return the new number of either rows or columns.
1306              
1307             =over
1308              
1309             =item B)>
1310              
1311             Adds the specified elements as a new row at the given index.
1312              
1313             =cut
1314              
1315             sub ins_row {
1316 0     0 1   my ( $class, $self ) = &$invocant_cr;
1317 0           my $row_idx = shift;
1318 0           my @row = @_;
1319              
1320 0 0         if ( $#{$self} < $row_idx ) {
  0            
1321 0           $self->[$row_idx] = [@row];
1322             }
1323             else {
1324 0           splice( @{$self}, $row_idx, 0, [@row] );
  0            
1325             }
1326              
1327 0           return scalar @{$self};
  0            
1328             }
1329              
1330             =item B)>
1331              
1332             Adds the specified elements as a new column at the given index.
1333              
1334             =cut
1335              
1336             sub ins_col {
1337 0     0 1   my ( $class, $self ) = &$invocant_cr;
1338 0           my $col_idx = shift;
1339 0           my @col = @_;
1340              
1341             # handle negative col_idx
1342 0           my $width = $class->width($self);
1343 0 0         return $width unless @col;
1344              
1345 0 0         if ( $col_idx < -$width ) {
1346 0           croak("$class->ins_col: negative index off the beginning of the array");
1347             }
1348 0 0         $col_idx += $width if $col_idx < 0;
1349              
1350 0           my $last_row = max( $class->last_row($self), $#col );
1351             # if this is below the array, extend the array so it is longer
1352 0           $#{$self} = $last_row;
  0            
1353              
1354 0           for my $row_idx ( 0 .. $last_row ) {
1355             # if this is off to the right of this row,
1356 0 0         if ( $#{ $self->[$row_idx] } < $col_idx ) {
  0            
1357             # just set the element
1358 0           $self->[$row_idx][$col_idx] = $col[$row_idx];
1359             }
1360             else {
1361             # otherwise, insert it in using splice
1362 0           splice( @{ $self->[$row_idx] }, $col_idx, 0, $col[$row_idx] );
  0            
1363             }
1364             }
1365              
1366 0 0         return $class->width($self) if defined wantarray;
1367 0           return;
1368             } ## tidy end: sub ins_col
1369              
1370             =item B)>
1371              
1372             Takes the specified array of arrays and inserts them as new rows at the
1373             given index.
1374              
1375             The arguments after the row index are passed to C, so it accepts
1376             any of the arguments that C accepts.
1377              
1378             =cut
1379              
1380             sub ins_rows {
1381 0     0 1   my ( $class, $self ) = &$invocant_cr;
1382 0           my $row_idx = shift;
1383 0           my $given = $class->new(@_);
1384              
1385 0           splice( @{$self}, $row_idx, 0, @$given );
  0            
1386 0           return scalar @{$self};
  0            
1387             }
1388              
1389             =item B)>
1390              
1391             Takes the specified array of arrays and inserts them as new columns at
1392             the given index.
1393              
1394             =cut
1395              
1396             sub ins_cols {
1397 0     0 1   my ( $class, $self ) = &$invocant_cr;
1398 0           my $col_idx = shift;
1399 0           my @cols = @_;
1400              
1401 0           my $last_row = max( $class->last_row($self), map { $#{$_} } @cols );
  0            
  0            
1402              
1403 0           for my $row_idx ( 0 .. $last_row ) {
1404 0           for my $col (@cols) {
1405 0           splice( @{ $self->[$row_idx] }, $col_idx, 0, $col->[$row_idx] );
  0            
1406             }
1407             }
1408 0 0         return $class->width($self) if defined wantarray;
1409 0           return;
1410             }
1411              
1412             =item B)>
1413              
1414             Adds the specified elements as the new first row.
1415              
1416             =cut
1417              
1418             sub unshift_row {
1419 0     0 1   my ( $class, $self ) = &$invocant_cr;
1420 0           my @col_values = @_;
1421 0           return unshift @{$self}, \@col_values;
  0            
1422             }
1423              
1424             =item B)>
1425              
1426             Adds the specified elements as the new first column.
1427              
1428             =cut
1429              
1430             sub unshift_col {
1431 0     0 1   my ( $class, $self ) = &$invocant_cr;
1432 0           my @col_values = @_;
1433 0           return $class->ins_col( $self, 0, @col_values );
1434             }
1435              
1436             =item B)>
1437              
1438             =item B)>
1439              
1440             Takes the specified array of arrays and adds them as new rows before
1441             the beginning of the existing rows. Returns the new number of rows.
1442              
1443             The arguments are passed to C, so it accepts
1444             any of the arguments that C accepts.
1445              
1446             =cut
1447              
1448             sub unshift_rows {
1449 0     0 1   my ( $class, $self ) = &$invocant_cr;
1450 0           my $given = $class->new(@_);
1451 0           return unshift @{$self}, @$given;
  0            
1452             }
1453              
1454             =item B)>
1455              
1456             Takes the specified array of arrays and adds them as new columns,
1457             before the beginning of the existing columns. Returns the new number of
1458             columns.
1459              
1460             =cut
1461              
1462             sub unshift_cols {
1463 0     0 1   my ( $class, $self ) = &$invocant_cr;
1464 0           my @cols = @_;
1465 0           return $class->ins_cols( $self, 0, @cols );
1466             }
1467              
1468             =item B)>
1469              
1470             Adds the specified elements as the new final row. Returns the new
1471             number of rows.
1472              
1473             =cut
1474              
1475             sub push_row {
1476 0     0 1   my ( $class, $self ) = &$invocant_cr;
1477 0           my @col_values = @_;
1478 0           return push @{$self}, \@col_values;
  0            
1479             }
1480              
1481             =item B)>
1482              
1483             Adds the specified elements as the new final column. Returns the new
1484             number of columns.
1485              
1486             =cut
1487              
1488             sub push_col {
1489 0     0 1   my ( $class, $self ) = &$invocant_cr;
1490 0           my @col = @_;
1491 0           my $width = $class->width($self);
1492 0 0         return $width unless @col;
1493              
1494 0           for my $row_idx ( 0 .. max( $class->last_row($self), $#col ) ) {
1495 0           $self->[$row_idx][$width] = $col[$row_idx];
1496             }
1497              
1498 0           return $width + 1; # new width
1499             }
1500              
1501             =item B)>
1502              
1503             =item B)>
1504              
1505             Takes the specified array of arrays and adds them as new rows after the
1506             end of the existing rows. Returns the new number of rows.
1507              
1508             The arguments are passed to C, so it accepts
1509             any of the arguments that C accepts.
1510              
1511             =cut
1512              
1513             sub push_rows {
1514 0     0 1   my ( $class, $self ) = &$invocant_cr;
1515 0           my $rows = $class->new(@_);
1516 0           return push @{$self}, @$rows;
  0            
1517             }
1518              
1519             =item B)>
1520              
1521             Takes the specified array of arrays and adds them as new columns, after
1522             the end of the existing columns. Returns the new number of columns.
1523              
1524             =cut
1525              
1526             sub push_cols {
1527 0     0 1   my ( $class, $self ) = &$invocant_cr;
1528 0           my @cols = @_;
1529 0           my $col_idx = $class->last_col($self);
1530              
1531 0 0         if ( -1 == $col_idx ) {
1532 0           @{$self} = map { [ @{$_} ] } @{$self};
  0            
  0            
  0            
  0            
1533 0 0         return $class->width($self) if defined wantarray;
1534 0           return;
1535             }
1536              
1537 0           my $last_row = max( $class->last_row($self), $#cols );
1538 0           my $last_col = $class->last_col($self);
1539              
1540 0           for my $row_index ( 0 .. $last_row ) {
1541 0           my $row_r = $self->[$row_index];
1542 0 0         if ( not defined $row_r ) {
1543 0           $row_r = $self->[$row_index] = [];
1544             }
1545 0           $#{$row_r} = $last_col; # pad out
  0            
1546 0           push @{$row_r}, @{ $cols[$row_index] };
  0            
  0            
1547             }
1548              
1549 0 0         return $class->width($self) if defined wantarray;
1550 0           return;
1551              
1552             } ## tidy end: sub push_cols
1553              
1554             =back
1555              
1556             =head2 RETRIEVING AND DELETING ROWS AND COLUMNS
1557              
1558             =over
1559              
1560             =item B)>
1561              
1562             Removes the row of the object specified by the index and returns a list
1563             of the elements of that row.
1564              
1565             =cut
1566              
1567             sub del_row {
1568 0     0 1   my ( $class, $self ) = &$invocant_cr;
1569 0           my $row_idx = shift;
1570              
1571 0 0         return () unless @{$self};
  0            
1572 0 0         return () if $class->last_row($self) < $row_idx;
1573              
1574 0 0         if ( defined wantarray ) {
1575 0           my @deleted = $class->row( $self, $row_idx );
1576 0           splice( @{$self}, $row_idx, 1 );
  0            
1577 0           $class->prune($self);
1578 0   0       pop @deleted while @deleted and not defined $deleted[-1]; # prune
1579 0           return @deleted;
1580             }
1581              
1582 0           splice( @{$self}, $row_idx, 1 );
  0            
1583 0           $class->prune($self);
1584 0           return;
1585             }
1586              
1587             =item B)>
1588              
1589             Removes the column of the object specified by the index and returns a
1590             list of the elements of that column.
1591              
1592             =cut
1593              
1594             sub del_col {
1595 0     0 1   my ( $class, $self ) = &$invocant_cr;
1596 0           my $col_idx = shift;
1597              
1598             # handle negative col_idx
1599 0           my $width = $class->width($self);
1600 0 0         return () if $width <= $col_idx;
1601              
1602 0 0         if ( $col_idx < -$width ) {
1603 0           croak("$class->del_col: negative index off the beginning of the array");
1604             }
1605 0 0         $col_idx += $width if $col_idx < 0;
1606              
1607 0           my @deleted;
1608 0 0         if ( defined wantarray ) {
1609 0           @deleted = $class->col( $self, $col_idx );
1610 0   0       pop @deleted while @deleted and not defined $deleted[-1]; # prune
1611             }
1612              
1613 0           foreach my $row ( @{$self} ) {
  0            
1614 0           splice( @{$row}, $col_idx, 1 );
  0            
1615             }
1616 0           $class->prune($self);
1617              
1618 0 0         return @deleted if defined wantarray;
1619 0           return;
1620             } ## tidy end: sub del_col
1621              
1622             =item B, I...)>
1623              
1624             Removes the rows of the object specified by the indices. Returns an
1625             Array::2D object of those rows.
1626              
1627             =cut
1628              
1629             sub del_rows {
1630 0     0 1   my ( $class, $self ) = &$invocant_cr;
1631 0           my @row_idxs = @_;
1632              
1633 0 0         unless (@$self) {
1634 0 0         return $class->empty if defined wantarray;
1635 0           return;
1636             }
1637              
1638 0           my $deleted;
1639 0 0         if ( defined wantarray ) {
1640 0           $deleted = $class->rows( $self, @row_idxs );
1641             }
1642              
1643 0           foreach my $row_idx (@row_idxs) {
1644 0           splice( @{$self}, $row_idx, 1 );
  0            
1645             }
1646              
1647 0           $class->prune($self);
1648 0 0         return $deleted if defined wantarray;
1649 0           return;
1650             } ## tidy end: sub del_rows
1651              
1652             =item B, I...)>
1653              
1654             Removes the columns of the object specified by the indices. Returns an
1655             Array::2D object of those columns.
1656              
1657             =cut
1658              
1659             sub del_cols {
1660 0     0 1   my ( $class, $self ) = &$invocant_cr;
1661 0           my @col_idxs = @_;
1662              
1663 0 0         unless (@$self) {
1664 0 0         return $class->empty if defined wantarray;
1665 0           return;
1666             }
1667              
1668 0           my $deleted;
1669 0 0         if ( defined wantarray ) {
1670 0           $deleted = $class->cols( $self, @col_idxs );
1671             }
1672              
1673 0           foreach my $col_idx ( reverse sort @_ ) {
1674 0           $self->del_col($col_idx);
1675             }
1676              
1677 0           $class->prune($self);
1678 0 0         return $deleted if defined wantarray;
1679 0           return;
1680             } ## tidy end: sub del_cols
1681              
1682             =item B
1683              
1684             Removes the first row of the object and returns a list of the elements
1685             of that row.
1686              
1687             =cut
1688              
1689             sub shift_row {
1690 0     0 1   my ( $class, $self ) = &$invocant_cr;
1691 0 0         return () unless @{$self};
  0            
1692 0           my @row = @{ shift @{$self} };
  0            
  0            
1693 0   0       pop @row while @row and not defined $row[-1];
1694 0           $class->prune($self);
1695 0           return @row;
1696             }
1697              
1698             =item B
1699              
1700             Removes the first column of the object and returns a list of the
1701             elements of that column.
1702              
1703             =cut
1704              
1705             sub shift_col {
1706 0     0 1   my ( $class, $self ) = &$invocant_cr;
1707 0           my @col = map { shift @{$_} } @{$self};
  0            
  0            
  0            
1708 0   0       pop @col while @col and not defined $col[-1]; # prune
1709 0           $class->prune($self);
1710 0           return @col;
1711             }
1712              
1713             =item B
1714              
1715             Removes the last row of the object and returns a list of the elements
1716             of that row.
1717              
1718             =cut
1719              
1720             sub pop_row {
1721 0     0 1   my ( $class, $self ) = &$invocant_cr;
1722 0 0         return () unless @{$self};
  0            
1723 0           my @row = @{ pop @{$self} };
  0            
  0            
1724 0   0       pop @row while @row and not defined $row[-1]; # prune
1725 0           $class->prune($self);
1726 0           return @row;
1727             }
1728              
1729             =item B
1730              
1731             Removes the last column of the object and returns a list of the
1732             elements of that column.
1733              
1734             =cut
1735              
1736             sub pop_col {
1737 0     0 1   my ( $class, $self ) = &$invocant_cr;
1738 0 0         return () unless @{$self};
  0            
1739 0           my $last_col = $class->last_col($self);
1740 0 0         return () if -1 == $last_col;
1741 0           $class->prune($self);
1742 0           return $class->del_col( $self, $last_col );
1743             }
1744              
1745             =back
1746              
1747             =head2 ADDING OR REMOVING PADDING
1748              
1749             Padding, here, means empty values beyond
1750             the last defined value of each column or row. What counts as "empty"
1751             depends on the method being used.
1752              
1753             =over
1754              
1755             =item B
1756              
1757             Occasionally an array of arrays can end up with final rows or columns
1758             that are entirely undefined. For example:
1759              
1760             my $obj = Array::2D->new ( [ qw/a b c/] , [ qw/f g h/ ]);
1761             $obj->[0][4] = 'e';
1762             $obj->[3][0] = 'k';
1763              
1764             # a b c undef e
1765             # f g h
1766             # (empty)
1767             # k
1768              
1769             $obj->pop_row();
1770             $obj->pop_col();
1771              
1772             # a b c undef
1773             # f g h
1774             # (empty)
1775              
1776             That would yield an object with four columns, but in which the last
1777             column and last row (each with index 3) consists of only undefined
1778             values.
1779              
1780             The C method eliminates these entirely undefined or empty
1781             columns and rows at the end of the object.
1782              
1783             In void context, alters the original object. Otherwise, creates a new
1784             Array::2D object and returns the object.
1785              
1786             =cut
1787              
1788             sub prune {
1789 0     0 1   my ( $class, $self ) = &$invocant_cr;
1790 0     0     my $callback = sub { !defined $_ };
  0            
1791 0           return $class->prune_callback( $self, $callback );
1792             }
1793              
1794             =item B
1795              
1796             Like C, but treats not only undefined values as blank, but also
1797             empty strings.
1798              
1799             =cut
1800              
1801             sub prune_empty {
1802 0     0 1   my ( $class, $self ) = &$invocant_cr;
1803 0 0   0     my $callback = sub { not defined $_ or $_ eq q[] };
  0            
1804 0           return $class->prune_callback( $self, $callback );
1805             }
1806              
1807             =item B
1808              
1809             Like C, but treats not only undefined values as blank, but also
1810             strings that are empty or that consist solely of white space.
1811              
1812             =cut
1813              
1814             sub prune_space {
1815 0     0 1   my ( $class, $self ) = &$invocant_cr;
1816 0 0   0     my $callback = sub { not defined $_ or m[\A \s* \z]x };
  0            
1817 0           return $class->prune_callback( $self, $callback );
1818             }
1819              
1820             =item B)>
1821              
1822             Like C, but calls the for each element, setting $_ to
1823             each element. If the callback code returns true, the value is
1824             considered padding, and is removed if it's beyond the last non-padding
1825             value at the end of a column or row.
1826              
1827             For example, this would prune values that were undefined, the empty
1828             string, or zero:
1829              
1830             my $callback = sub {
1831             ! defined $_ or $_ eq q[] or $_ == 0;
1832             }
1833             $obj->prune_callback($callback);
1834              
1835             In void context, alters the original object. Otherwise, creates a new
1836             Array::2D object and returns the object.
1837              
1838             Completely empty rows cannot be sent to the callback function,
1839             so those are always removed.
1840              
1841             =cut
1842              
1843             sub prune_callback {
1844 0     0 1   my ( $class, $orig ) = &$invocant_cr;
1845 0           my $callback = shift;
1846 0           my $self;
1847              
1848 0 0         if ( defined wantarray ) {
1849 0           $self = $class->clone($orig);
1850             }
1851             else {
1852 0           $self = $orig;
1853             }
1854              
1855             # remove final blank rows
1856 0   0       while (
      0        
1857 0           @{$self}
1858             and ( not defined $self->[-1]
1859             or 0 == @{ $self->[-1] }
1860 0     0     or all { $callback->() } @{ $self->[-1] } )
1861             )
1862             {
1863 0           pop @{$self};
  0            
1864             }
1865              
1866             # return if it's all blank
1867 0 0         return $self unless ( @{$self} );
  0            
1868              
1869             # remove final blank columns
1870              
1871 0           foreach my $row_r ( @{$self} ) {
  0            
1872 0           while ( @{$row_r} ) {
  0            
1873 0           local $_ = $row_r->[-1];
1874 0 0         last if not $callback->();
1875 0           pop @$row_r;
1876             }
1877             }
1878              
1879 0           return $self;
1880             } ## tidy end: sub prune_callback
1881              
1882             =item B)>
1883              
1884             The opposite of C, this pads out the array so every column
1885             has the same number of elements. If provided, the added elements are
1886             given the value provided; otherwise, they are set to undef.
1887              
1888             =cut
1889              
1890             sub pad {
1891 0     0 1   my ( $class, $orig ) = &$invocant_cr;
1892 0           my $padding = shift;
1893 0           my $self;
1894 0 0         if ( defined wantarray ) {
1895 0           $self = $class->clone($orig);
1896             }
1897             else {
1898 0           $self = $orig;
1899             }
1900 0           my $last_col = $class->last_col($self);
1901              
1902 0 0         if ( not defined $padding ) {
1903 0           foreach (@$self) {
1904 0           $#{$_} = $last_col;
  0            
1905             }
1906             }
1907             else {
1908 0           foreach (@$self) {
1909 0           push @$_, $padding while $#{$_} < $last_col;
  0            
1910             }
1911             }
1912              
1913 0           return $self;
1914              
1915             } ## tidy end: sub pad
1916              
1917             =back
1918              
1919             =head2 MODIFYING EACH ELEMENT
1920              
1921             Each of these methods alters the original array in void context.
1922             If not in void context, creates a new Array::2D object and returns
1923             the object.
1924              
1925             =over
1926              
1927             =item B)>
1928              
1929             Calls the C<$code_ref> for each element, aliasing $_ to each element in
1930             turn. This allows an operation to be performed on every element.
1931              
1932             For example, this would lowercase every element in the array (assuming
1933             all values are defined):
1934              
1935             $obj->apply(sub {lc});
1936              
1937             If an entry in the array is undefined, it will still be passed to the
1938             callback.
1939              
1940             For each invocation of the callback, @_ is set to the row and column
1941             indexes (0-based).
1942              
1943             =cut
1944              
1945             sub apply {
1946 0     0 1   my ( $class, $orig ) = &$invocant_cr;
1947 0           my $callback = shift;
1948 0           my $self;
1949              
1950 0 0         if ( defined wantarray ) {
1951 0           $self = $class->clone($orig);
1952             }
1953             else {
1954 0           $self = $orig;
1955             }
1956              
1957 0           for my $row ( @{$self} ) {
  0            
1958 0           for my $idx ( 0 .. $#{$row} ) {
  0            
1959 0           for ( $row->[$idx] ) {
1960             # localize $_ to $row->[$idx]. Autovivifies the row.
1961 0           $callback->( $row, $idx );
1962             }
1963             }
1964             }
1965 0           return $self;
1966             } ## tidy end: sub apply
1967              
1968             =item B
1969              
1970             Removes white space, if present, from the beginning and end of each
1971             element in the array.
1972              
1973             =cut
1974              
1975             sub trim {
1976 0     0 1   my ( $class, $self ) = &$invocant_cr;
1977              
1978             my $callback = sub {
1979 0 0   0     return unless defined;
1980 0           s/\A\s+//;
1981 0           s/\s+\z//;
1982 0           return;
1983 0           };
1984              
1985 0           return $class->apply( $self, $callback );
1986             }
1987              
1988             =item B
1989              
1990             Removes white space from the end of each element in the array.
1991              
1992             In void context, alters the original object. Otherwise, creates a new
1993             Array::2D object and returns the object.
1994              
1995             =cut
1996              
1997             sub trim_right {
1998 0     0 1   my ( $class, $self ) = &$invocant_cr;
1999              
2000             my $callback = sub {
2001 0 0   0     return unless defined;
2002 0           s/\s+\z//;
2003 0           return;
2004 0           };
2005              
2006 0           return $class->apply( $self, $callback );
2007             }
2008              
2009             =item B
2010              
2011             Replaces undefined values with the empty string.
2012              
2013             =cut
2014              
2015             sub define {
2016 0     0 1   my ( $class, $self ) = &$invocant_cr;
2017              
2018             my $callback = sub {
2019 0   0 0     $_ //= q[];
2020 0           };
2021 0           return $class->apply( $self, $callback );
2022             }
2023              
2024             =back
2025              
2026             =head2 TRANSFORMING ARRAYS INTO OTHER STRUCTURES
2027              
2028             =over
2029              
2030             =item B)>
2031              
2032             Returns a hash reference. The values of the specified
2033             column of the array become the keys of the hash. The values of the hash
2034             are arrayrefs containing the elements
2035             of the rows of the array, with the value in the key column removed.
2036              
2037             If the key column is not specified, the first column is used for the
2038             keys.
2039              
2040             So:
2041              
2042             $obj = Array::2D->new([qw/a 1 2/],[qw/b 3 4/]);
2043             $hashref = $obj->hash_of_rows(0);
2044             # $hashref = { a => [ '1' , '2' ] , b => [ '3' , '4' ] }
2045              
2046             =cut
2047              
2048             sub hash_of_rows {
2049 0     0 1   my ( $class, $self ) = &$invocant_cr;
2050 0           my $col = shift;
2051              
2052 0           my %hash;
2053              
2054 0 0         if ($col) {
2055 0           for my $row_r ( @{$self} ) {
  0            
2056 0           my @row = @{$row_r};
  0            
2057 0           my $key = splice( @row, $col, 1 );
2058 0           $hash{$key} = \@row;
2059             }
2060             }
2061             else {
2062              
2063 0           for my $row_r ( @{$self} ) {
  0            
2064 0           my @row = @{$row_r};
  0            
2065 0           my $key = shift @row;
2066 0           $hash{$key} = \@row;
2067             }
2068              
2069             }
2070              
2071 0           return \%hash;
2072             } ## tidy end: sub hash_of_rows
2073              
2074             =item B)>
2075              
2076             Like C, but accepts a key column and a value column, and
2077             the values are not whole rows but only single elements.
2078              
2079             So:
2080              
2081             $obj = Array::2D->new([qw/a 1 2/],[qw/b 3 4/]);
2082             $hashref = $obj->hash_of_row_elements(0, 1);
2083             # $hashref = { a => '1' , b => '3' }
2084              
2085             If neither key column nor value column are specified, column 0 will be
2086             used for the key and the column 1 will be used for the value.
2087              
2088             If the key column is specified but the value column is not, then the
2089             first column that is not the key column will be used as the value
2090             column. (In other words, if the key column is column 0, then column 1
2091             will be used as the value; otherwise column 0 will be used as the
2092             value.)
2093              
2094             =cut
2095              
2096             sub hash_of_row_elements {
2097 0     0 1   my ( $class, $self ) = &$invocant_cr;
2098              
2099 0           my ( $keycol, $valuecol );
2100 0 0         if (@_) {
2101 0           $keycol = shift;
2102 0 0 0       $valuecol = shift // ( 0 == $keycol ? 1 : 0 );
2103              
2104             # $valuecol defaults to first column that is not the same as $keycol
2105             }
2106             else {
2107 0           $keycol = 0;
2108 0           $valuecol = 1;
2109             }
2110              
2111 0           my %hash;
2112 0           for my $row_r ( @{$self} ) {
  0            
2113 0           $hash{ $row_r->[$keycol] } = $row_r->[$valuecol];
2114             }
2115              
2116 0           return \%hash;
2117             } ## tidy end: sub hash_of_row_elements
2118              
2119             =back
2120              
2121             =head2 TABULATING INTO COLUMNAR OUTPUT
2122              
2123             If the L module can be loaded,
2124             its C method will be used to determine the width of each
2125             character. This will treat composed accented characters and
2126             double-width Asian characters correctly.
2127              
2128             Otherwise, Array::2D will use Perl's C function.
2129              
2130             =over
2131              
2132             =item B)>
2133              
2134             Returns an arrayref of strings, where each string consists of the
2135             elements of each row, padded with enough spaces to ensure that each
2136             column has a consistent width.
2137              
2138             The columns will be separated by whatever string is passed to
2139             C. If nothing is passed, a single space will be used.
2140              
2141             So, for example,
2142              
2143             $obj = Array::2D->new([qw/a bbb cc/],[qw/dddd e f/]);
2144             $arrayref = $obj->tabulate();
2145              
2146             # $arrayref = [ 'a bbb cc' ,
2147             # 'dddd e f'
2148             # ];
2149              
2150             Completely empty columns and rows will be removed.
2151              
2152             =item B)>
2153              
2154             Like C, but instead of each column having its own width,
2155             all columns have the same width.
2156              
2157             =cut
2158              
2159             my $prune_space_list_cr = sub {
2160             my @cells = @_;
2161              
2162             pop @cells
2163             while @cells
2164             and (not defined $cells[-1]
2165             or $cells[-1] eq q[]
2166             or $cells[-1] =~ m/\A\s*\z/ );
2167              
2168             return @cells;
2169             };
2170              
2171             {
2172             my $equal_width;
2173              
2174             my $tabulate_cr = sub {
2175             my ( $class, $orig ) = &$invocant_cr;
2176             my $self = $class->define($orig);
2177              
2178             my $separator = shift // q[ ];
2179             my @length_of_col;
2180             my $maxwidths = 0;
2181              
2182             foreach my $row ( @{$self} ) {
2183             my @cells = @{$row};
2184             for my $this_col ( 0 .. $#cells ) {
2185             my $thislength = $text_columns_cr->( $cells[$this_col] );
2186              
2187             $maxwidths = max( $maxwidths, $thislength ) if $equal_width;
2188             $length_of_col[$this_col] = $thislength
2189             if ( not $length_of_col[$this_col]
2190             or $length_of_col[$this_col] < $thislength );
2191             }
2192             }
2193              
2194             my @lines;
2195              
2196             foreach my $record_r ( @{$self} ) {
2197             my @cells = $prune_space_list_cr->( @{$record_r} );
2198              
2199             # prune trailing cells
2200              
2201             next unless @cells; # skip blank rows
2202              
2203             for my $this_col ( reverse( 0 .. ( $#cells - 1 ) ) ) {
2204             if ( 0 == $length_of_col[$this_col] ) {
2205             splice @cells, $this_col, 1;
2206             next;
2207             }
2208             # delete blank columns so it doesn't add the separator
2209              
2210             my $width
2211             = $equal_width ? $maxwidths : $length_of_col[$this_col];
2212              
2213             #$cells[$this_col]
2214             # = sprintf( '%-*s', $width, $cells[$this_col] );
2215            
2216             my $spaces = $width - $text_columns_cr->( $cells[$this_col]);
2217             $cells[$this_col] .= ( ' ' x $spaces) if $spaces > 0;
2218             }
2219             push @lines, join( $separator, @cells );
2220              
2221             } ## tidy end: foreach my $record_r ( @{$self...})
2222              
2223             return \@lines;
2224              
2225             };
2226              
2227             sub tabulate {
2228 0     0 1   $equal_width = 0;
2229 0           goto $tabulate_cr;
2230             }
2231              
2232             sub tabulate_equal_width {
2233 0     0 1   $equal_width = 1;
2234 0           goto $tabulate_cr;
2235             }
2236              
2237             }
2238              
2239             =item B)>
2240              
2241             Like C, but returns the data as a single string, using
2242             line feeds as separators of rows, suitable for sending to a terminal.
2243              
2244             =cut
2245              
2246             sub tabulated {
2247 0     0 1   my ( $class, $self ) = &$invocant_cr;
2248 0           my $lines_r = $class->tabulate( $self, @_ );
2249 0           return join( "\n", @$lines_r ) . "\n";
2250             }
2251              
2252             =back
2253              
2254             =head2 SERIALIZING AND OUTPUT TO FILES
2255              
2256             =over
2257              
2258             =item B<< tsv_lines(I) >>
2259              
2260             Returns a list of strings in list context, or an arrayref of strings in
2261             scalar context. The elements of each row are present in the string,
2262             separated by tab characters.
2263              
2264             If there are any arguments, they will be used first as the first
2265             row of text. The idea is that these will be the headers of the
2266             columns. It's not really any different than putting the column
2267             headers as the first element of the data, but frequently these are
2268             stored separately. If there is only one element and it is a reference
2269             to an array, that array will be used as the first row of text.
2270              
2271             If tabs are present in any element,
2272             they will be replaced by the Unicode Replacement Character, U+FFFD.
2273              
2274             =cut
2275              
2276             =item B<< tsv(I) >>
2277              
2278             Returns a single string with the elements of each row delimited by
2279             tabs, and rows delimited by line feeds.
2280              
2281             If there are any arguments, they will be used first as the first
2282             row of text. The idea is that these will be the headers of the
2283             columns. It's not really any different than putting the column
2284             headers as the first element of the data, but frequently these are
2285             stored separately. If there is only one element and it is a reference
2286             to an array, that array will be used as the first row of text.
2287              
2288             If tabs or line feeds are present in any element,
2289             they will be replaced by the Unicode Replacement Character, U+FFFD.
2290              
2291             =cut
2292              
2293             sub tsv_lines {
2294              
2295 0     0 1   my ( $class, $self ) = &$invocant_cr;
2296 0           my @rows = @$self;
2297              
2298 0           my @lines;
2299              
2300 0           my @headers = @_;
2301 0 0         if (@headers) {
2302 0 0 0       if ( 1 == @headers and is_plain_arrayref( $headers[0] ) ) {
2303 0           unshift @rows, $headers[0];
2304             }
2305             else {
2306 0           unshift @rows, \@headers;
2307             }
2308             }
2309              
2310 0           my $carped;
2311 0           foreach my $row (@rows) {
2312 0           my @cells = @{$row};
  0            
2313 0           foreach (@cells) {
2314 0   0       $_ //= q[];
2315 0           my $substitutions = s/\t/\x{FFFD}/g;
2316 0 0 0       if ( $substitutions and not $carped ) {
2317 0           carp 'Tab character found converting to tab-separated values. '
2318             . 'Replaced with REPLACEMENT CHARACTER';
2319 0           $carped = 1;
2320             }
2321             }
2322              
2323 0           @cells = $prune_space_list_cr->(@cells);
2324              
2325 0           my $line = join( "\t", @cells );
2326 0           push @lines, $line;
2327             }
2328              
2329 0 0         return wantarray ? @lines : \@lines;
2330              
2331             } ## tidy end: sub tsv_lines
2332              
2333             sub tsv {
2334              
2335             # tab-separated-values,
2336             # suitable for something like File::Slurper::write_text
2337              
2338             # converts line feeds, tabs, and carriage returns to the Replacement
2339             # Character.
2340              
2341 0     0 1   my ( $class, $self ) = &$invocant_cr;
2342              
2343 0           my $lines_r = $class->tsv_lines( $self, @_ );
2344              
2345 0           my $carped;
2346 0           foreach my $line (@$lines_r) {
2347 0           my $substitutions = $line =~ s/\n/\x{FFFD}/g;
2348 0 0 0       if ( $substitutions and not $carped ) {
2349 0           carp 'Line feed character found assembling tab-separated values. '
2350             . 'Replaced with REPLACEMENT CHARACTER';
2351 0           $carped = 1;
2352             }
2353             }
2354 0           return join( "\n", @$lines_r ) . "\n";
2355              
2356             } ## tidy end: sub tsv
2357              
2358             =item B<< file(...) >>
2359              
2360             Accepts a file specification and creates a new file at that location
2361             containing the data in the 2D array.
2362              
2363             This method uses named parameters.
2364              
2365             =over
2366              
2367             =item type
2368              
2369             This parameter is the file's type. Currently, the types recognized are
2370             'tsv' for tab-separated values, and 'xlsx' for Excel XLSX. If the type
2371             is not given, it attempts to determine the type from the file
2372             extension, which can be (case-insensitively) 'xlsx' for Excel XLSX
2373             files or 'tab', 'tsv' or 'txt' for tab-separated value files.
2374              
2375             (If other text file formats are someday added, either they will have
2376             to have different extensions, or an explicit type must be passed
2377             to force that type to have a ".txt" extension.
2378              
2379             =item output_file
2380              
2381             This mandatory parameter contains the file specification.
2382              
2383             =item headers
2384              
2385             This parameter is optional. If present, it contains an array reference
2386             to be used as the first row in the ouptut file.
2387              
2388             The idea is that these will be the headers of the columns. It's not
2389             really any different than putting the column headers as the first
2390             element of the data, but frequently these are stored separately.
2391              
2392             =back
2393              
2394             =cut
2395              
2396             sub file {
2397 0     0 1   my ( $class, $self ) = &$invocant_cr;
2398              
2399 0           my %params = validate(
2400             @_,
2401             { headers => { type => ARRAYREF, optional => 1 },
2402             output_file => 1,
2403             type => 0,
2404             },
2405             );
2406 0           my $output_file = $params{output_file};
2407 0   0       my $type = $params{type} || $filetype_from_ext_r->($output_file);
2408              
2409 0 0         croak "Cannot determine type of $output_file in " . __PACKAGE__ . '->file'
2410             unless $type;
2411              
2412 0 0         if ( $type eq 'xlsx' ) {
2413 0           $class->xlsx( $self, \%params );
2414 0           return;
2415             }
2416 0 0         if ( $type eq 'tsv' ) {
2417 0           my $text = $class->tsv($self);
2418              
2419 0 0         if ( $params{headers} ) {
2420 0           $text = join( "\t", @{ $params{headers} } ) . "\n" . $text;
  0            
2421             }
2422              
2423 0           require File::Slurper;
2424 0           File::Slurper::write_text( $output_file, $text );
2425 0           return;
2426             }
2427 0           croak "Unrecognized type $type in " . __PACKAGE__ . '->file';
2428             } ## tidy end: sub file
2429              
2430             =item B<< xlsx(...) >>
2431              
2432             Accepts a file specification and creates a new Excel XLSX file at that
2433             location, with one sheet, containing the data in the 2D array.
2434              
2435             This method uses named parameters.
2436              
2437             =over
2438              
2439             =item output_file
2440              
2441             This mandatory parameter contains the file specification.
2442              
2443             =item headers
2444              
2445             This parameter is optional. If present, it contains an array reference
2446             to be used as the first row in the Excel file.
2447              
2448             The idea is that these will be the headers of the columns. It's not
2449             really any different than putting the column headers as the first
2450             element of the data, but frequently these are stored separately. At
2451             this point no attempt is made to make them bold or anything like that.
2452              
2453             =item format
2454              
2455             This parameter is optional. If present, it contains a hash reference,
2456             with format parameters as specified by Excel::Writer::XLSX.
2457              
2458             =back
2459              
2460             =cut
2461              
2462             sub xlsx {
2463 0     0 1   my ( $class, $self ) = &$invocant_cr;
2464 0           my %params = validate(
2465             @_,
2466             { headers => { type => ARRAYREF, optional => 1 },
2467             format => { type => HASHREF, optional => 1 },
2468             output_file => 1,
2469             },
2470             );
2471              
2472 0           my $output_file = $params{output_file};
2473 0           my $format_properties = $params{format};
2474 0           my @headers;
2475 0 0         if ( $params{headers} ) {
2476 0           @headers = @{ $params{headers} };
  0            
2477             }
2478              
2479 0           require Excel::Writer::XLSX; ### DEP ###
2480              
2481 0           my $workbook = Excel::Writer::XLSX->new($output_file);
2482             ## no critic (Variables::ProhibitPunctuationVars]
2483 0 0         croak "Can't open $output_file for writing: $!"
2484             unless defined $workbook;
2485             ## use critic
2486 0           my $sheet = $workbook->add_worksheet();
2487 0           my @format;
2488              
2489 0 0         if ( defined $format_properties ) {
2490 0           push @format, $workbook->add_format(%$format_properties);
2491             }
2492              
2493             # an array @format is used because if it were a scalar, it would be undef,
2494             # where what we want if it is empty is no value at all
2495              
2496 0 0         my $unblessed = blessed $self ? $self->unblessed : $self;
2497              
2498             # Excel::Writer::XLSX checks 'ref' and not 'reftype'
2499              
2500 0 0         if (@headers) {
2501 0           $sheet->write_row( 0, 0, \@headers, @format );
2502 0           $sheet->write_col( 1, 0, $unblessed, @format );
2503             }
2504             else {
2505 0           $sheet->write_col( 0, 0, $unblessed, @format );
2506             }
2507              
2508 0           return $workbook->close();
2509              
2510             } ## tidy end: sub xlsx
2511              
2512             1;
2513              
2514             __END__