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

I have a hash containing keys that refer to particular benchmarks. The hash is populated from a file, and each file contains a baseline benchmark to which the other benchmarks are compared. The set of baseline benchmarks is known and listed in @BASELINES. The hash should always contain exactly one such baseline benchmark.

Needing to both get the baseline and test that exactly one baseline is defined, I wrote something like:

my ($baseline, $oops) = grep { defined } @benchmarks{@BASELINES}; if(not defined $baseline) { die "No baseline case in file" } elsif(defined $oops) { die "Multiple baseline cases in file" }

This works, as far as it goes. However, testing for the definition of the values in the hash creates the corresponding key. Presumably this is because the slice elements are aliases to the original hash, and grep operates on $_, which is also an alias back to the original value. I'm still confused that just testing for definition is enough to create the key, though.

To get around the problem I've used sort in place of grep, sorting so that the defined values appear first:

my ($baseline, $oops) = sort { defined $a ? defined $b ? 0 : -1 : 1 } +@benchmarks{@BASELINES};

This achieves the same effect in this case, and avoids creating the keys, presumably because I'm not using $_ any longer. It's probably less efficient, though, and is not as easy to understand. What I'd really like to know is:

  1. Why does testing for definition of a value create a key?
  2. Is there a way around the key creation?

Replies are listed 'Best First'.
Re: Accidentally creating hash elements with grep { defined } on a hash slice
by ikegami (Patriarch) on Nov 04, 2008 at 11:30 UTC

    Why does testing for definition of a value create a key?

    Bad question. It's not defined that creates it.

    >perl -MData::Dumper -e"grep { 0 } @h{qw(a b)}; print Dumper \%h;" $VAR1 = { 'a' => undef, 'b' => undef };

    grep aliases $_ to the element being tested. That means is needs something to alias to. That causes the element to be created.

    Is there a way around the key creation?

    Yes, pass the keys to grep, not the values.

    my @defined_values = map $benchmarks{$_}, grep defined($benchmarks{$_}), @BASELINES;

    or maybe you really want

    my @defined_values = map $benchmarks{$_}, grep exists($benchmarks{$_}), @BASELINES;

      And what about $a, $b in sort? They are aliases also, but keys are not created.

        I was just asking myself exactly that question. Turns out grep requires lvalues, but sort doesn't.

        >perl -MO=Concise -e"@b = grep f, @h{@a}" ... a <@> hslice lKM ->b ... >perl -MO=Concise -e"@b = sort @h{@a}" ... a <@> hslice lK ->b ...

        Notice the "M" for "Modifiable" in the former.

        So that begs the question: Why does grep require lvalues? The difference is:

        >perl -e"@a = grep { $_='!'; 0 } @h{qw(i j)};" >perl -e"@a = sort { $a='!'; 0 } @h{qw(i j)};" Modification of a read-only value attempted at -e line 1.

        But as far as I can tell, there is no good reason.

Re: Accidentally creating hash elements with grep { defined } on a hash slice
by ccn (Vicar) on Nov 04, 2008 at 11:25 UTC

    Very interesting. The issue doesn't deal with defined. That can be checked by the following:

    my @keys = qw(foo bar baz); my %h; grep '', @h{@keys}; # creates keys 1 for @h{@keys}; # creates keys map '', @h{@keys}; # creates keys my @a = @h{@keys}; # DOESN'T create keys sort {$a <=> $b} @h{@keys}; # DOESN'T create keys

    I think it depends on creating a list for iterations

Re: Accidentally creating hash elements with grep { defined } on a hash slice
by TGI (Parson) on Nov 04, 2008 at 19:27 UTC

    You can use a list slice to get around the key creation, too. An ugly hack it is, though.

    my @keys = qw( a b c d ); my %h = ( b => 'b' ); my @found = grep {defined} (@h{@keys})[0..@keys]; print Dumper \%h; print Dumper \@found

    Output:

    $VAR1 = { 'b' => 'b' }; $VAR1 = [ 'b' ];


    TGI says moo