开发者

sort key then by subkey—if subkey undef set to null string

my %data (
    KEY1 => {
     开发者_Go百科   SUBKEY1 => "Canada",
        SUBKEY3 => "75.00",
        SUBKEY2 => "50.00",    
    },
    KEY3 => {
        SUBKEY2 => "150.00",
    },  
    KEY2 => { 
        SUBKEY3 => "200.00",
        SUBKEY1 => "Mexico",
 },
);

How do I print a list that is sorted by Keyname and for each keyname sorted by subkeyname?

Here is what I want to print: (notice that if the subkey wasn't defined, a placeholder for the subkey with a null string is present)

KEY1: SUBKEY1 is "Canada"
KEY1: SUBKEY2 is "50.00"
KEY1: SUBKEY3 is "75.00"
KEY2: SUBKEY1 is ''
KEY2: SUBKEY2 is "150.00"
KEY2: SUBKEY3 is ''
KEY3: SUBKEY1 is "Mexico"
KEY3: SUBKEY2 is ''
KEY3: SUBKEY3 is "200.00"


use strict;
use warnings;

my %data = (
    KEY1 => {
        SUBKEY1 => "Canada",
        SUBKEY3 => "75.00",
        SUBKEY2 => "50.00",    
    },
    KEY3 => {
        SUBKEY2 => "150.00",
    },  
    KEY2 => { 
        SUBKEY3 => "200.00",
        SUBKEY1 => "Mexico",
 },
);

my %all_sub_keys;
for my $sub_hash (values %data){
    $all_sub_keys{$_} ++ for keys %$sub_hash;
}

my @all_sub_keys = sort keys %all_sub_keys;

for my $k ( sort keys %data ){
    for my $sk (@all_sub_keys){
        my $val = exists $data{$k}{$sk} ? $data{$k}{$sk} : '--';
        print join(' ', $k, $sk, $val), "\n";
    }
}


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 ""


I am assuming the set of subkeys is known ahead of time.

#!/usr/bin/perl

use strict; use warnings;

my %data = (
    KEY1 => {
        SUBKEY1 => "Canada",
        SUBKEY3 => "75.00",
        SUBKEY2 => "50.00",
    },
    KEY3 => {
        SUBKEY2 => "150.00",
    },
    KEY2 => {
        SUBKEY3 => "200.00",
        SUBKEY1 => "Mexico",
 },
);

my @subkeys = qw( SUBKEY1 SUBKEY2 SUBKEY3 );

for my $key ( sort keys %data ) {
    my %sub = map {
        my $v = $data{$key}{$_};
        $_ => defined($v) ? $v : '';
    } @subkeys;

    for my $subkey ( @subkeys ) {
        print "$key $subkey $sub{$subkey}\n";
    }
}

Output:

KEY1 SUBKEY1 Canada
KEY1 SUBKEY2 50.00
KEY1 SUBKEY3 75.00
KEY2 SUBKEY1 Mexico
KEY2 SUBKEY2
KEY2 SUBKEY3 200.00
KEY3 SUBKEY1
KEY3 SUBKEY2 150.00
KEY3 SUBKEY3
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