http://qs1969.pair.com?node_id=11140005

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi fellow monks, I'm looking for a solution to join together all the second-level keys from a hash constructed like that :
%replaces = 
{ "foo"
   => ( 'W100' => 'W102',
        'W101' => 'W103',
		...),
"bar"
   => ( 'W120' => 'W99',
        'W121' => 'W104',
		...),
... }
For instance, the hash shown below should led to the string W100|W101|W120|W121. Would it be possible to find a one-liner expression, or at least, a graceful piece of code to perform this ? I tried this :
my $expr = join '|', keys(%{$replaces{$_}}) foreach keys(%replaces);
But $expr remains undefined after this line. I ended up hardcoding the expecting first-level values for the current run, but it's a dirty solution :
my $expr = join '|', (keys(%{$replaces{'foo'}}), keys(%{$replaces{'bar'}}));
Thanks for any help on that !

Replies are listed 'Best First'.
Re: One-liner to join keys on a two-dimensionnal hash ?
by ikegami (Patriarch) on Dec 29, 2021 at 14:44 UTC

    Close.

    my $expr = join '|', map { keys( %{ $replaces{$_} } ) } keys( %replaces );

    With foreach:

    my @keys; for ( keys( %replaces ) ) { push @keys, keys( %{ $replaces{$_} } ); } my $expr = join '|', @keys;
      OP here!
      Thanks a lot for your help, I didn't know that map could be used such a way (i.e. having an output instead of altering every item in a list)..
      Have a greet day!

        That the point of map. It takes a list of inputs, and produces a different list from those inputs. It does so by applying the transformation function (the block or expression) to each input, and collecting the outputs of that function.

        Examples:

        my @uc = ( uc("abc"), uc("def") ); | v my @uc = map { uc($_) } "abc", "def";
        my %h = ( "abc" => 1, "def" => 1 ); | v my %h = map { $_ => 1 } "abc", "def";

        I didn't know that map could be used such a way (i.e. having an output instead of altering every item in a list)

        Erm, that is what map is for! :) Perl::Critic even provides a ProhibitVoidMap policy to warn you of code that uses map in void context.

        I suggest you follow the simple stylistic advice given in Effective Perl Programming in the item "Use foreach, map and grep as appropriate" namely:

        • Use foreach to iterate read-only over each element of a list
        • Use map to create a list based on the contents of another list
        • Use foreach to modify elements of a list
        • Use grep to select elements in a list

Re: One-liner to join keys on a two-dimensionnal hash ?
by johngg (Canon) on Dec 29, 2021 at 15:11 UTC

    Perhaps a bit simpler with values since the second-level hashes you are getting the keys of are the values of the first-level hash. However, if order is important you will have to sort keys in some fashion.

    johngg@abouriou:~/perl/Monks$ perl -Mstrict -Mwarnings -E ' my %replaces = ( foo => { W100 => q{W102}, W101 => q{W103} }, bar => { W120 => q{W99}, W121 => q{W104} }, ); my $expr = join q{|}, map { keys %{ $_ } } values %replaces; say $expr;' W121|W120|W101|W100

    I hope this is helpful.

    Cheers,

    JohnGG

Re: One-liner to join keys on a two-dimensionnal hash ?
by LanX (Saint) on Dec 29, 2021 at 15:31 UTC
    my solution:
    use strict; use warnings; my %replaces = ( "foo" => { 'W100' => 'W102', 'W101' => 'W103', }, "bar" => { 'W120' => 'W99', 'W121' => 'W104', }, ); my $expr = join '|', map {keys %$_} values %replaces; print $expr;

    (similar to ikegami's but DRYer)

    There are multiple issues with your code:

    • your data structure was buggy since it swaps ( with {
    • your my $expr is scoped° to the loop, hence undef outside
    • $expr = will be overwritten with each iteration

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    °) kind of, strict doesn't complain.