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

Once upon a time...

I wrote a piece of code that did not exactly do what I wanted. Consider the following:

use strict ; use warnings ; use Data::Dumper ; my %hash_origin = ( 'key1' => 1, 'key2' => 1, 'key3' => 1 ) ; my @keys = qw( key1 key1 key2 key2 key2 ) ; my %hash ; foreach ( @keys ) { my $key = $_ ; $hash{ $key } += $hash_origin{ $key } ; } print Dumper( \%hash ) ; __END__ $VAR1 = { 'key1' => 2, 'key2' => 3 };

What was missing was the key 'key3' in the result because the result had to match the original hash! So I went back into solitude and pondered the following solution:

my %hash2 ; my @keys_origin = keys %hash_origin ; @hash2{@keys_origin} = (0) x @keys_origin ; foreach ( @keys ) { my $key = $_ ; if ( exists $hash2{ $key } ) { $hash2{ $key } += $hash_origin{ $key } ; } } print Dumper( \%hash2 ) ; __END__ $VAR1 = { 'key3' => 0, 'key2' => 3, 'key1' => 2 };

Awesome! Was it not until last Thursday that I knew I had found the solution for these kind of things, but then this pesky little code had sprung from my hands:

my %hash3_origin = ( 'key1' => [1, 2], 'key2' => [3, 4], 'key3' => [5, + 6] ) ; my @keys3_0 = qw( key1 key1 key2 key2 key2 ) ; my @keys3_1 = qw( key2 key3 key3 key3 key3 ) ; my %hash3 ; my @keys3_origin = keys %hash3_origin ; @hash3{@keys3_origin} = ([0, 0]) x @keys3_origin ; foreach ( @keys3_0 ) { my $key = $_ ; $hash3{ $key }->[0] += $hash3_origin{ $key }->[0] ; } foreach ( @keys3_1 ) { my $key = $_ ; $hash3{ $key }->[1] += $hash3_origin{ $key }->[1] ; } print Dumper( \%hash3 ) ; __END__ $VAR1 = { 'key3' => [ 11, 28 ], 'key1' => $VAR1->{'key3'}, 'key2' => $VAR1->{'key3'} };

As yo can see in the result this is not what is intended. So I went back into solitude. (And I am not kidding, my gf was calling me the other day: Can you stop doing your Perl Monkey stuff!!!). But nevertheless, I found this (and here it comes) ugly solution by replacing this line:

@hash3{@keys3_origin} = ([0, 0]) x @keys3_origin ;

into:

@hash3{@keys3_origin} = map [($_) x 2], (0) x @keys3_origin ;

But but but... why can't I just:

@hash3{@keys3_origin} = ((0, 0)) x @keys3_origin ; or @hash3{@keys3_origin} = (@{[0, 0]}) x @keys3_origin ;

Because scalar says: There is no equivalent operator to force an expression to be interpolated in list context because in practice, this is never needed. If you really wanted to do so, however, you could use the construction @{[ (some expression) ]} , but usually a simple (some expression) suffices.

Never needed? Well, there is only one way to say this: I need that right here! I want x in list-list context! And it seems there is no way to do it!

Dear Monks: Is there any way to do this more beautifully? I just can't grasp the ugliness of the solution that I have found! I seek for a nice to read one line instruction similar to the one that I already found.

Replies are listed 'Best First'.
Re: Best way to initialize hash x repetition array?
by ikegami (Patriarch) on Jun 29, 2018 at 20:38 UTC

    The repeated expression is only evaluated once, so you are reusing the same anonymous array for each element of the hash. You can map instead of the repetition operator if you want the expression to be evaluated for each repetition.

    For example, the problem can be solved by replacing

    my %hash3; @hash3{keys(%hash3_origin)} = ([0, 0]) x keys(%hash3_origin);
    with
    my %hash3; @hash3{keys(%hash3_origin)} = map { [0, 0] } 1..keys(%hash3_origin);

    That said, the following are much simpler:

    my %hash3; $hash3{$_} = [0, 0] for keys(%hash3_origin);
    or
    my @hash3 = map { $_ => [0, 0] } keys(%hash3_origin);

      Ah, thanks for that and of course! I was so focused on getting this x operation to work that I completely lost attention to other useful constructs. I really like the one with 'for'. I avoid using map where I can (personal preference)

      The x operator is too bad though. The repeat operator x does repeat the array reference because as you said: The repeated expression is only evaluated once. But (0) in (0) x ... is also evaluated once, but still we get a copy of 0 for each assignment and we can get a reference to 0 for every assignment if we want too. So why can't the language detect when it is dealing with an array instead of an array reference and return a copy of eachthe 'evaluated' array expression for us too...

      The last thing that I'd like to find now is something that automatically generates [0, 0] or [0, 0, 0 ...]. It would be easy to forget to change the size of the initialization list in code after changing the size of the arrays in the hash.

        The last thing that I'd like to find now is something that automatically generates 0, 0 or 0, 0, 0 ....

        [0]{} Perl> print [ (0) x $_ ] for 1 .. 4;; ARRAY(0x36fed28) ARRAY(0x36fecf8) ARRAY(0x36fece0) ARRAY(0x36fecc8) [0]{} Perl> print @{ [ (0) x $_ ] } for 1 .. 4;; 0 0 0 0 0 0 0 0 0 0

        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
        In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit