18

I'm wondering if Perl has a built-in way to check for the existence of a hash element with a key matching a particular regex. For example:

my %h = ( 'twelve' => 12, 'thirteen' => 13, 'fourteen' => 14 );

I'm wondering if there is any way to do this:

print "We have 12\n" if exists $h{twelve};
print "We have some teens\n" if exists $h{/.*teen$/};
ajwood
  • 18,227
  • 15
  • 61
  • 104

4 Answers4

25

The smart match operator does this (available since Perl v5.10).

$a      $b        Type of Match Implied    Matching Code
======  =====     =====================    =============
...
Regex   Hash      hash key grep            grep /$a/, keys %$b
...

Sample usage:

# print if any key in %h ends in "teen"
print "We have some teens\n" if /.*teen$/ ~~ %h;
mob
  • 117,087
  • 18
  • 149
  • 283
  • 1
    Note that this operator has been in a state of flux and has been deemed "experimental" as of Perl 5.18 (emitting warnings anywhere it's used). http://stackoverflow.com/questions/16927024/perl-5-20-and-the-fate-of-smartmatch-and-given-when/ – Sundar R Oct 13 '13 at 01:18
12

In addition to the other answers here you can also do this with perl's grep:

print "We have some teens\n" if grep {/.*teen/} keys %h;
Flexo
  • 87,323
  • 22
  • 191
  • 272
6

Yeah, it's called:

use List::Util qw<first>;

# Your regex does not compile perhaps you mean /teen$/
my $value = $hash{ ( first { m/teen/ } keys %hash ) || '' };

(Before smart match, that is. See mob's answer for smart match.)

You could also sort the keys:

my $value = $hash{ ( first { m/teen/ } sort keys %hash ) || '' };

I would freeze this into an "operation":

use Scalar::Util qw<reftype>;

sub values_for_keys_like (\[%$]$) {
    my $ref = reftype( $_[0] ) eq 'HASH' ? $_[0] : $$_[0];
    return unless my @keys = keys %$ref;

    my $regex = shift;
    # allow strings
    $regex    = qr/$regex/ unless my $typ = ref( $regex );
    # allow regex or just plain ol' filter functions.
    my $test  = $typ eq 'CODE' ? $regex : sub { return unless m/$regex/; 1 };

    if ( wantarray ) { 
        return unless my @k = grep { defined $test->( $_ ) } @keys;
        return @$ref{ @k };
    }
    else {
        return unless my $key = first { defined $test->( $_ ) } @keys;
        return $ref->{ $key };
    }
}

And you could use it like so:

my $key = values_for_keys_like( %hash => qr/teen/ );

Or

my $key = values_for_keys_like( $base->{level_two}{level_three} => qr/teen/ );
Axeman
  • 29,660
  • 2
  • 47
  • 102
  • `sort`? Eh? There's no reason to sort the keys if you're just testing for whether a match exists. Just `first { m/teen$/ } keys %hash` is better. – Dave Sherohman Mar 04 '11 at 11:44
  • @Dave Sherohman: As usual in SO, there is the general question and there is the particular application the user is looking for. The general question is "Matching hash keys to regular expression". `first` is an implementation of `any` concept--but contains other implications: first *how?* I tend to like to cover more than the specific application. – Axeman Mar 04 '11 at 15:47
  • 2
    I fail to see *any* situation in which sorting is applicable to the question "is there a match?". Whether a match exists or not is independent of order, thus order is irrelevant. The sole effect of sorting is to impose a specific order, but order is irrelevant, so sorting just uses up resources (time/memory) for no purpose. The question here is asking about the `any` concept, independent of order; `first` is appropriate solely because `List::Util` doesn't provide an `any` function. – Dave Sherohman Mar 05 '11 at 11:08
4

There's no built-in way, but there's Tie::Hash::Regex on CPAN.

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378