13

I'm looking for ways to express this Python snippet in Perl:

data = {"A": None, "B": "yes", "C": None}
key_list = [k for k in data if data[k]]  
# in this case the same as filter(lambda k: data[k], data) but let's ignore that

So looking at it one way, I just want the keys where the values are None or undef. Looking at it another way, what I want is the concise perl equivalent of a list comprehension with conditional.

conny
  • 9,973
  • 6
  • 38
  • 47
  • 1
    Do you mean you want to _exclude_ the keys where the values are None or undef? That's what your example does. – glenn jackman Jul 11 '09 at 13:45
  • Hmm. In hindsight that *does* sound quite ambiguous. I guess that by "I want the keys" I referred to "I want them - so that I can filter them out" but the question that got answered was the right one: how to do pick certain stuff out of a collection in a one liner. Sorry :) – conny Oct 23 '09 at 09:03
  • normally i use `map{ f($_)} grep{cond($_)} @list` – JJoao Feb 23 '15 at 08:25

3 Answers3

20

I think you want grep:

#!/usr/bin/env perl
use strict;
use warnings;

my %data = ( A => undef, B => 'yes', C => undef );

my @keys = grep { defined $data{$_} } keys %data;

print "Key: $_\n" for @keys;

I also think that I type too slowly, and that I should reload the page before posting answers. By the way, either a value of 0 or undef can be a good way to handle null values, but make sure you remember which you're using. A false value and and undefined value aren't the same thing in Perl. To clarify: undef returns false in a boolean test, but so does 0. If 0 is a valid value, then you want to explicitly test for definedness, not simply truth. (I mention it because James went for 0 and I went the other way, and you may or may not know if it matters.)

Telemachus
  • 19,459
  • 7
  • 57
  • 79
  • It doesn't matter that you type slowly, you get more votes anyway! :) – James Thompson Jul 11 '09 at 01:34
  • Which is odd, since we wrote essentially the same answer. I completely don't get the voting here. – Telemachus Jul 11 '09 at 02:01
  • I think it's a sign that Perl programmers on this site think undef > 0 in this instance. :-P – C. K. Young Jul 11 '09 at 02:43
  • Chris has hit the nail on the head here. I've definitely learnt my lesson. :) Voting is odd. No big deal, and you have good answers anyhow so I don't mind. – James Thompson Jul 11 '09 at 05:22
  • 1
    Yeah the two proposed solutions are very similar, but I think the note about undef was helpful, so I'll go with the majority vote. Thanks, all! – conny Jul 13 '09 at 09:17
13

Use grep:

#!/usr/bin/perl

use strict;
use warnings;

my %data = ("A" => 0, "B" => "yes", "C" => 0 );
my @keys = grep { $data{$_} } keys %data;

Grep returns the values from the list on the right-hand side for which the expression in braces evaluates to a true value. As telemachus points out, you want to make sure you understand true/false values in Perl. This question has a good overview of truth in Perl.

You'll likely want a look at map, which applies an expression in braces to each element of a list and returns the result. An example would be:

my @data = ("A" => 0, "B" => 1, "C" => 0 );
my @modified_data = map { $data{$_} + 1 } @data;
print join ' ', @data, "\n";
print join ' ', @modified_data, "\n";
Community
  • 1
  • 1
James Thompson
  • 46,512
  • 18
  • 65
  • 82
  • Cool. I have seldom seen the "non-m//" generalized grep in Perl. I usually use "map" for stuff like this, with a block like: { <> ? $_ : () }. TMTOWTDI, I guess :-) – Roboprog Jul 11 '09 at 00:24
  • @Roboprog - Thanks. I think this is more concise, but your way is definitely good too. @ysth - can you give a more concrete example? What's up with the !! characters? It's Perl, so I'm tempted to try it. :) – James Thompson Jul 11 '09 at 05:24
  • 1
    @James Thompson: ()x in list context is the list repetition operator. !!() booleanizes; anything true becomes 1, anything false becomes (in the numeric context provided by ()x) 0. So grep { xxx } is equivalent to map { ($_)x!!( xxx ) } since the $_ will be repeated 0 times when xxx is false and once when xxx is true. I was lightly making fun of the idea of using map to do a grep by showing an even sillier way to do it. – ysth Jul 29 '09 at 00:38
  • if i understood, `[f(x) | x in list , pred(x)]` can be coded as `map { f(x) x!! pred(x) } list` cool!! – JJoao Feb 23 '15 at 00:55
6

For variation on the theme have a look at autobox (see its implementations autobox::Core and Moose::Autobox )

use autobox::Core;

my %data = ( A => undef, B => 'yes', C => undef );
my $key_list = %data->keys->grep( sub { defined $data{$_} } );

say "Key: $_" for @$key_list;

# => Key: B


Moose::Autobox comes with key/value 'kv' which makes the code DRYer:

my $key_list = %data->kv->grep( sub{ defined $_->[1] } )->map( sub{ $_->[0] } );

Here is a more explicit and even longer version of above:

my $key_list = %data->kv
                    ->grep( sub { my ($k, $v) = @$_; defined $v } )
                    ->map(  sub { my ($k, $v) = @$_; $k }         );
AndyG
  • 39,700
  • 8
  • 109
  • 143
draegtun
  • 22,441
  • 5
  • 48
  • 71