File Coverage

blib/lib/Array/2D.pm
Criterion Covered Total %
statement 33 721 4.5
branch 2 208 0.9
condition 4 103 3.8
subroutine 10 92 10.8
pod 70 70 100.0
total 119 1194 9.9


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   398490 use 5.008001;
  20         84  
5 20     20   221 use strict;
  20         45  
  20         456  
6 20     20   139 use warnings;
  20         70  
  20         1427  
7              
8             our $VERSION = '0.001_004';
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_004
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         61  
  20         1500  
126 20     20   130 use List::Util(qw/max min/);
  20         44  
  20         1918  
127 20     20   10022 use POSIX (qw/floor ceil/);
  20         117853  
  20         109  
128 20     20   25015 use Scalar::Util(qw/blessed reftype/);
  20         44  
  20         1520  
129              
130             # non-core modules
131 20     20   9174 use List::MoreUtils 0.28 (qw/natatime any all none/);
  20         144239  
  20         264  
132 20     20   26873 use Params::Validate(qw/validate ARRAYREF HASHREF/);
  20         163998  
  20         3188  
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   340 || our $NO_REF_UTIL;
138              
139 20 100 66     88 if ( !$impl && eval { require Ref::Util; 1 } ) {
  18         7151  
  18         23472  
140 18         110211 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   8 *is_plain_arrayref = sub { ref( $_[0] ) eq 'ARRAY' };
  0         0  
148 2     0   12479 *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 and then split into values by tabs.
500              
501             Under perls earlier than 5.10.0, lines must be separated by a line feed.
502             Under perl 5.10 or later, lines can be separated by by carriage returns,
503             line feeds, a CR/LF pair, or other characters matching Perl's \R (see
504             L).
505              
506             If multiple strings are provided, they will be considered additional lines. So,
507             if one has already read a TSV file, one can pass the entire contents, the
508             series of lines in the TSV file, or a combination of two.
509              
510             Note that this is not a routine that reads TSV I, just TSV
511             I, which may or may not have been read from a file. See
512             C()> for a method that reads TSV
513             files (and other kinds).
514              
515             =cut
516              
517             sub new_from_tsv {
518 0     0 1   my $class = shift;
519 0           my @lines;
520 0 0         if ( $] lt '5.010') {
521 0           @lines = map { split(/\n/) } @_;
  0            
522             } else {
523 0           @lines = map { split(/\R/) } @_;
  0            
524             }
525              
526 0           my $self = [ map { [ split(/\t/) ] } @lines ];
  0            
