How about a Schwartzian transform?
#! /usr/bin/perl
use 5.10.0; # for // (aka defined-or)
use warnings;
use strict;
my %data = ...;
# get all subkeys used in %data
my @subkeys = keys %{
{ map { map +($_ => 1),
keys %{ $data{$_} } }
keys %data
}
};
print map qq|$_->[0]: $_->[1] is "$_->[2]"\n|,
sort { $a->[0] cmp $b->[0]
||
$a->[1] cmp $b->[1] }
map { my $key = $_;
map [ $key, $_, $data{$key}{$_} // "" ] =>
@subkeys }
keys %data;
Remember to read Schwartzian transforms from back-to-front. The first—closest to the end—map
flattens or "denormalizes" %data
into a list of records in some unspecified order. The nested map
is necessary to reach the subkeys. To handle arbitrarily deep nesting, define flatten
recursively.
We made an earlier pass to collect all the subkeys used, so if a particular subkey is not present, the value of $data{$key}{$_}
is the undefined value. Using //
, the defined-or operator new in version 5.10.0, specifies a default value of ""
.
With flattened records of the form
[ "KEY1", "SUBKEY3", "75.00" ],
[ "KEY1", "SUBKEY1", "Canada" ],
...
sorting is straightforward: compare the respective first elements (the keys) and if those are equal, fall back to the seconds (the subkeys).
Finally, the outermost map
formats the now-sorted denormalized records for output, and the resulting list goes to the standard output by way of the print
operator.
Output:
KEY1: SUBKEY1 is "Canada"
KEY1: SUBKEY2 is "50.00"
KEY1: SUBKEY3 is "75.00"
KEY2: SUBKEY1 is "Mexico"
KEY2: SUBKEY2 is ""
KEY2: SUBKEY3 is "200.00"
KEY3: SUBKEY1 is ""
KEY3: SUBKEY2 is "150.00"
KEY3: SUBKEY3 is ""
To group subkeys on the same line with the respective key of each, go with code such as
my @subkeys = sort keys %{ ... ;
foreach my $key (sort keys %data) {
my @values;
foreach my $subkey (@subkeys) {
my $value = $data{$key}{$subkey} // "";
push @values => qq|$subkey is "$value"|;
}
local $" = ", ";
print "$key: @values\n";
}
You could write it in a functional style, but the result is a muddy mess:
print map { my $key = $_;
"$key: " .
join(", " =>
map { my $value = $data{$key}{$_} // "";
qq|$_ is "$value"|
}
@subkeys) .
"\n"
}
sort keys %data;
Output:
KEY1: SUBKEY1 is "Canada", SUBKEY2 is "50.00", SUBKEY3 is "75.00"
KEY2: SUBKEY1 is "Mexico", SUBKEY2 is "", SUBKEY3 is "200.00"
KEY3: SUBKEY1 is "", SUBKEY2 is "150.00", SUBKEY3 is ""