Thomas L Holaday
Thomas L Holaday

Reputation: 13814

What is the idiomatic way in Perl to determine whether a string variable matches a string in a list?

Part of the specification says "Some names are special, e.g. Hughie, Dewey, Louis, and Donald. Other names may be added over the lifetime of the project at arbitrary times. Whenever you input one of those names, play quack.wav."

I could write ...

while (<>) {
    if ($_ =~ /Hughie|Dewey|Louis/) {
        quack() ;
    }
    elsif ($_ =~ /Donald/ {
        quack() ;
        you_re_fired_apprentice() ; # Easter egg don't tell QA
    }
}

... but though untaxing to implement, it looks WTF-y: Where's the binary search? What if there were a sudden stupendous increase in the number of duck names? It would not scale at all!

I could create empty files using those names in a temporary directory, and then use the "file exists" API, but that seems roundabout, and I would have to be sure they were deleted at the end.

Surely there is a better way?

Upvotes: 7

Views: 247

Answers (6)

thkala
thkala

Reputation: 86333

You can use a Perl Hash. See also How can I represent sets in Perl? and Representing Sets in Perl.

Using hashes to implement a set is not exactly pretty, but it should be fast.

Upvotes: 3

oylenshpeegul
oylenshpeegul

Reputation: 3424

Alternatively, you could use smart matching

my @ducks = qw(Hughie Dewey Louis);
my $name = 'Dewey';

say 'smart match' if $name ~~ @ducks;

This is what is used by switch statements, so you could write

given ($name) {
    when (@ducks) {
        quack();
    }
    when ('Donald') {
        quack();
        you_re_fired_apprentice(); # Easter egg don't tell QA
    }
}

Upvotes: 7

oylenshpeegul
oylenshpeegul

Reputation: 3424

To find a string in a list, you could also use any in List::MoreUtils

use List::MoreUtils qw(any);

my @ducks = qw(Hughie Dewey Louis);
my $name = 'Dewey';

say 'any' if any {$name eq $_} @ducks;

Upvotes: 2

Eric Strom
Eric Strom

Reputation: 40142

You could write that, but you should write this:

my %ducks = map {$_ => 1} qw(Hughie Dewey Louis);

while (<>) {
    if ($ducks{$_}) {
        quack() ;
    }
    elsif ($_ eq 'Donald') {
        quack() ;
        you_re_fired_apprentice() ; # Easter egg don't tell QA
    }
}

Creating the hash takes a little bit of time, but not more than O(n). Lookup with a hash is O(1) though, so it is much more efficient than sequential search (via grep or a regex with alternation) assuming you will be checking for more than one or two items.

By the way, the regex that you have will match the words anywhere in the search string. You need to add start and end anchors if you want an exact match.

Upvotes: 7

Bill Ruppert
Bill Ruppert

Reputation: 9016

As mentioned, hashes are the way to go for this. This is sort of what OOP looked like before OOP.

use strict;
use warnings;

my %duck_action = (
  Donald => sub {quack(); you_re_fired_apprentice()},
  Hughie => sub {quack()},
  Dewie  => sub {quack()},
  Louis  => sub {quack()},
);

for my $duck (qw( Hughie Dewie Donald Louis Porkie )) {
    print "$duck: ";
    my $action = $duck_action{$duck} || &no_such_duck;
    $action->();
}

sub quack {
    print "Quack!\n";
}

sub you_re_fired_apprentice {
    print "You're fired!\n";
}

sub no_such_duck {
    print "No such duck!\n";
}

Upvotes: 4

Wooble
Wooble

Reputation: 89897

If you're tied to using an array rather than a hash, you can use perl's grep function to search the array for a string.

@specialnames = qw(Hughie Dewey Louis);
while (my $value = <>) {
    if (grep {$value eq $_}, @specialnames) {
        quack() ;
    }
    elsif ($_ =~ /Donald/ {
        quack() ;
        you_re_fired_apprentice() ; # Easter egg don't tell QA
    }
}

This does scale a lot worse than a hash, and might even scale worse than copying the array into a hash and then doing hash lookups.

Upvotes: 1

Related Questions