Greg Kennedy
Greg Kennedy

Reputation: 632

Elegant way to catch "for loop reached last element"?

Sometimes in Perl, I write a for / foreach loop that iterates over values to check a value against a list. After the first hit, the loop can exit, as we've satisfied my test condition. For example, this simple code:

my @animals = qw/cat dog horse/;

foreach my $animal (@animals)
{
  if ($input eq $animal)
  {
    print "Ah, yes, an $input is an animal!\n";
    last;
  }
}
# <-----

Is there an elegant way - an overloaded keyword, maybe - to handle "for loop reached last element"? Something to put at the arrow above?

I can think of ways to do this, like creating / setting an additional $found variable and testing it at the end.... But I was hoping Perl might have something else built in, like:

foreach my $animal (@animals)
{
  if ($input eq $animal)
  {
    print "Ah, yes, an $input is an animal!\n";
    last;
  }
} finally {
  print "Sorry, I'm not sure if $input is an animal or not\n";
}

which would make this test more intuitive.

Upvotes: 4

Views: 1999

Answers (6)

zdim
zdim

Reputation: 66883

It's natural with any from List::Util library

use List::Util qw(any); 

if ( any { $input eq $_ } @animals ) { 
    say "yes $input is among animals" 
} 
else { 
    say "No $input in animals"
}

Upvotes: 2

ikegami
ikegami

Reputation: 385647

It's not the best solution here, but sometimes it helps to iterates over the indexes.

for my $i (0..$#animals) {
    my $animal = $animals[$i];
    ...
}

Then, you can check if the index is 0 (first pass) or $#animals (last pass).

Upvotes: 1

Steven Lembark
Steven Lembark

Reputation: 97

First is going to be the least overhead; eval avoids having to nest it all in if-blocks; newline because you probably don't really care what line it wasn't an animal on.

eval
{
  my $found = first { check for an animal } @animalz
  or die "Sorry, no animal found.\n";

  # process an animal

  1
}
// do
{
  # deal with non-animals
};

Upvotes: 0

Micha
Micha

Reputation: 191

I'd keep it Old School and use a well-known C-idiom (for-loop split up in 1st statement and a while-loop).

#!/usr/bin/env perl

use strict;
use warnings;

my $input = 'lion';

my @animals = qw/cat dog horse/;

my $index = 0;

while ($index < scalar @animals) {
    if ($animals[ $index++ ] eq $input) {
        print "Ah, yes, an $input is an animal!\n";
        last;
    }
}

if ($index == scalar @animals) {
    print "Sorry, I'm not sure if $input is an animal or not\n";
}

Thus, "Sorry, I'm not sure if lion is an animal or not" will come very naturally. Hope, this helps. Regards, M.

Upvotes: 1

Grinnz
Grinnz

Reputation: 9231

Just have the loop set a variable so you can check if it's been set and act on it later:

my $found;
foreach my $animal (@animals) {
    if ($input eq $animal) {
        $found = $animal;
        last outer;
    }
}
print defined $found ? "Ah, yes, an $input is an animal!\n" : "no animal found\n";

But for this particular problem, as @choroba says, just use the first (or any) function from List::Util. Or if you will be checking a lot of inputs, it's easier to check a hash.

my %animals = map { ($_ => 1) } qw/cat dog horse/;
print exists $animals{$input} ? "Ah, yes, an $input is an animal!\n" : "no animal found\n";

Upvotes: 2

clamp
clamp

Reputation: 3262

You can wrap your loop with a labeled block like so:

outer: {
    foreach my $animal (@animals) {
        if ($input eq $animal) {
            print "Ah, yes, an $input is an animal!\n";
            last outer;
        }
    }
    print "no animal found\n";
}

Upvotes: 4

Related Questions