527              
528 0           CORE::bless $self, $class;
529 0           return $self;
530             }
531              
532             =back
533              
534             =head2 CONSTRUCTOR METHODS THAT READ FILES
535              
536             =over
537              
538             =item B<<< new_from_xlsx(I) >>>
539              
540             This method requires that L
541             be installed on the local system.
542              
543             Returns a new object from a worksheet in an Excel XLSX file, consisting
544             of the rows and columns of that sheet. The I parameter
545             is passed directly to the C<< ->worksheet >> method of
546             C, which accepts a name or an index. If nothing
547             is passed, it requests sheet 0 (the first sheet).
548              
549             =cut
550              
551             sub new_from_xlsx {
552 0     0 1   my $class = shift;
553 0           my $xlsx_filespec = shift;
554 0   0       my $sheet_requested = shift || 0;
555              
556             # || handles empty strings
557              
558 0 0         croak 'No file specified in ' . __PACKAGE__ . '->new_from_xlsx'
559             unless $xlsx_filespec;
560              
561 0           require Spreadsheet::ParseXLSX; ### DEP ###
562              
563 0           my $parser = Spreadsheet::ParseXLSX->new;
564 0           my $workbook = $parser->parse($xlsx_filespec);
565              
566 0 0         if ( !defined $workbook ) {
567 0           croak $parser->error();
568             }
569              
570 0           my $sheet = $workbook->worksheet($sheet_requested);
571              
572 0 0         if ( !defined $sheet ) {
573 0           croak "Sheet $sheet_requested not found in $xlsx_filespec in "
574             . __PACKAGE__
575             . '->new_from_xlsx';
576             }
577              
578 0           my ( $minrow, $maxrow ) = $sheet->row_range();
579 0           my ( $mincol, $maxcol ) = $sheet->col_range();
580              
581 0           my @rows;
582              
583 0           foreach my $row ( $minrow .. $maxrow ) {
584              
585 0           my @cells = map { $sheet->get_cell( $row, $_ ) } ( $mincol .. $maxcol );
  0            
586              
587 0           foreach (@cells) {
588 0 0         if ( defined $_ ) {
589 0           $_ = $_->value;
590             }
591             else {
592 0           $_ = q[];
593             }
594             }
595              
596 0           push @rows, \@cells;
597              
598             }
599              
600 0           return $class->bless( \@rows );
601              
602             } ## tidy end: sub new_from_xlsx
603              
604             =item B<<< new_from_file(I, I) >>>
605              
606             Returns a new object from a file on disk, specified as I.
607              
608             If I is present, then it must be either 'xlsx' or 'tsv', and it
609             will read the file assuming it is of that type.
610              
611             If no I is present, it will attempt to use the file's
612             extension to determine the proper filetype. Any file whose extension is
613             '.xlsx' will be treated as type 'xlsx', and any file whose extension is
614             either '.tab' or '.tsv' will be treated as type 'tsv'.
615              
616             For the moment, it will also assume that a file whose extension is '.txt'
617             is of type 'tsv'. It should be assumed that future versions
618             may attempt to determine whether the file is more likely to be a comma-separated
619             values file instead. To ensure that the file will be treated as tab-separated,
620             pass in a filetype explicitly.
621              
622             If the file type is 'xlsx', this method
623             passes that file on to C and requests the first worksheet.
624              
625             If the file type is 'tsv',
626             it slurps the file in memory and passes the result to C.
627             This uses L, which mus be installed on the system.
628              
629             =cut
630              
631             my $filetype_from_ext_r = sub {
632             my $filespec = shift;
633             return unless $filespec;
634              
635             my ($ext) = $filespec =~ m[
636             [.] # a dot
637             ([^.]+) # one or more non-dot characters
638             \z # end of the string
639             ]x;
640              
641             my $fext = fc($ext);
642              
643             if ( $fext eq fc('xlsx') ) {
644             return 'xlsx';
645             }
646              
647             if ( any { $fext eq fc($_) } qw/tsv tab txt/ ) {
648             return 'tsv';
649             }
650              
651             return;
652              
653             };
654              
655             sub new_from_file {
656 0     0 1   my $class = shift;
657 0           my $filespec = shift;
658 0   0       my $filetype = shift || $filetype_from_ext_r->($filespec);
659              
660 0 0         croak "Cannot determine type of $filespec in "
661             . __PACKAGE__
662             . '->new_from_file'
663             unless $filetype;
664              
665 0 0         if ( $filetype eq 'xlsx' ) {
666 0           return $class->new_from_xlsx($filespec);
667             }
668              
669 0 0         if ( $filetype eq 'tsv' ) {
670 0           require File::Slurper; ### DEP ###
671 0           my $tsv = File::Slurper::read_text($filespec);
672 0           return $class->new_from_tsv($tsv);
673             }
674              
675 0           croak "File type $filetype unrecognized in "
676             . __PACKAGE__
677             . '->new_from_file';
678              
679             } ## tidy end: sub new_from_file
680              
681             ################################################################
682             ### shim allowing being called as either class or object method
683              
684             my $invocant_cr = sub {
685             my $invocant = shift;
686             my $blessing = blessed $invocant;
687              
688             return ( $blessing, $invocant ) if defined $blessing;
689             # invocant is an object blessed into the $blessing class
690              
691             my $array2d = shift;
692             return ( $invocant, $array2d ) if is_arrayref($array2d);
693             # invocant is a class
694              
695             ## no critic (ProhibitMagicNumbers)
696             croak 'No array passed to ' . ( caller(1) )[3];
697              
698             };
699              
700             =back
701              
702             =head2 COPYING AND REARRANGING ARRAYS
703              
704             =over
705              
706             =item B
707              
708             Returns new object which has copies of the data in the 2D array object.
709             The 2D array will be different, but if any of the elements of the 2D
710             array are themselves references, they will refer to the same things as
711             in the original 2D array.
712              
713             =cut
714              
715             sub clone {
716 0     0 1   my ( $class, $self ) = &$invocant_cr;
717 0           my $new = [ map { [ @{$_} ] } @{$self} ];
  0            
  0            
  0            
718 0           CORE::bless $new, $class;
719 0           return $new;
720             }
721              
722             =item B
723              
724             Returns an unblessed array containing the same rows as the 2D
725             array object. If called as a class method and given an argument that is
726             already unblessed, will return the argument. Otherwise will create
727             a new, unblessed array.
728              
729             This is usually pointless, as Perl lets you ignore the object-ness of
730             any object and access the data inside, but sometimes certain modules
731             don't like to break object encapsulation, and this will allow getting
732             around that.
733              
734             Note that while modifying the elements inside the rows will modify the
735             original 2D array, modifying the outer arrayref will not (unless
736             that arrayref was not blessed in the first place). So:
737              
738             my $unblessed = $array2d->unblessed;
739              
740             $unblessed->[0][0] = 'Up in the corner';
741             # modifies original object
742              
743             $unblessed->[0] = [ 'Up in the corner ' , 'Yup'];
744             # does not modify original object
745              
746             This can be confusing, so it's best to avoid modifying the result of
747             C. Use C instead.
748              
749             =cut
750              
751             sub unblessed {
752 0     0 1   my ( $class, $self ) = &$invocant_cr;
753 0 0         return $self if not blessed $self;
754 0           my $new = [ @{$self} ];
  0            
755 0           return $new;
756             }
757              
758             =item B
759              
760             Returns a new, unblessed, array of arrays containing copies of the data
761             in the 2D array object.
762              
763             The array of arrays will be different, but if any of the elements of
764             the 2D array are themselves references, they will refer to the same
765             things as in the original 2D array.
766              
767             =cut
768              
769             sub clone_unblessed {
770 0     0 1   my ( $class, $self ) = &$invocant_cr;
771 0           my $new = [ map { [ @{$_} ] } @{$self} ];
  0            
  0            
  0            
772 0           return $new;
773             }
774              
775             =item B
776              
777             Transposes the array: the elements that used to be
778             in rows are now in columns, and vice versa.
779              
780             In void context, alters the original. Otherwise, creates a new
781             Array::2D object and returns that.
782              
783             The result of transpose() is pruned.
784              
785             =cut
786              
787             sub transpose {
788 0     0 1   my ( $class, $self ) = &$invocant_cr;
789              
790 0 0         unless ( @{$self} ) {
  0            
791 0 0         return $class->empty if defined wantarray;
792 0           return $self;
793             }
794              
795 0           my $new = [];
796              
797 0           foreach my $col ( 0 .. $class->last_col($self) ) {
798 0           push @{$new}, [ map { $_->[$col] } @{$self} ];
  0            
  0            
  0            
799             }
800              
801 0           $class->prune($new);
802              
803             # non-void context: return new object
804 0 0         if ( defined wantarray ) {
805 0           CORE::bless $new, $class;
806 0           return $new;
807             }
808              
809             # void context: alter existing array
810 0           @{$self} = @{$new};
  0            
  0            
811 0           return;
812              
813             } ## tidy end: sub transpose
814              
815             =item B
816              
817             Returns the array as a single, one-dimensional flat list of all the defined
818             values. Note that it does not flatten any arrayrefs that are deep inside
819             the 2D structure -- just the rows and columns of the structure itself.
820              
821             =cut
822              
823             sub flattened {
824 0     0 1   my ( $class, $self ) = &$invocant_cr;
825 0           my @flattened = map { @{$_} } @$self;
  0            
  0            
826 0           return grep { defined $_ } @flattened;
  0            
827             }
828              
829             =back
830              
831             =head2 DIMENSIONS OF THE ARRAY
832              
833             =over
834              
835             =item B
836              
837             Returns a true value if the array is empty, false otherwise.
838              
839             =cut
840              
841             sub is_empty {
842 0     0 1   my ( $class, $self ) = &$invocant_cr;
843 0           return not( scalar @$self );
844             }
845              
846             =item B
847              
848             Returns the number of rows in the array. The same as C.
849              
850             =cut
851              
852             sub height {
853 0     0 1   my ( $class, $self ) = &$invocant_cr;
854 0           return scalar @$self;
855             }
856              
857             =item B
858              
859             Returns the number of columns in the array. (The number of elements in
860             the longest row.)
861              
862             =cut
863              
864             sub width {
865 0     0 1   my ( $class, $self ) = &$invocant_cr;
866 0 0         return 0 unless @{$self};
  0            
867 0           return max( map { scalar @{$_} } @{$self} );
  0            
  0            
  0            
868             }
869              
870             =item B
871              
872             Returns the index of the last row of the array. If the array is
873             empty, returns -1. The same as C<$#{$array}>.
874              
875             =cut
876              
877             sub last_row {
878 0     0 1   my ( $class, $self ) = &$invocant_cr;
879 0           return $#{$self};
  0            
880             }
881              
882             =item B
883              
884             Returns the index of the last column of the array. (The index of the
885             last element in the longest row.) If the array is
886             empty, returns -1.
887              
888             =cut
889              
890             sub last_col {
891 0     0 1   my ( $class, $self ) = &$invocant_cr;
892 0 0         return -1 unless @{$self};
  0            
893 0           return max( map { $#{$_} } @{$self} );
  0            
  0            
  0            
894             }
895              
896             =back
897              
898             =head2 READING ELEMENTS, ROWS, COLUMNS, SLICES
899              
900             =over
901              
902             =item B)>
903              
904             Returns the element in the given row and column. A slower way of
905             saying C<< $array2d->[I][I] >>, except that it avoids
906             autovivification. Like that construct, it will return undef if the element
907             does not already exist.
908              
909             =cut
910              
911             sub element {
912             ## no critic (ProhibitExplicitReturnUndef)
913 0     0 1   my ( $class, $self ) = &$invocant_cr;
914              
915 0           my $row_idx = shift;
916             return undef
917 0 0 0       unless -@$self <= $row_idx and $row_idx <= $#{$self};
  0            
918 0           my $col_idx = shift;
919             return undef
920 0           unless -@{ $self->[$row_idx] } <= $col_idx
921 0 0 0       and $col_idx <= $#{ $self->[$row_idx] };
  0            
922 0           return $self->[$row_idx][$col_idx];
923             }
924              
925             =item B)>
926              
927             Returns the elements in the given row. A slower way of saying C<<
928             @{$array2d->[I]} >>, except that it avoids autovivification.
929              
930             =cut
931              
932             sub row {
933 0     0 1   my ( $class, $self ) = &$invocant_cr;
934 0           my $row_idx = shift;
935             return ()
936             unless -@$self <= $row_idx
937 0 0 0       and $row_idx <= $#{$self};
  0            
938             # if empty, will test (0 <= $col_idx and $col_idx <= -1) which is always false
939 0           my @row = @{ $self->[$row_idx] };
  0            
940 0   0       pop @row while @row and not defined $row[-1]; # prune
941 0           return @row;
942             }
943              
944             =item B)>
945              
946             Returns the elements in the given column.
947              
948             =cut
949              
950             sub col {
951 0     0 1   my ( $class, $self ) = &$invocant_cr;
952              
953 0           my $col_idx = shift;
954 0           my $width = $class->width($self);
955             return ()
956 0 0 0       unless -$width <= $col_idx
957             and $col_idx < $width;
958             # if empty, will test (0 <= $col_idx and $col_idx < 0) which is always false
959              
960 0 0         $col_idx += $width if $col_idx < 0;
961             # make into offset from beginning, not the end
962             # Must do this because otherwise, counts from end of *this row*, not end of
963             # whole array
964              
965             my @col
966 0 0 0       = map { ( 0 <= $col_idx && $col_idx <= $#{$_} ) ? $_->[$col_idx] : undef }
967 0           @{$self};
  0            
968             # the element if it's valid in that row, otherwise undef
969 0   0       pop @col while @col and not defined $col[-1]; # prune
970 0           return @col;
971             } ## tidy end: sub col
972              
973             =item B<< rows(I) >>
974              
975             Returns a new Array::2D object with all the columns of the
976             specified rows.
977              
978             Note that duplicates are not de-duplicated, so the result of
979             $obj->rows(1,1,1) will be three copies of the same row.
980              
981             =cut
982              
983             sub rows {
984 0     0 1   my ( $class, $self ) = &$invocant_cr;
985 0           my @row_indices = @_;
986              
987             my $rows
988             = $class->new(
989 0 0 0       map { ( -@$self <= $_ && $_ <= $#{$self} ) ? $self->[$_] : [] }
  0            
990             @row_indices );
991             # the row if it's a valid row idx, othewise an empty ref
992 0           $rows->prune();
993 0           return $rows;
994             }
995              
996             =item B, ...)>
997              
998             Returns a new Array::2D object with the specified columns. This is transposed
999             from the original array's order, so each column requested will be in its own
1000             row.
1001              
1002             $array = [
1003             [ qw/ a b c d / ],
1004             [ qw/ j k l m / ],
1005             [ qw/ w x y z / ],
1006             ];
1007             my $cols = Array::2D->cols($array, 1, 2);
1008             # $cols = bless [ [ qw/ b k x / ] , [ qw/ c l y / ] ], 'Array::2D';
1009              
1010             Note that duplicates are not de-duplicated, so the result of
1011             $obj->cols(1,1,1) will retrieve three copies of the same column.
1012              
1013             =cut
1014              
1015             sub cols {
1016 0     0 1   my ( $class, $self ) = &$invocant_cr;
1017 0           my @col_indices = @_;
1018              
1019 0           my $cols = [ map { [ $class->col( $self, $_ ) ] } @col_indices ];
  0            
1020              
1021 0           CORE::bless $cols, $class;
1022 0           $cols->prune;
1023 0           return $cols;
1024             }
1025              
1026             =item B, ...)>
1027              
1028             Returns a new Array::2D object with the specified columns of each row.
1029             Unlike C, the result of this method is not transposed.
1030              
1031             $array = [
1032             [ qw/ a b c d / ],
1033             [ qw/ j k l m / ],
1034             [ qw/ w x y z / ],
1035             ];
1036             my $sliced_cols = Array::2D->slice_cols($array, 1, 2);
1037             # $sliced_cols = bless [
1038             # [ qw/ b c / ] ,
1039             # [ qw/ k l / ] ,
1040             # [ qw/ x y / ] ,
1041             # ], 'Array::2D';
1042              
1043             Note that duplicates are not de-duplicated, so the result of
1044             $obj->slice_cols(1,1,1) will retrieve three copies of the same column.
1045              
1046             =cut
1047              
1048             sub slice_cols {
1049 0     0 1   my ( $class, $self ) = &$invocant_cr;
1050 0           my @col_indices = @_;
1051 0           my $width = $class->width($self);
1052 0           for my $col_idx (@col_indices) {
1053 0 0         $col_idx += $width if $col_idx < 0;
1054             }
1055             # must adjust this to whole array width, not just row width
1056              
1057 0           my $return = [];
1058              
1059 0           foreach my $row_r (@$self) {
1060 0           my @new_row;
1061 0           foreach my $col_idx (@col_indices) {
1062 0 0 0       if ( -$width <= $col_idx and $col_idx < $width ) {
1063 0           push @new_row, $row_r->[$col_idx];
1064             }
1065             else {
1066 0           push @new_row, undef;
1067             }
1068             }
1069 0           push @$return, \@new_row;
1070             }
1071              
1072 0           CORE::bless $return, $class;
1073 0           $return->prune;
1074 0           return $return;
1075             } ## tidy end: sub slice_cols
1076              
1077             =item B)>
1078              
1079             Takes a two-dimensional slice of the array; like cutting a rectangle
1080             out of the array.
1081              
1082             In void context, alters the original array, which then will contain
1083             only the area specified; otherwise, creates a new Array::2D
1084             object and returns the object.
1085              
1086             Negative indicies are treated as though they mean that many from the end:
1087             the last item is -1, the second-to-last is -2, and so on.
1088              
1089             Slices are always returned in the order of the original array, so
1090             $obj->slice(0,1,0,1) is the same as $obj->slice(1,0,1,0).
1091              
1092             =cut
1093              
1094             sub slice {
1095 0     0 1   my ( $class, $self ) = &$invocant_cr;
1096              
1097 0           my ( $firstrow, $lastrow, $firstcol, $lastcol, ) = @_;
1098              
1099             ### adjust row indices
1100              
1101 0           my $self_lastrow = $class->last_row($self);
1102              
1103 0           foreach my $row_idx ( $firstrow, $lastrow ) {
1104 0 0         next unless $row_idx < 0;
1105 0           $row_idx += $self_lastrow + 1;
1106             }
1107              
1108             ### adjust col indices
1109              
1110 0           my $self_lastcol = $class->last_col($self);
1111              
1112 0           foreach my $col ( $firstcol, $lastcol ) {
1113 0 0         next unless $col < 0;
1114 0           $col += $self_lastcol + 1;
1115             }
1116              
1117             ### sort indices
1118              
1119 0 0         ( $firstrow, $lastrow ) = ( $lastrow, $firstrow )
1120             if $lastrow < $firstrow;
1121              
1122 0 0         ( $firstcol, $lastcol ) = ( $lastcol, $firstcol )
1123             if $lastcol < $firstcol;
1124              
1125             # if it's specifying an area entirely off the beginning or end
1126             # of the array, return empty
1127 0 0 0       if ( $lastrow < 0
      0        
      0        
1128             or $self_lastrow < $firstrow
1129             or $lastcol < 0
1130             or $self_lastcol < $firstcol )
1131             {
1132 0 0         return $class->empty() if defined wantarray;
1133 0           @{$self} = ();
  0            
1134 0           return;
1135             }
1136              
1137             # otherwise, since it's at least partially in the array, set the rows
1138             # to be within the array.
1139 0 0         $lastrow = $self_lastrow if $self_lastrow < $lastrow;
1140 0 0         $firstrow = 0 if $firstrow < 0;
1141              
1142 0           my $rows = $class->rows( $self, $firstrow .. $lastrow );
1143              
1144             # set the bounds to be within the column of these rows
1145 0 0         $firstcol = 0 if $firstcol < 0;
1146 0           my $rows_lastcol = $class->last_col($rows);
1147 0 0         $lastcol = $rows_lastcol if $rows_lastcol < $lastcol;
1148              
1149 0           my $new = $class->slice_cols( $rows, $firstcol .. $lastcol );
1150 0 0         return $new if defined wantarray;
1151 0           @{$self} = @{$new};
  0            
  0            
1152 0           return;
1153             } ## tidy end: sub slice
1154              
1155             =back
1156              
1157             =head2 SETTING ELEMENTS, ROWS, COLUMNS, SLICES
1158              
1159             None of these methods return anything. At some point it might
1160             be worthwhile to have them return the old values of whatever they changed
1161             (when not called in void context), but they don't do that yet.
1162              
1163             =over
1164              
1165             =item B)>
1166              
1167             Sets the element in the given row and column to the given value.
1168             Just a slower way of saying
1169             C<< $array2d->[I][I] = I >>.
1170              
1171             =cut
1172              
1173             sub set_element {
1174 0     0 1   my ( $class, $self ) = &$invocant_cr;
1175 0           my $row_idx = shift;
1176 0           my $col_idx = shift;
1177 0           $self->[$row_idx][$col_idx] = shift;
1178 0           return;
1179             }
1180              
1181             =item B)>
1182              
1183             Sets the given row to the given set of values.
1184             A slower way of saying C<< {$array2d->[I] = [ @values ] >>.
1185              
1186             =cut
1187              
1188             sub set_row {
1189 0     0 1   my ( $class, $self ) = &$invocant_cr;
1190 0   0       my $row_idx = shift || 0;
1191 0           my @elements = @_;
1192 0 0         return $#{$self} unless @elements;
  0            
1193 0           $self->[$row_idx] = \@elements;
1194 0           return;
1195             }
1196              
1197             =item B)>
1198              
1199             Sets the given column to the given set of values. If more values are given than
1200             there are rows, will add rows; if fewer values than there are rows, will set the
1201             entries in the remaining rows to C.
1202              
1203             =cut
1204              
1205             sub set_col {
1206 0     0 1   my ( $class, $self ) = &$invocant_cr;
1207 0           my $col_idx = shift;
1208 0           my @elements = @_;
1209              
1210             # handle negative col_idx
1211              
1212 0           my $width = $class->width($self);
1213 0 0         return $width unless @elements;
1214              
1215 0 0         if ( $col_idx < -$width ) {
1216 0           croak("$class->set_col: negative index off the beginning of the array");
1217             }
1218 0 0         $col_idx += $width if $col_idx < 0;
1219              
1220 0           for my $row_idx ( 0 .. max( $class->last_row($self), $#elements ) ) {
1221 0           $self->[$row_idx][$col_idx] = $elements[$row_idx];
1222             }
1223 0           return;
1224              
1225             } ## tidy end: sub set_col
1226              
1227             =item B<< set_rows(I) >>
1228              
1229             =item B<< set_rows(I) >>
1230              
1231             Sets the rows starting at the given start row index to the rows given.
1232             So, for example, $obj->set_rows(1, $row_ref_a, $row_ref_b) will set
1233             row 1 of the object to be the elements of $row_ref_a and row 2 to be the
1234             elements of $row_ref_b.
1235              
1236             The arguments after I are passed to C, so it accepts
1237             any of the arguments that C accepts.
1238              
1239             Returns the height of the array.
1240              
1241             =cut
1242              
1243             sub set_rows {
1244 0     0 1   my ( $class, $self ) = &$invocant_cr;
1245 0           my $self_start_row_idx = shift;
1246 0           my $given = $class->new(@_);
1247 0           my @given_rows = @{$given};
  0            
1248 0           for my $given_row_idx ( 0 .. $#given_rows ) {
1249 0           my @elements = @{ $given_rows[$given_row_idx] };
  0            
1250 0           $self->[ $self_start_row_idx + $given_row_idx ] = \@elements;
1251             }
1252 0           return;
1253             }
1254              
1255             =item B...)>
1256              
1257             Sets the columns starting at the given start column index to the columns given.
1258             So, for example, $obj->set_cols(1, $col_ref_a, $col_ref_b) will set
1259             column 1 of the object to be the elemnents of $col_ref_a and column 2 to be the
1260             elements of $col_ref_b.
1261              
1262             =cut
1263              
1264             sub set_cols {
1265 0     0 1   my ( $class, $self ) = &$invocant_cr;
1266 0           my $self_start_col_idx = shift;
1267 0           my @given_cols = @_;
1268 0           my $width;
1269              
1270 0           foreach my $given_col_idx ( 0 .. $#given_cols ) {
1271 0           my @given_elements = @{ $given_cols[$given_col_idx] };
  0            
1272 0           $width = $class->set_col( $self, $self_start_col_idx + $given_col_idx,
1273             @given_elements );
1274             }
1275 0           return;
1276             }
1277              
1278             =item B)>
1279              
1280             =item B)>
1281              
1282             Sets a rectangular segment of the object to have the values of the supplied
1283             rows or array of arrays, beginning at the supplied first row and first column.
1284             The arguments after the row and columns are passed to C, so it accepts
1285             any of the arguments that C accepts.
1286              
1287             =cut
1288              
1289             sub set_slice {
1290 0     0 1   my ( $class, $self ) = &$invocant_cr;
1291              
1292 0           my $class_firstrow = shift;
1293 0           my $class_firstcol = shift;
1294              
1295 0           my $slice = $class->new(@_);
1296 0           my $slice_last_row = $slice->last_row;
1297 0           my $slice_last_col = $slice->last_col;
1298              
1299 0           for my $row_idx ( 0 .. $slice_last_row ) {
1300 0           for my $col_idx ( 0 .. $slice_last_col ) {
1301 0           $self->[ $class_firstrow + $row_idx ][ $class_firstcol + $col_idx ]
1302             = $slice->[$row_idx][$col_idx];
1303             }
1304             }
1305              
1306 0           return;
1307              
1308             } ## tidy end: sub set_slice
1309              
1310             =back
1311              
1312             =head2 INSERTING ROWS AND COLUMNS
1313              
1314             All these methods return the new number of either rows or columns.
1315              
1316             =over
1317              
1318             =item B)>
1319              
1320             Adds the specified elements as a new row at the given index.
1321              
1322             =cut
1323              
1324             sub ins_row {
1325 0     0 1   my ( $class, $self ) = &$invocant_cr;
1326 0           my $row_idx = shift;
1327 0           my @row = @_;
1328              
1329 0 0         if ( $#{$self} < $row_idx ) {
  0            
1330 0           $self->[$row_idx] = [@row];
1331             }
1332             else {
1333 0           splice( @{$self}, $row_idx, 0, [@row] );
  0            
1334             }
1335              
1336 0           return scalar @{$self};
  0            
1337             }
1338              
1339             =item B)>
1340              
1341             Adds the specified elements as a new column at the given index.
1342              
1343             =cut
1344              
1345             sub ins_col {
1346 0     0 1   my ( $class, $self ) = &$invocant_cr;
1347 0           my $col_idx = shift;
1348 0           my @col = @_;
1349              
1350             # handle negative col_idx
1351 0           my $width = $class->width($self);
1352 0 0         return $width unless @col;
1353              
1354 0 0         if ( $col_idx < -$width ) {
1355 0           croak("$class->ins_col: negative index off the beginning of the array");
1356             }
1357 0 0         $col_idx += $width if $col_idx < 0;
1358              
1359 0           my $last_row = max( $class->last_row($self), $#col );
1360             # if this is below the array, extend the array so it is longer
1361 0           $#{$self} = $last_row;
  0            
1362              
1363 0           for my $row_idx ( 0 .. $last_row ) {
1364             # if this is off to the right of this row,
1365 0 0         if ( $#{ $self->[$row_idx] } < $col_idx ) {
  0            
1366             # just set the element
1367 0           $self->[$row_idx][$col_idx] = $col[$row_idx];
1368             }
1369             else {
1370             # otherwise, insert it in using splice
1371 0           splice( @{ $self->[$row_idx] }, $col_idx, 0, $col[$row_idx] );
  0            
1372             }
1373             }
1374              
1375 0 0         return $class->width($self) if defined wantarray;
1376 0           return;
1377             } ## tidy end: sub ins_col
1378              
1379             =item B)>
1380              
1381             Takes the specified array of arrays and inserts them as new rows at the
1382             given index.
1383              
1384             The arguments after the row index are passed to C, so it accepts
1385             any of the arguments that C accepts.
1386              
1387             =cut
1388              
1389             sub ins_rows {
1390 0     0 1   my ( $class, $self ) = &$invocant_cr;
1391 0           my $row_idx = shift;
1392 0           my $given = $class->new(@_);
1393              
1394 0           splice( @{$self}, $row_idx, 0, @$given );
  0            
1395 0           return scalar @{$self};
  0            
1396             }
1397              
1398             =item B)>
1399              
1400             Takes the specified array of arrays and inserts them as new columns at
1401             the given index.
1402              
1403             =cut
1404              
1405             sub ins_cols {
1406 0     0 1   my ( $class, $self ) = &$invocant_cr;
1407 0           my $col_idx = shift;
1408 0           my @cols = @_;
1409              
1410 0           my $last_row = max( $class->last_row($self), map { $#{$_} } @cols );
  0            
  0            
1411              
1412 0           for my $row_idx ( 0 .. $last_row ) {
1413 0           for my $col (@cols) {
1414 0           splice( @{ $self->[$row_idx] }, $col_idx, 0, $col->[$row_idx] );
  0            
1415             }
1416             }
1417 0 0         return $class->width($self) if defined wantarray;
1418 0           return;
1419             }
1420              
1421             =item B)>
1422              
1423             Adds the specified elements as the new first row.
1424              
1425             =cut
1426              
1427             sub unshift_row {
1428 0     0 1   my ( $class, $self ) = &$invocant_cr;
1429 0           my @col_values = @_;
1430 0           return unshift @{$self}, \@col_values;
  0            
1431             }
1432              
1433             =item B)>
1434              
1435             Adds the specified elements as the new first column.
1436              
1437             =cut
1438              
1439             sub unshift_col {
1440 0     0 1   my ( $class, $self ) = &$invocant_cr;
1441 0           my @col_values = @_;
1442 0           return $class->ins_col( $self, 0, @col_values );
1443             }
1444              
1445             =item B)>
1446              
1447             =item B)>
1448              
1449             Takes the specified array of arrays and adds them as new rows before
1450             the beginning of the existing rows. Returns the new number of rows.
1451              
1452             The arguments are passed to C, so it accepts
1453             any of the arguments that C accepts.
1454              
1455             =cut
1456              
1457             sub unshift_rows {
1458 0     0 1   my ( $class, $self ) = &$invocant_cr;
1459 0           my $given = $class->new(@_);
1460 0           return unshift @{$self}, @$given;
  0            
1461             }
1462              
1463             =item B)>
1464              
1465             Takes the specified array of arrays and adds them as new columns,
1466             before the beginning of the existing columns. Returns the new number of
1467             columns.
1468              
1469             =cut
1470              
1471             sub unshift_cols {
1472 0     0 1   my ( $class, $self ) = &$invocant_cr;
1473 0           my @cols = @_;
1474 0           return $class->ins_cols( $self, 0, @cols );
1475             }
1476              
1477             =item B)>
1478              
1479             Adds the specified elements as the new final row. Returns the new
1480             number of rows.
1481              
1482             =cut
1483              
1484             sub push_row {
1485 0     0 1   my ( $class, $self ) = &$invocant_cr;
1486 0           my @col_values = @_;
1487 0           return push @{$self}, \@col_values;
  0            
1488             }
1489              
1490             =item B)>
1491              
1492             Adds the specified elements as the new final column. Returns the new
1493             number of columns.
1494              
1495             =cut
1496              
1497             sub push_col {
1498 0     0 1   my ( $class, $self ) = &$invocant_cr;
1499 0           my @col = @_;
1500 0           my $width = $class->width($self);
1501 0 0         return $width unless @col;
1502              
1503 0           for my $row_idx ( 0 .. max( $class->last_row($self), $#col ) ) {
1504 0           $self->[$row_idx][$width] = $col[$row_idx];
1505             }
1506              
1507 0           return $width + 1; # new width
1508             }
1509              
1510             =item B)>
1511              
1512             =item B)>
1513              
1514             Takes the specified array of arrays and adds them as new rows after the
1515             end of the existing rows. Returns the new number of rows.
1516              
1517             The arguments are passed to C, so it accepts
1518             any of the arguments that C accepts.
1519              
1520             =cut
1521              
1522             sub push_rows {
1523 0     0 1   my ( $class, $self ) = &$invocant_cr;
1524 0           my $rows = $class->new(@_);
1525 0           return push @{$self}, @$rows;
  0            
1526             }
1527              
1528             =item B)>
1529              
1530             Takes the specified array of arrays and adds them as new columns, after
1531             the end of the existing columns. Returns the new number of columns.
1532              
1533             =cut
1534              
1535             sub push_cols {
1536 0     0 1   my ( $class, $self ) = &$invocant_cr;
1537 0           my @cols = @_;
1538 0           my $col_idx = $class->last_col($self);
1539              
1540 0 0         if ( -1 == $col_idx ) {
1541 0           @{$self} = map { [ @{$_} ] } @{$self};
  0            
  0            
  0            
  0            
1542 0 0         return $class->width($self) if defined wantarray;
1543 0           return;
1544             }
1545              
1546 0           my $last_row = max( $class->last_row($self), $#cols );
1547 0           my $last_col = $class->last_col($self);
1548              
1549 0           for my $row_index ( 0 .. $last_row ) {
1550 0           my $row_r = $self->[$row_index];
1551 0 0         if ( not defined $row_r ) {
1552 0           $row_r = $self->[$row_index] = [];
1553             }
1554 0           $#{$row_r} = $last_col; # pad out
  0            
1555 0           push @{$row_r}, @{ $cols[$row_index] };
  0            
  0            
1556             }
1557              
1558 0 0         return $class->width($self) if defined wantarray;
1559 0           return;
1560              
1561             } ## tidy end: sub push_cols
1562              
1563             =back
1564              
1565             =head2 RETRIEVING AND DELETING ROWS AND COLUMNS
1566              
1567             =over
1568              
1569             =item B)>
1570              
1571             Removes the row of the object specified by the index and returns a list
1572             of the elements of that row.
1573              
1574             =cut
1575              
1576             sub del_row {
1577 0     0 1   my ( $class, $self ) = &$invocant_cr;
1578 0           my $row_idx = shift;
1579              
1580 0 0         return () unless @{$self};
  0            
1581 0 0         return () if $class->last_row($self) < $row_idx;
1582              
1583 0 0         if ( defined wantarray ) {
1584 0           my @deleted = $class->row( $self, $row_idx );
1585 0           splice( @{$self}, $row_idx, 1 );
  0            
1586 0           $class->prune($self);
1587 0   0       pop @deleted while @deleted and not defined $deleted[-1]; # prune
1588 0           return @deleted;
1589             }
1590              
1591 0           splice( @{$self}, $row_idx, 1 );
  0            
1592 0           $class->prune($self);
1593 0           return;
1594             }
1595              
1596             =item B)>
1597              
1598             Removes the column of the object specified by the index and returns a
1599             list of the elements of that column.
1600              
1601             =cut
1602              
1603             sub del_col {
1604 0     0 1   my ( $class, $self ) = &$invocant_cr;
1605 0           my $col_idx = shift;
1606              
1607             # handle negative col_idx
1608 0           my $width = $class->width($self);
1609 0 0         return () if $width <= $col_idx;
1610              
1611 0 0         if ( $col_idx < -$width ) {
1612 0           croak("$class->del_col: negative index off the beginning of the array");
1613             }
1614 0 0         $col_idx += $width if $col_idx < 0;
1615              
1616 0           my @deleted;
1617 0 0         if ( defined wantarray ) {
1618 0           @deleted = $class->col( $self, $col_idx );
1619 0   0       pop @deleted while @deleted and not defined $deleted[-1]; # prune
1620             }
1621              
1622 0           foreach my $row ( @{$self} ) {
  0            
1623 0           splice( @{$row}, $col_idx, 1 );
  0            
1624             }
1625 0           $class->prune($self);
1626              
1627 0 0         return @deleted if defined wantarray;
1628 0           return;
1629             } ## tidy end: sub del_col
1630              
1631             =item B, I...)>
1632              
1633             Removes the rows of the object specified by the indices. Returns an
1634             Array::2D object of those rows.
1635              
1636             =cut
1637              
1638             sub del_rows {
1639 0     0 1   my ( $class, $self ) = &$invocant_cr;
1640 0           my @row_idxs = @_;
1641              
1642 0 0         unless (@$self) {
1643 0 0         return $class->empty if defined wantarray;
1644 0           return;
1645             }
1646              
1647 0           my $deleted;
1648 0 0         if ( defined wantarray ) {
1649 0           $deleted = $class->rows( $self, @row_idxs );
1650             }
1651              
1652 0           foreach my $row_idx (@row_idxs) {
1653 0           splice( @{$self}, $row_idx, 1 );
  0            
1654             }
1655              
1656 0           $class->prune($self);
1657 0 0         return $deleted if defined wantarray;
1658 0           return;
1659             } ## tidy end: sub del_rows
1660              
1661             =item B, I...)>
1662              
1663             Removes the columns of the object specified by the indices. Returns an
1664             Array::2D object of those columns.
1665              
1666             =cut
1667              
1668             sub del_cols {
1669 0     0 1   my ( $class, $self ) = &$invocant_cr;
1670 0           my @col_idxs = @_;
1671              
1672 0 0         unless (@$self) {
1673 0 0         return $class->empty if defined wantarray;
1674 0           return;
1675             }
1676              
1677 0           my $deleted;
1678 0 0         if ( defined wantarray ) {
1679 0           $deleted = $class->cols( $self, @col_idxs );
1680             }
1681              
1682 0           foreach my $col_idx ( reverse sort @_ ) {
1683 0           $self->del_col($col_idx);
1684             }
1685              
1686 0           $class->prune($self);
1687 0 0         return $deleted if defined wantarray;
1688 0           return;
1689             } ## tidy end: sub del_cols
1690              
1691             =item B
1692              
1693             Removes the first row of the object and returns a list of the elements
1694             of that row.
1695              
1696             =cut
1697              
1698             sub shift_row {
1699 0     0 1   my ( $class, $self ) = &$invocant_cr;
1700 0 0         return () unless @{$self};
  0            
1701 0           my @row = @{ shift @{$self} };
  0            
  0            
1702 0   0       pop @row while @row and not defined $row[-1];
1703 0           $class->prune($self);
1704 0           return @row;
1705             }
1706              
1707             =item B
1708              
1709             Removes the first column of the object and returns a list of the
1710             elements of that column.
1711              
1712             =cut
1713              
1714             sub shift_col {
1715 0     0 1   my ( $class, $self ) = &$invocant_cr;
1716 0           my @col = map { shift @{$_} } @{$self};
  0            
  0            
  0            
1717 0   0       pop @col while @col and not defined $col[-1]; # prune
1718 0           $class->prune($self);
1719 0           return @col;
1720             }
1721              
1722             =item B
1723              
1724             Removes the last row of the object and returns a list of the elements
1725             of that row.
1726              
1727             =cut
1728              
1729             sub pop_row {
1730 0     0 1   my ( $class, $self ) = &$invocant_cr;
1731 0 0         return () unless @{$self};
  0            
1732 0           my @row = @{ pop @{$self} };
  0            
  0            
1733 0   0       pop @row while @row and not defined $row[-1]; # prune
1734 0           $class->prune($self);
1735 0           return @row;
1736             }
1737              
1738             =item B
1739              
1740             Removes the last column of the object and returns a list of the
1741             elements of that column.
1742              
1743             =cut
1744              
1745             sub pop_col {
1746 0     0 1   my ( $class, $self ) = &$invocant_cr;
1747 0 0         return () unless @{$self};
  0            
1748 0           my $last_col = $class->last_col($self);
1749 0 0         return () if -1 == $last_col;
1750 0           $class->prune($self);
1751 0           return $class->del_col( $self, $last_col );
1752             }
1753              
1754             =back
1755              
1756             =head2 ADDING OR REMOVING PADDING
1757              
1758             Padding, here, means empty values beyond
1759             the last defined value of each column or row. What counts as "empty"
1760             depends on the method being used.
1761              
1762             =over
1763              
1764             =item B
1765              
1766             Occasionally an array of arrays can end up with final rows or columns
1767             that are entirely undefined. For example:
1768              
1769             my $obj = Array::2D->new ( [ qw/a b c/] , [ qw/f g h/ ]);
1770             $obj->[0][4] = 'e';
1771             $obj->[3][0] = 'k';
1772              
1773             # a b c undef e
1774             # f g h
1775             # (empty)
1776             # k
1777              
1778             $obj->pop_row();
1779             $obj->pop_col();
1780              
1781             # a b c undef
1782             # f g h
1783             # (empty)
1784              
1785             That would yield an object with four columns, but in which the last
1786             column and last row (each with index 3) consists of only undefined
1787             values.
1788              
1789             The C method eliminates these entirely undefined or empty
1790             columns and rows at the end of the object.
1791              
1792             In void context, alters the original object. Otherwise, creates a new
1793             Array::2D object and returns the object.
1794              
1795             =cut
1796              
1797             sub prune {
1798 0     0 1   my ( $class, $self ) = &$invocant_cr;
1799 0     0     my $callback = sub { !defined $_ };
  0            
1800 0           return $class->prune_callback( $self, $callback );
1801             }
1802              
1803             =item B
1804              
1805             Like C, but treats not only undefined values as blank, but also
1806             empty strings.
1807              
1808             =cut
1809              
1810             sub prune_empty {
1811 0     0 1   my ( $class, $self ) = &$invocant_cr;
1812 0 0   0     my $callback = sub { not defined $_ or $_ eq q[] };
  0            
1813 0           return $class->prune_callback( $self, $callback );
1814             }
1815              
1816             =item B
1817              
1818             Like C, but treats not only undefined values as blank, but also
1819             strings that are empty or that consist solely of white space.
1820              
1821             =cut
1822              
1823             sub prune_space {
1824 0     0 1   my ( $class, $self ) = &$invocant_cr;
1825 0 0   0     my $callback = sub { not defined $_ or m[\A \s* \z]x };
  0            
1826 0           return $class->prune_callback( $self, $callback );
1827             }
1828              
1829             =item B)>
1830              
1831             Like C, but calls the for each element, setting $_ to
1832             each element. If the callback code returns true, the value is
1833             considered padding, and is removed if it's beyond the last non-padding
1834             value at the end of a column or row.
1835              
1836             For example, this would prune values that were undefined, the empty
1837             string, or zero:
1838              
1839             my $callback = sub {
1840             ! defined $_ or $_ eq q[] or $_ == 0;
1841             }
1842             $obj->prune_callback($callback);
1843              
1844             In void context, alters the original object. Otherwise, creates a new
1845             Array::2D object and returns the object.
1846              
1847             Completely empty rows cannot be sent to the callback function,
1848             so those are always removed.
1849              
1850             =cut
1851              
1852             sub prune_callback {
1853 0     0 1   my ( $class, $orig ) = &$invocant_cr;
1854 0           my $callback = shift;
1855 0           my $self;
1856              
1857 0 0         if ( defined wantarray ) {
1858 0           $self = $class->clone($orig);
1859             }
1860             else {
1861 0           $self = $orig;
1862             }
1863              
1864             # remove final blank rows
1865 0   0       while (
      0        
1866 0           @{$self}
1867             and ( not defined $self->[-1]
1868             or 0 == @{ $self->[-1] }
1869 0     0     or all { $callback->() } @{ $self->[-1] } )
1870             )
1871             {
1872 0           pop @{$self};
  0            
1873             }
1874              
1875             # return if it's all blank
1876 0 0         return $self unless ( @{$self} );
  0            
1877              
1878             # remove final blank columns
1879              
1880 0           foreach my $row_r ( @{$self} ) {
  0            
1881 0           while ( @{$row_r} ) {
  0            
1882 0           local $_ = $row_r->[-1];
1883 0 0         last if not $callback->();
1884 0           pop @$row_r;
1885             }
1886             }
1887              
1888 0           return $self;
1889             } ## tidy end: sub prune_callback
1890              
1891             =item B)>
1892              
1893             The opposite of C, this pads out the array so every column
1894             has the same number of elements. If provided, the added elements are
1895             given the value provided; otherwise, they are set to undef.
1896              
1897             =cut
1898              
1899             sub pad {
1900 0     0 1   my ( $class, $orig ) = &$invocant_cr;
1901 0           my $padding = shift;
1902 0           my $self;
1903 0 0         if ( defined wantarray ) {
1904 0           $self = $class->clone($orig);
1905             }
1906             else {
1907 0           $self = $orig;
1908             }
1909 0           my $last_col = $class->last_col($self);
1910              
1911 0 0         if ( not defined $padding ) {
1912 0           foreach (@$self) {
1913 0           $#{$_} = $last_col;
  0            
1914             }
1915             }
1916             else {
1917 0           foreach (@$self) {
1918 0           push @$_, $padding while $#{$_} < $last_col;
  0            
1919             }
1920             }
1921              
1922 0           return $self;
1923              
1924             } ## tidy end: sub pad
1925              
1926             =back
1927              
1928             =head2 MODIFYING EACH ELEMENT
1929              
1930             Each of these methods alters the original array in void context.
1931             If not in void context, creates a new Array::2D object and returns
1932             the object.
1933              
1934             =over
1935              
1936             =item B)>
1937              
1938             Calls the C<$code_ref> for each element, aliasing $_ to each element in
1939             turn. This allows an operation to be performed on every element.
1940              
1941             For example, this would lowercase every element in the array (assuming
1942             all values are defined):
1943              
1944             $obj->apply(sub {lc});
1945              
1946             If an entry in the array is undefined, it will still be passed to the
1947             callback.
1948              
1949             For each invocation of the callback, @_ is set to the row and column
1950             indexes (0-based).
1951              
1952             =cut
1953              
1954             sub apply {
1955 0     0 1   my ( $class, $orig ) = &$invocant_cr;
1956 0           my $callback = shift;
1957 0           my $self;
1958              
1959 0 0         if ( defined wantarray ) {
1960 0           $self = $class->clone($orig);
1961             }
1962             else {
1963 0           $self = $orig;
1964             }
1965              
1966 0           for my $row ( @{$self} ) {
  0            
1967 0           for my $idx ( 0 .. $#{$row} ) {
  0            
1968 0           for ( $row->[$idx] ) {
1969             # localize $_ to $row->[$idx]. Autovivifies the row.
1970 0           $callback->( $row, $idx );
1971             }
1972             }
1973             }
1974 0           return $self;
1975             } ## tidy end: sub apply
1976              
1977             =item B
1978              
1979             Removes white space, if present, from the beginning and end of each
1980             element in the array.
1981              
1982             =cut
1983              
1984             sub trim {
1985 0     0 1   my ( $class, $self ) = &$invocant_cr;
1986              
1987             my $callback = sub {
1988 0 0   0     return unless defined;
1989 0           s/\A\s+//;
1990 0           s/\s+\z//;
1991 0           return;
1992 0           };
1993              
1994 0           return $class->apply( $self, $callback );
1995             }
1996              
1997             =item B
1998              
1999             Removes white space from the end of each element in the array.
2000              
2001             In void context, alters the original object. Otherwise, creates a new
2002             Array::2D object and returns the object.
2003              
2004             =cut
2005              
2006             sub trim_right {
2007 0     0 1   my ( $class, $self ) = &$invocant_cr;
2008              
2009             my $callback = sub {
2010 0 0   0     return unless defined;
2011 0           s/\s+\z//;
2012 0           return;
2013 0           };
2014              
2015 0           return $class->apply( $self, $callback );
2016             }
2017              
2018             =item B
2019              
2020             Replaces undefined values with the empty string.
2021              
2022             =cut
2023              
2024             sub define {
2025 0     0 1   my ( $class, $self ) = &$invocant_cr;
2026              
2027             my $callback = sub {
2028 0   0 0     $_ //= q[];
2029 0           };
2030 0           return $class->apply( $self, $callback );
2031             }
2032              
2033             =back
2034              
2035             =head2 TRANSFORMING ARRAYS INTO OTHER STRUCTURES
2036              
2037             =over
2038              
2039             =item B)>
2040              
2041             Returns a hash reference. The values of the specified
2042             column of the array become the keys of the hash. The values of the hash
2043             are arrayrefs containing the elements
2044             of the rows of the array, with the value in the key column removed.
2045              
2046             If the key column is not specified, the first column is used for the
2047             keys.
2048              
2049             So:
2050              
2051             $obj = Array::2D->new([qw/a 1 2/],[qw/b 3 4/]);
2052             $hashref = $obj->hash_of_rows(0);
2053             # $hashref = { a => [ '1' , '2' ] , b => [ '3' , '4' ] }
2054              
2055             =cut
2056              
2057             sub hash_of_rows {
2058 0     0 1   my ( $class, $self ) = &$invocant_cr;
2059 0           my $col = shift;
2060              
2061 0           my %hash;
2062              
2063 0 0         if ($col) {
2064 0           for my $row_r ( @{$self} ) {
  0            
2065 0           my @row = @{$row_r};
  0            
2066 0           my $key = splice( @row, $col, 1 );
2067 0           $hash{$key} = \@row;
2068             }
2069             }
2070             else {
2071              
2072 0           for my $row_r ( @{$self} ) {
  0            
2073 0           my @row = @{$row_r};
  0            
2074 0           my $key = shift @row;
2075 0           $hash{$key} = \@row;
2076             }
2077              
2078             }
2079              
2080 0           return \%hash;
2081             } ## tidy end: sub hash_of_rows
2082              
2083             =item B)>
2084              
2085             Like C, but accepts a key column and a value column, and
2086             the values are not whole rows but only single elements.
2087              
2088             So:
2089              
2090             $obj = Array::2D->new([qw/a 1 2/],[qw/b 3 4/]);
2091             $hashref = $obj->hash_of_row_elements(0, 1);
2092             # $hashref = { a => '1' , b => '3' }
2093              
2094             If neither key column nor value column are specified, column 0 will be
2095             used for the key and the column 1 will be used for the value.
2096              
2097             If the key column is specified but the value column is not, then the
2098             first column that is not the key column will be used as the value
2099             column. (In other words, if the key column is column 0, then column 1
2100             will be used as the value; otherwise column 0 will be used as the
2101             value.)
2102              
2103             =cut
2104              
2105             sub hash_of_row_elements {
2106 0     0 1   my ( $class, $self ) = &$invocant_cr;
2107              
2108 0           my ( $keycol, $valuecol );
2109 0 0         if (@_) {
2110 0           $keycol = shift;
2111 0 0 0       $valuecol = shift // ( 0 == $keycol ? 1 : 0 );
2112              
2113             # $valuecol defaults to first column that is not the same as $keycol
2114             }
2115             else {
2116 0           $keycol = 0;
2117 0           $valuecol = 1;
2118             }
2119              
2120 0           my %hash;
2121 0           for my $row_r ( @{$self} ) {
  0            
2122 0           $hash{ $row_r->[$keycol] } = $row_r->[$valuecol];
2123             }
2124              
2125 0           return \%hash;
2126             } ## tidy end: sub hash_of_row_elements
2127              
2128             =back
2129              
2130             =head2 TABULATING INTO COLUMNAR OUTPUT
2131              
2132             If the L module can be loaded,
2133             its C method will be used to determine the width of each
2134             character. This will treat composed accented characters and
2135             double-width Asian characters correctly.
2136              
2137             Otherwise, Array::2D will use Perl's C function.
2138              
2139             =over
2140              
2141             =item B)>
2142              
2143             Returns an arrayref of strings, where each string consists of the
2144             elements of each row, padded with enough spaces to ensure that each
2145             column has a consistent width.
2146              
2147             The columns will be separated by whatever string is passed to
2148             C. If nothing is passed, a single space will be used.
2149              
2150             So, for example,
2151              
2152             $obj = Array::2D->new([qw/a bbb cc/],[qw/dddd e f/]);
2153             $arrayref = $obj->tabulate();
2154              
2155             # $arrayref = [ 'a bbb cc' ,
2156             # 'dddd e f'
2157             # ];
2158              
2159             Completely empty columns and rows will be removed.
2160              
2161             =item B)>
2162              
2163             Like C, but instead of each column having its own width,
2164             all columns have the same width.
2165              
2166             =cut
2167              
2168             my $prune_space_list_cr = sub {
2169             my @cells = @_;
2170              
2171             pop @cells
2172             while @cells
2173             and (not defined $cells[-1]
2174             or $cells[-1] eq q[]
2175             or $cells[-1] =~ m/\A\s*\z/ );
2176              
2177             return @cells;
2178             };
2179              
2180             {
2181             my $equal_width;
2182              
2183             my $tabulate_cr = sub {
2184             my ( $class, $orig ) = &$invocant_cr;
2185             my $self = $class->define($orig);
2186              
2187             my $separator = shift // q[ ];
2188             my @length_of_col;
2189             my $maxwidths = 0;
2190              
2191             foreach my $row ( @{$self} ) {
2192             my @cells = @{$row};
2193             for my $this_col ( 0 .. $#cells ) {
2194             my $thislength = $text_columns_cr->( $cells[$this_col] );
2195              
2196             $maxwidths = max( $maxwidths, $thislength ) if $equal_width;
2197             $length_of_col[$this_col] = $thislength
2198             if ( not $length_of_col[$this_col]
2199             or $length_of_col[$this_col] < $thislength );
2200             }
2201             }
2202              
2203             my @lines;
2204              
2205             foreach my $record_r ( @{$self} ) {
2206             my @cells = $prune_space_list_cr->( @{$record_r} );
2207              
2208             # prune trailing cells
2209              
2210             next unless @cells; # skip blank rows
2211              
2212             for my $this_col ( reverse( 0 .. ( $#cells - 1 ) ) ) {
2213             if ( 0 == $length_of_col[$this_col] ) {
2214             splice @cells, $this_col, 1;
2215             next;
2216             }
2217             # delete blank columns so it doesn't add the separator
2218              
2219             my $width
2220             = $equal_width ? $maxwidths : $length_of_col[$this_col];
2221              
2222             #$cells[$this_col]
2223             # = sprintf( '%-*s', $width, $cells[$this_col] );
2224            
2225             my $spaces = $width - $text_columns_cr->( $cells[$this_col]);
2226             $cells[$this_col] .= ( ' ' x $spaces) if $spaces > 0;
2227             }
2228             push @lines, join( $separator, @cells );
2229              
2230             } ## tidy end: foreach my $record_r ( @{$self...})
2231              
2232             return \@lines;
2233              
2234             };
2235              
2236             sub tabulate {
2237 0     0 1   $equal_width = 0;
2238 0           goto $tabulate_cr;
2239             }
2240              
2241             sub tabulate_equal_width {
2242 0     0 1   $equal_width = 1;
2243 0           goto $tabulate_cr;
2244             }
2245              
2246             }
2247              
2248             =item B)>
2249              
2250             Like C, but returns the data as a single string, using
2251             line feeds as separators of rows, suitable for sending to a terminal.
2252              
2253             =cut
2254              
2255             sub tabulated {
2256 0     0 1   my ( $class, $self ) = &$invocant_cr;
2257 0           my $lines_r = $class->tabulate( $self, @_ );
2258 0           return join( "\n", @$lines_r ) . "\n";
2259             }
2260              
2261             =back
2262              
2263             =head2 SERIALIZING AND OUTPUT TO FILES
2264              
2265             =over
2266              
2267             =item B<< tsv_lines(I) >>
2268              
2269             Returns a list of strings in list context, or an arrayref of strings in
2270             scalar context. The elements of each row are present in the string,
2271             separated by tab characters.
2272              
2273             If there are any arguments, they will be used first as the first
2274             row of text. The idea is that these will be the headers of the
2275             columns. It's not really any different than putting the column
2276             headers as the first element of the data, but frequently these are
2277             stored separately. If there is only one element and it is a reference
2278             to an array, that array will be used as the first row of text.
2279              
2280             If tabs are present in any element,
2281             they will be replaced by the Unicode Replacement Character, U+FFFD.
2282              
2283             =cut
2284              
2285             =item B<< tsv(I) >>
2286              
2287             Returns a single string with the elements of each row delimited by
2288             tabs, and rows delimited by line feeds.
2289              
2290             If there are any arguments, they will be used first as the first
2291             row of text. The idea is that these will be the headers of the
2292             columns. It's not really any different than putting the column
2293             headers as the first element of the data, but frequently these are
2294             stored separately. If there is only one element and it is a reference
2295             to an array, that array will be used as the first row of text.
2296              
2297             If tabs or line feeds are present in any element,
2298             they will be replaced by the Unicode Replacement Character, U+FFFD.
2299              
2300             =cut
2301              
2302             sub tsv_lines {
2303              
2304 0     0 1   my ( $class, $self ) = &$invocant_cr;
2305 0           my @rows = @$self;
2306              
2307 0           my @lines;
2308              
2309 0           my @headers = @_;
2310 0 0         if (@headers) {
2311 0 0 0       if ( 1 == @headers and is_plain_arrayref( $headers[0] ) ) {
2312 0           unshift @rows, $headers[0];
2313             }
2314             else {
2315 0           unshift @rows, \@headers;
2316             }
2317             }
2318              
2319 0           my $carped;
2320 0           foreach my $row (@rows) {
2321 0           my @cells = @{$row};
  0            
2322 0           foreach (@cells) {
2323 0   0       $_ //= q[];
2324 0           my $substitutions = s/\t/\x{FFFD}/g;
2325 0 0 0       if ( $substitutions and not $carped ) {
2326 0           carp 'Tab character found converting to tab-separated values. '
2327             . 'Replaced with REPLACEMENT CHARACTER';
2328 0           $carped = 1;
2329             }
2330             }
2331              
2332 0           @cells = $prune_space_list_cr->(@cells);
2333              
2334 0           my $line = join( "\t", @cells );
2335 0           push @lines, $line;
2336             }
2337              
2338 0 0         return wantarray ? @lines : \@lines;
2339              
2340             } ## tidy end: sub tsv_lines
2341              
2342             sub tsv {
2343              
2344             # tab-separated-values,
2345             # suitable for something like File::Slurper::write_text
2346              
2347             # converts line feeds, tabs, and carriage returns to the Replacement
2348             # Character.
2349              
2350 0     0 1   my ( $class, $self ) = &$invocant_cr;
2351              
2352 0           my $lines_r = $class->tsv_lines( $self, @_ );
2353              
2354 0           my $carped;
2355 0           foreach my $line (@$lines_r) {
2356 0           my $substitutions = $line =~ s/\n/\x{FFFD}/g;
2357 0 0 0       if ( $substitutions and not $carped ) {
2358 0           carp 'Line feed character found assembling tab-separated values. '
2359             . 'Replaced with REPLACEMENT CHARACTER';
2360 0           $carped = 1;
2361             }
2362             }
2363 0           return join( "\n", @$lines_r ) . "\n";
2364              
2365             } ## tidy end: sub tsv
2366              
2367             =item B<< file(...) >>
2368              
2369             Accepts a file specification and creates a new file at that location
2370             containing the data in the 2D array.
2371              
2372             This method uses named parameters.
2373              
2374             =over
2375              
2376             =item type
2377              
2378             This parameter is the file's type. Currently, the types recognized are
2379             'tsv' for tab-separated values, and 'xlsx' for Excel XLSX. If the type
2380             is not given, it attempts to determine the type from the file
2381             extension, which can be (case-insensitively) 'xlsx' for Excel XLSX
2382             files or 'tab', 'tsv' or 'txt' for tab-separated value files.
2383              
2384             (If other text file formats are someday added, either they will have
2385             to have different extensions, or an explicit type must be passed
2386             to force that type to have a ".txt" extension.
2387              
2388             =item output_file
2389              
2390             This mandatory parameter contains the file specification.
2391              
2392             =item headers
2393              
2394             This parameter is optional. If present, it contains an array reference
2395             to be used as the first row in the ouptut file.
2396              
2397             The idea is that these will be the headers of the columns. It's not
2398             really any different than putting the column headers as the first
2399             element of the data, but frequently these are stored separately.
2400              
2401             =back
2402              
2403             =cut
2404              
2405             sub file {
2406 0     0 1   my ( $class, $self ) = &$invocant_cr;
2407              
2408 0           my %params = validate(
2409             @_,
2410             { headers => { type => ARRAYREF, optional => 1 },
2411             output_file => 1,
2412             type => 0,
2413             },
2414             );
2415 0           my $output_file = $params{output_file};
2416 0   0       my $type = $params{type} || $filetype_from_ext_r->($output_file);
2417              
2418 0 0         croak "Cannot determine type of $output_file in " . __PACKAGE__ . '->file'
2419             unless $type;
2420              
2421 0 0         if ( $type eq 'xlsx' ) {
2422 0           $class->xlsx( $self, \%params );
2423 0           return;
2424             }
2425 0 0         if ( $type eq 'tsv' ) {
2426 0           my $text = $class->tsv($self);
2427              
2428 0 0         if ( $params{headers} ) {
2429 0           $text = join( "\t", @{ $params{headers} } ) . "\n" . $text;
  0            
2430             }
2431              
2432 0           require File::Slurper;
2433 0           File::Slurper::write_text( $output_file, $text );
2434 0           return;
2435             }
2436 0           croak "Unrecognized type $type in " . __PACKAGE__ . '->file';
2437             } ## tidy end: sub file
2438              
2439             =item B<< xlsx(...) >>
2440              
2441             Accepts a file specification and creates a new Excel XLSX file at that
2442             location, with one sheet, containing the data in the 2D array.
2443              
2444             This method uses named parameters.
2445              
2446             =over
2447              
2448             =item output_file
2449              
2450             This mandatory parameter contains the file specification.
2451              
2452             =item headers
2453              
2454             This parameter is optional. If present, it contains an array reference
2455             to be used as the first row in the Excel file.
2456              
2457             The idea is that these will be the headers of the columns. It's not
2458             really any different than putting the column headers as the first
2459             element of the data, but frequently these are stored separately. At
2460             this point no attempt is made to make them bold or anything like that.
2461              
2462             =item format
2463              
2464             This parameter is optional. If present, it contains a hash reference,
2465             with format parameters as specified by Excel::Writer::XLSX.
2466              
2467             =back
2468              
2469             =cut
2470              
2471             sub xlsx {
2472 0     0 1   my ( $class, $self ) = &$invocant_cr;
2473 0           my %params = validate(
2474             @_,
2475             { headers => { type => ARRAYREF, optional => 1 },
2476             format => { type => HASHREF, optional => 1 },
2477             output_file => 1,
2478             },
2479             );
2480              
2481 0           my $output_file = $params{output_file};
2482 0           my $format_properties = $params{format};
2483 0           my @headers;
2484 0 0         if ( $params{headers} ) {
2485 0           @headers = @{ $params{headers} };
  0            
2486             }
2487              
2488 0           require Excel::Writer::XLSX; ### DEP ###
2489              
2490 0           my $workbook = Excel::Writer::XLSX->new($output_file);
2491             ## no critic (Variables::ProhibitPunctuationVars]
2492 0 0         croak "Can't open $output_file for writing: $!"
2493             unless defined $workbook;
2494             ## use critic
2495 0           my $sheet = $workbook->add_worksheet();
2496 0           my @format;
2497              
2498 0 0         if ( defined $format_properties ) {
2499 0           push @format, $workbook->add_format(%$format_properties);
2500             }
2501              
2502             # an array @format is used because if it were a scalar, it would be undef,
2503             # where what we want if it is empty is no value at all
2504              
2505 0 0         my $unblessed = blessed $self ? $self->unblessed : $self;
2506              
2507             # Excel::Writer::XLSX checks 'ref' and not 'reftype'
2508              
2509 0 0         if (@headers) {
2510 0           $sheet->write_row( 0, 0, \@headers, @format );
2511 0           $sheet->write_col( 1, 0, $unblessed, @format );
2512             }
2513             else {
2514 0           $sheet->write_col( 0, 0, $unblessed, @format );
2515             }
2516              
2517 0           return $workbook->close();
2518              
2519             } ## tidy end: sub xlsx
2520              
2521             1;
2522              
2523             __END__