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

I have to do an operation of each key of a hash that isn't listed in a "@do_not_want" array.

How can I get this to work?
#!/usr/bin/perl -w use strict; my %hash; # don't want these keys in the subroutine $hash{'ytd'} = 1.5; $hash{'mtd'} = 2.0; $hash{'wtd'} = 2.5; # store what I don't want here my @do_not_want = qw{ ytd mtd wtd }; # want these keys and any others in subroutine $hash{'Jumbo_Tron'} = "United Center"; $hash{'Meat_Pie'} = "Gross"; $hash{'Word'} = "Association"; # my try, grepping each item in @do_not_want into a larger grep foreach my $anything_but ( grep { grep { }, @do_not_want }, keys %hash + ) { # print every key except for "ytd", "mtd", and "wtd" print $anything_but . "\n"; }

Replies are listed 'Best First'.
Re: Selecting a Specific Keys in Relation to a List
by GrandFather (Saint) on Nov 01, 2007 at 03:07 UTC

    You really want that reject list as a hash. Consider:

    my @do_not_want = qw{ ytd mtd wtd }; my %rejects; @rejects{@do_not_want} = (); foreach my $anything_but ( grep {! exists $rejects{$_}} keys %hash ) { print $anything_but . "\n"; }

    which in the context of the rest of your sample prints:

    Meat_Pie Jumbo_Tron Word

    The magic bit is @rejects{@do_not_want} = (); which uses a hash slice (see Slices) to create a hash with an entry for each unique element in @do_not_want.

    Update: provided better hash slice link


    Perl is environmentally friendly - it saves trees
Re: Selecting a Specific Keys in Relation to a List
by artist (Parson) on Nov 01, 2007 at 03:07 UTC
    Hint: make use of %do_not_want and use !defined $do_not_want{$key}
    --Artist
Re: Selecting a Specific Keys in Relation to a List
by Anonymous Monk on Nov 01, 2007 at 05:32 UTC
    another approach is to use an intermediate hash and the  delete built-in.

    C:\@Work\Perl>perl -wMstrict -e "my %h = qw(a 1 b 2 c 3 d 4); my @not_wanted = qw(b d); my %wanted = %h; delete @wanted{ @not_wanted }; print qq(key $_ => value $wanted{$_} \n) for keys %wanted" Outputs: key c => value 3 key a => value 1
Re: Selecting a Specific Keys in Relation to a List
by tuxz0r (Pilgrim) on Nov 01, 2007 at 17:54 UTC
    I don't think there is a need for extra data structures for this, just use the @do_not_want in a loop and skip those keys from %hash that match one of them:
    my %hash = ( ytd => 1.5, mtd => 2.0, wtd => 2.5, Jumbo_Tron => "United Center", Meat_Pie => "Gross", "Word" => "Association", ); my @do_not_want = qw{ ytd mtd wtd }; foreach my $k (keys %hash) { next if scalar grep { m/$k/ } @do_not_want; print "$k => $hash{$k}\n"; }
    Which would output the following. You just replace the print line with whatever operatiosn you need to perform on the "wanted" keys of %hash.
    Meat_Pie => Gross Jumbo_Tron => United Center Word => Association

    ---
    echo S 1 [ Y V U | perl -ane 'print reverse map { $_ = chr(ord($_)-1) } @F;'

      While your algorithm works (mostly), it trades a small memory gain for a lot of extra CPU work, especially as @do_not_want grows.

      For every key in %hash, the code must loop through every member of @do_not_want. Grep does not short-circuit, so even after you've found 'ytd', you are still going to check 'mtd' and 'wtd'.

      The other problem with this code is that if $hash{ytd_total} = 1000; is a desired report value, your grep will match and incorrectly exclude that value.

      As long as pure textual equivalence is the matching criteria, using a hash for the lookup is going to be faster and make a tiny impact on memory.

      If you need to do some kind of complex matching, then there is no way (that I know of) to avoid nested loops. But you can use a short circuited loop to avoid checking for any matches after the first. List::MoreUtils any comes to mind as an excellent way to handle this situation.

      Here's a version that uses a hash

      my %hash = ( ytd => 1.5, mtd => 2.0, wtd => 2.5, Jumbo_Tron => "United Center", Meat_Pie => "Gross", "Word" => "Association", ); my %do_not_want; @do_not_want{ qw(ytd mtd wtd) } = (); foreach my $k (keys %hash) { next if exists $do_not_want{$k}; print "$k => $hash{$k}\n"; }

      Here's a version that matches hash keys against a regex:

      use List::MoreUtils qw(any); my %hash = ( ytd => 1.5, mtd => 2.0, wtd => 2.5, Jumbo_Tron => "United Center", Meat_Pie => "Gross", "Word" => "Association", ); my @do_not_want = ( qr/ytd/, qr/mtd/, qr/wtd/ ); foreach my $k (keys %hash) { next if any { $k =~ $_ } @do_not_want ; print "$k => $hash{$k}\n"; }

      Warning, all this code is untested. I modified it in a browser edit box.


      TGI says moo

        I agree about using exists on a hash of "unwanted" keys instead of the array. Good point. I didn't go that route due to the small number of keys (3) in the example. For larger sets of keys to check against, a grep on a array would definitely not be the way to go.

        And, the matching in the regex is easily fixed by using /^$k$/ instead, which would allow the possible exceptions like ytd_total, etc.

        Hadn't thought about List::MoreUtils, either.

        ---
        echo S 1 [ Y V U | perl -ane 'print reverse map { $_ = chr(ord($_)-1) } @F;'