File Coverage

lib/Pivot/ArrayOfHashes.pm
Criterion Covered Total %
statement 57 57 100.0
branch 3 6 50.0
condition 2 4 50.0
subroutine 9 9 100.0
pod 1 1 100.0
total 72 77 93.5


line stmt bran cond sub pod time code
1             package Pivot::ArrayOfHashes 1.0002;
2              
3             # ABSTRACT: Pivot arrays of hashes, such as those returned by DBI
4              
5 1     1   196416 use strict;
  1         2  
  1         46  
6 1     1   6 use warnings;
  1         2  
  1         59  
7              
8 1     1   21 use 5.006;
  1         4  
9 1     1   12 use v5.12.0; # Before 5.006, v5.10.0 would not be understood.
  1         4  
10              
11 1     1   1112 use UUID qw{uuid};
  1         4157  
  1         6  
12 1     1   162 use List::Util qw{uniq any};
  1         3  
  1         82  
13              
14 1     1   7 use parent 'Exporter';
  1         2  
  1         7  
15             our @EXPORT_OK = qw{pivot};
16              
17             sub pivot {
18 1     1 1 175260 my ( $rows, %opts ) = @_;
19              
20             # Extract the pivoted cols.
21 1         4 my @data = uniq map { $_->{ $opts{pivot_into} } } @$rows;
  6         25  
22              
23             # Vital for grouping the data.
24 1         63 my $data_splitter = uuid();
25 1         8 my $row_splitter = uuid();
26              
27             # make sure we always use the same order of keys in comparisons
28             my @key_order = sort grep {
29 4         8 my $subj = $_;
30 7     7   25 !any { $_ eq $subj } ( $opts{pivot_on}, $opts{pivot_into} )
31 1         3 } keys( %{ $rows->[0] } );
  4         23  
  1         5  
32              
33             # First, we group by the nonspecified cols.
34             # We do this by creating string aggregations of the relevant data.
35 1         3 my @set;
36 1         4 foreach my $row (@$rows) {
37 6         11 my @s;
38 6         34 foreach my $key (@key_order) {
39             push( @s,
40 12   50     51 ( $key // '' ) . $data_splitter . ( $row->{$key} // '' ) );
      50        
41             }
42 6         19 push( @set, join( $row_splitter, @s ) );
43              
44             # Stash text rep of row for later
45 6 50       17 $row->{_hash} = join( '', map { $_ || '' } @$row{@key_order} );
  12         43  
46             }
47              
48             # Next, we reverse the process into a hash after a uniq() filter.
49             # Whether this is done with hash keys or uniq() is of little consequence, we would have to reexpand them.
50             my @grouped = map {
51 1         10 my $subj = $_;
  3         7  
52             my %h = map {
53              
54             # We need to pad with undef in some cases.
55 3         30 my @out = split( /\Q$data_splitter\E/, $_ );
  6         32  
56 6 50       16 push( @out, undef ) if @out == 1;
57             @out
58 6         22 } ( split( /\Q$row_splitter\E/, $subj ) );
59 3         9 \%h
60             } uniq(@set);
61              
62             # Next, we have to pivot.
63             @grouped = map {
64 1         5 my $subj = $_;
  3         14  
65              
66             # Make sure to null-fill all the relevant pivoted data points.
67 3         5 foreach my $param (@data) {
68 9         21 $subj->{$param} = undef;
69             }
70 3 50       8 my $hash = join( '', map { $_ || '' } @$subj{@key_order} );
  6         20  
71 3         8 my @relevant_rows = grep { $hash eq $_->{_hash} } @$rows;
  18         39  
72              
73 3         7 foreach my $row (@relevant_rows) {
74              
75             # Append this row's info iff we are in the group.
76 6         13 my $field = $row->{ $opts{pivot_into} };
77 6         16 $subj->{$field} = $row->{ $opts{pivot_on} };
78             }
79             $subj
80 3         8 } @grouped;
81              
82 1         8 return @grouped;
83             }
84              
85             1;
86              
87             __END__