Recipe 2.18. Printing Correct Plurals
Author: Tom Christiansen & Nathan Torkington
Date: 1999-07-02T01:30:25Z

#

| Chapter 2 Numbers |

## 2.18. Printing Correct Plurals

### Problem

You're printing something like "`It` `took` `$time` `hours`", but "`It` `took` `1` `hours`" is ungrammatical. You would like to get it right.

### Solution

Use `printf` and a ternary conditional (`X` `?` `Y` `:` `Z`) to alter the noun or verb:

    printf "It took %d hour%s\n", $time, $time == 1 ? "" : "s";

    printf "%d hour%s %s enough.\n", $time,
            $time == 1 ? ""   : "s",
            $time == 1 ? "is" : "are";

Or, use the Lingua::EN::Inflect module from CPAN as described in the Discussion.

### Discussion

The only reason inane messages like "`1` `file(s)` `updated`" appear is because their authors are too lazy to bother checking whether the count is 1 or not.

If your noun changes by more than an `"-s"`, you'll need to change the `printf` accordingly:

    printf "It took %d centur%s", $time, $time == 1 ? "y" : "ies";

This is good for simple cases, but you'll get tired of writing it. This leads you to write funny functions like this:

    sub noun_plural {
        local $_ = shift;
        # order really matters here!
        s/ss$/sses/                             ||
        s/([psc]h)$/${1}es/                     ||
        s/z$/zes/                               ||
        s/ff$/ffs/                              ||
        s/f$/ves/                               ||
        s/ey$/eys/                              ||
        s/y$/ies/                               ||
        s/ix$/ices/                             ||
        s/([sx])$/$1es/                         ||
        s/$/s/                                  ||
                    die "can't get here";
        return $_;
    }
    *verb_singular = \&noun_plural;   # make function alias

As you find more exceptions, your function will become increasingly convoluted. When you need to handle such morphological changes, turn to the flexible solution provided by the Lingua::EN::Inflect module from CPAN.

    use Lingua::EN::Inflect qw(PL classical);
    classical(1);               # why isn't this the default?
    while (<DATA>) {            # each line in the data
        for (split) {           # each word on the line
            print "One $_, two ", PL($_), ".\n";
        }
    }
    # plus one more
    $_ = 'secretary general';
    print "One $_, two ", PL($_), ".\n";

    __END__
    fish fly ox
    species genus phylum
    cherub radius jockey
    index matrix mythos
    phenomenon formula

That produces the following:

    One fish, two fish.
    One fly, two flies.
    One ox, two oxen.
    One species, two species.
    One genus, two genera.
    One phylum, two phyla.
    One cherub, two cherubim.
    One radius, two radii.
    One jockey, two jockeys.
    One index, two indices.
    One matrix, two matrices.
    One mythos, two mythoi.
    One phenomenon, two phenomena.
    One formula, two formulae.
    One secretary general, two secretaries general.

This is one of the many things the module can do. It also handles inflections or conjugations for other parts of speech, provides number-insensitive comparison functions, figures out whether to use a or an, and plenty more.

### See Also

The ternary ("hook-colon") operator discussed in perlop (1) and in the "Conditional Operator" section of Chapter 2 of Programming Perl; the documentation with the CPAN module Lingua::EN::Inflect

2.17. Putting Commas in Numbers |  | 2.19. Program: Calculating Prime Factors