Mohammed
Mohammed

Reputation: 1394

Why if/elsif in Perl execute only the first block?

I am new to Perl. I have an assignment to write a Perl program that accept a countable word from a command line and then generates its plural form. I have composed the following code below, and it shows no errors of compilation. When I execute it from the command line: (perl plural.pl, for example), it prompts me to enter a noun, then whatever noun I feed as input, the plural form is the same. It doesn't execute the remaining if statements.

For example, if I enter the word "cat", the plural is generated as "cats". But when I enter the word 'church', for example, the plural is generated as 'churches', "fly" as "flys".

Here is the code:

#!/usr/bin/perl

$suffix1 = 's';
$suffix2 = 'es';
$suffix3 = 'ies';

print "Enter a countable noun to get plural: ";
$word = <STDIN>;
chomp($word);

if(substr $word, -1 == 'b' or 'd' or 'c' or 'g' or 'r' or 'j' or 'k' or 'l' or 'm' or 'n' or 'p' or 'q' or 'r' or 't' or 'v' or 'w' or 'e' or 'i' or 'o' or 'u') {
    $temp = $word.$suffix1;
    print "The plural form of the word \"$word\" is: $temp \n";
}
elsif (substr $word, -1 == 's' or 'sh' or 'ch' or 'x' or 'z') {
    $temp = $word.$suffix2;
    print "The plural form of the word \"$word\" is: $temp \n";
}
elsif (substr $word, -1 == 'y') {
    chop($word);
    $temp = $word.$suffix3;
    print "The plural form of the word \"$word\" is: $temp \n";
}

Could you help me making the code execute the three statements.

Upvotes: 0

Views: 1666

Answers (5)

gangabass
gangabass

Reputation: 10666

I have changed your code a bit. I'm using regular expression:

#!/usr/bin/perl

$suffix1 = 's';
$suffix2 = 'es';
$suffix3 = 'ies';

print "Enter a countable noun to get plural: ";
$word = <STDIN>;
chomp($word);

if ( $word =~ m/(s|sh|ch|x|z)$/) {
    $temp = $word . $suffix2;
}
elsif ( substr( $word, -1 ) eq 'y' ) {
    chop($word);
    $temp = $word . $suffix3;
}
else {
    $temp = $word . $suffix1;
}

print "The plural form of the word \"$word\" is: $temp \n";

Also I recommend you always use strict; and use warnings;

Upvotes: 0

user1149862
user1149862

Reputation:

  1. In Perl, we use eq for string comparison instead of ==.
  2. You can't use or like this. It should be like if (substr($word, -1) eq 'b' or substr ($word, -1) eq 'd'). Otherwise you could use an array containing all the string that you would like to compare and grep from that array.

Upvotes: 3

David W.
David W.

Reputation: 107080

First of all, always, always include use strict; and use warnings;.

Second, use indentations. I've taught Perl courses at work and refuse to accept any assignment that was not indented correctly. In fact, I'm very, very strict about this because I want users to learn to code to the standard (4 space indent, etc.). It makes your program easier to read and to support.

While we're at it, break overly long lines -- especially on StackOverflow. It's hard to read a program when you have to scroll back and forth.

Quick look at your program:

In Perl, strings and numerics use two different sets of boolean operations. This is because strings can contain only digits, but still be strings. Imagine inventory item numbers like 1384 and 993. If I'm sorting these as strings, the 1384 item comes first. If I am sorting them numerically, 993 should come first. Your program has no way of knowing this except by the boolean operation you use:

Boolean Operation   Numeric  String
=================   =======  ======
Equals              ==       eq
Not Equals          !=       ne
Greater Than        >        gt
Less Than           <        lt
Greater than/Equals >=       ge
Less than/Equals    <=       le

THe other is that an or, and, || and && only work with two booleans. This won't work:

if ( $a > $b or $c ) {

What this is saying is this:

if ( ( $a > $b ) or $c ) {

So, if $c is a non-zero value, then $c will be true, and the whole statement would be true. You have to do your statement this way:

if ( $a > $b or $a > $c ) {

Another thing, use qq(..) and q() when quoting strings that contain quotation marks. This way, you don't have to put a backslash in front of them.

print "The word is \"swordfish\"\n";

print qq(The word is "swordfish"\n);

And, if you use use feature qw(say); at the top of your program, you get the bonus command of say which is like print, except the ending new line is assumed:

say qq(The word is "swordfish");

When you use substr, $foo, -1, you are only looking at the last character. It cannot ever be a two character string:

if ( substr $word, -1 eq "ch" ) {

will always be false.

Long ifs are hard to maintain. I would use a for loop (actually not, but let's pretend for now..):

#! /usr/bin/env perl

#
# Use these in ALL of your programs
#
use strict;
use warnings;
use feature qw(say);

#
# Use better, more descriptive names
#
my $standard_plural_suffix = 's';
my $uncommon_plural_suffix = 'es';
my $y_ending_plural_suffix = 'ies';

print "Enter a countable noun to get plural: ";
chomp (my $word = <STDIN>);

my $plural_form;

#
# Instead of a long, long "if", use a for loop for testing. Easier to maintain
#
for my $last_letter qw( b d c g r j k l m n p q r t v w e i o u) {
    if ( substr($word, -1) eq $last_letter ) {
        $plural_form = $word . $standard_plural_suffix;
        last;
    }
}
#
# Is it an "uncommon plural" test (single character)
#
if ( not $plural_form ) {
    for my $last_letter qw(s x z) {
        if ( substr($word, -1) eq $last_letter ) {
            $plural_form = $word . $uncommon_plural_suffix;
            last;
        }
    }
}
#
# Is it an "uncommon plural" test (double character)
#
if ( not $plural_form ) {
    for my $last_two_letters qw(sh ch) {
        if ( substr($word, -2) eq $last_two_letters ) {
            $plural_form = $word . $uncommon_plural_suffix;
            last;
        }
    }
}
if ( not $plural_form ) {
    if ( substr($word, -1) eq 'y' ) {
        chop ( my $chopped_word = $word );
        $plural_form = $chopped_word . $y_ending_plural_suffix;
    }
}

if ( $plural_form ) {
    say qq(The plural of "$word" is "$plural_form");
}
else {
    say qq(Could not find plural form of "$word");
}

Do you know about regular expressions? Those would work a lot better than using substr because you can test multiple things at once. Plus, I wouldn't use chop, but a regular expression substitution:

#! /usr/bin/env perl

#
# Use these in ALL of your programs
#
use strict;
use warnings;
use feature qw(say);

#
# Use better, more descriptive names
#
my $standard_plural_suffix = 's';
my $uncommon_plural_suffix = 'es';
my $y_ending_plural_suffix = 'ies';

print "Enter a countable noun to get plural: ";
chomp (my $word = <STDIN>);

my $plural_form;

#
# Standard plural (adding plain ol' 's'
#
if ( $word =~ /[bdcgrjklmnpqrtvweiou]$/ ) {
    $plural_form = $word . $standard_plural_suffix;
}
#
# Uncommon plural (adding es)
#
elsif ( $word =~ /([sxz]|[sc]h)$/ ) {
    $plural_form = $word . $uncommon_plural_suffix;
}
#
# Final 'y' rule: Replace y with ies
#
elsif ( $word =~ /y$/ ) {
    $plural_form = $word;
    $plural_form =~ s/y$/ies/;
}

if ( $plural_form ) {
    say qq(The plural of "$word" is "$plural_form");
}
else {
    say qq(Could not find plural form of "$word");
}

Upvotes: 2

ikegami
ikegami

Reputation: 386386

First of all, always use use strict; use warnings;.

  • Strings are compared using eq, not ==.

  • substr $word, -1 eq 'b' means substr $word, (-1 eq 'b') when you meant substr($word, -1) eq 'b'. You'll face lots of problems if you omit parens around function calls.

  • substr($word, -1) eq 'b' or 'd' means the same as (substr($word, -1) eq 'b') or ('d'). 'd' is always true. You'd need to use substr($word, -1) eq 'b' or substr($word, -1) eq 'd'. (Preferably, you'd save substr $word, -1 in a variable to avoid doing it repeatedly.)

  • substr $word, -1 will never equal ch or sh.

The match operator makes this easy:

if ($word =~ /[bdcgrjklmnpqrtvweiou]\z/) {
   ...
}
elsif ($word =~ /(?:[sxz]|[sc]h)\z/) {
   ...
}
elsif ($word =~ /y\z/) {
   ...
}

Upvotes: 7

Eric Jablow
Eric Jablow

Reputation: 7899

Duskast is right. Perl uses symbols for numeric comparisons, and strings for string comparisons.

==   eq
!=   ne
<    lt
<=   le
>    gt
>=   ge
<=>  cmp

Also, your use of or, though a good try, doesn't work. The keyword or has weak precedence, and so the expression

substr $word, -1 == 'b' or 'd' or 'c' or
                    'g' or 'r' or 'j' or
                    'k' or 'l' or 'm' or
                    'n' or 'p' or 'q' or
                    'r' or 't' or 'v' or
                    'w' or 'e' or 'i' or
                    'o' or 'u'

is interpreted as

substr ($word, (-1 == 'b')) or 'd' or 'c' or
                    'g' or 'r' or 'j' or
                    'k' or 'l' or 'm' or
                    'n' or 'p' or 'q' or
                    'r' or 't' or 'v' or
                    'w' or 'e' or 'i' or
                    'o' or 'u'

I'm not sure what the substr works out to, but if it's false, the expression continues to the or 'b', which is interpreted as true. Have you seen regular expressions yet? This is much more idiomatically done as

if ($word =~ /[bdcgrjklmnpqrtvweiou]$/) {...} 
# Does $word match any of those characters followed by
# the end of the line or string?

Look in the Perl docs for string substitution and the s/.../.../ construct.

By the way, if you were paid to do this instead of being a student, you'd use the Lingua modules instead.

Upvotes: 2

Related Questions