File Coverage

blib/lib/Lingua/EN/PluralToSingular.pm
Criterion Covered Total %
statement 38 38 100.0
branch 40 42 95.2
condition 2 3 66.6
subroutine 4 4 100.0
pod 2 2 100.0
total 86 89 96.6


line stmt bran cond sub pod time code
1             package Lingua::EN::PluralToSingular;
2 1     1   13538 use warnings;
  1         2  
  1         38  
3 1     1   3 use strict;
  1         1  
  1         775  
4             require Exporter;
5             our @ISA = qw(Exporter);
6             our @EXPORT_OK = qw/to_singular is_plural/;
7             our $VERSION = '0.19';
8              
9             # Irregular plurals.
10              
11             # References:
12             # http://www.macmillandictionary.com/thesaurus-category/british/Irregular-plurals
13             # http://web2.uvcs.uvic.ca/elc/studyzone/330/grammar/irrplu.htm
14             # http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
15              
16             # This mixes latin/greek plurals and anglo-saxon together. It may be
17             # desirable to split things like corpora and genera from "feet" and
18             # "geese" at some point.
19              
20             my %irregular = (qw/
21             analyses analysis
22             brethren brother
23             children child
24             corpora corpus
25             craftsmen craftsman
26             crises crisis
27             criteria criterion
28             curricula curriculum
29             feet foot
30             fungi fungus
31             geese goose
32             genera genus
33             gentlemen gentleman
34             indices index
35             lice louse
36             matrices matrix
37             memoranda memorandum
38             men man
39             mice mouse
40             monies money
41             neuroses neurosis
42             nuclei nucleus
43             oases oasis
44             oxen ox
45             pence penny
46             people person
47             phenomena phenomenon
48             quanta quantum
49             strata stratum
50             teeth tooth
51             testes testis
52             these this
53             theses thesis
54             those that
55             women woman
56             /);
57              
58             # Words ending in ves need care, since the ves may become "f" or "fe".
59              
60             # References:
61             # http://www.macmillandictionary.com/thesaurus-category/british/Irregular-plurals
62              
63             my %ves = (qw/
64             calves calf
65             dwarves dwarf
66             elves elf
67             halves half
68             knives knife
69             leaves leaf
70             lives life
71             loaves loaf
72             scarves scarf
73             sheaves sheaf
74             shelves shelf
75             wharves wharf
76             wives wife
77             wolves wolf
78             /);
79              
80             # A dictionary of plurals.
81              
82             my %plural = (
83             # Words ending in "us" which are plural, in contrast to words like
84             # "citrus" or "bogus".
85             'menus' => 'menu',
86             'buses' => 'bus',
87             %ves,
88             %irregular,
89             );
90              
91             # A store of words which are the same in both singular and plural.
92              
93             my @no_change = qw/
94             deer
95             ides
96             fish
97             means
98             offspring
99             series
100             sheep
101             species
102             /;
103              
104             @plural{@no_change} = @no_change;
105              
106             # A store of words which look like plurals but are not.
107              
108             # References:
109              
110             # http://wiki.answers.com/Q/What_are_some_examples_of_singular_nouns_ending_in_S
111             # http://virtuallinguist.typepad.com/the_virtual_linguist/2009/10/singular-nouns-ending-in-s.html
112              
113             my @not_plural = (qw/
114             Aries
115             Charles
116             Gonzales
117             Hades
118             Hercules
119             Hermes
120             Holmes
121             Hughes
122             Ives
123             Jacques
124             James
125             Keyes
126             Mercedes
127             Naples
128             Oates
129             Raines
130             Texas
131             athletics
132             bogus
133             bus
134             cactus
135             cannabis
136             caries
137             chaos
138             citrus
139             clothes
140             corps
141             corpus
142             devious
143             dias
144             facies
145             famous
146             hippopotamus
147             homunculus
148             iris
149             lens
150             mathematics
151             metaphysics
152             metropolis
153             mews
154             minus
155             miscellaneous
156             molasses
157             mrs
158             narcissus
159             news
160             octopus
161             ourselves
162             papyrus
163             perhaps
164             physics
165             platypus
166             plus
167             previous
168             pus
169             rabies
170             scabies
171             sometimes
172             stylus
173             themselves
174             this
175             thus
176             various
177             yes
178             nucleus
179             synchronous
180             /);
181              
182             my %not_plural;
183              
184             @not_plural{@not_plural} = (1) x @not_plural;
185              
186             # A store of words which end in "oe" and whose plural ends in "oes".
187              
188             # References
189             # http://www.scrabblefinder.com/ends-with/oe/
190              
191             # Also used
192              
193             # perl -n -e 'print if /oe$/' < /usr/share/dict/words
194              
195             my @oes = (qw/
196             canoes
197             does
198             foes
199             gumshoes
200             hoes
201             horseshoes
202             oboes
203             shoes
204             snowshoes
205             throes
206             toes
207             /);
208              
209             my %oes;
210              
211             @oes{@oes} = (1) x @oes;
212              
213             # A store of words which end in "ie" and whose plural ends in "ies".
214              
215             # References:
216             # http://www.scrabblefinder.com/ends-with/ie/
217             # (most of the words are invalid, the above list was manually searched
218             # for useful words).
219              
220             # Also get a good list using
221              
222             # perl -n -e 'print if /ie$/' < /usr/share/dict/words
223              
224             # There are too many obscure words there though.
225              
226             # Also, I'm deliberately not including "Bernie" and "Bessie" since the
227             # plurals are rare I think.
228              
229             my @ies = (qw/
230             Aussies
231             Valkryies
232             aunties
233             bogies
234             brownies
235             calories
236             charlies
237             coolies
238             coteries
239             curies
240             cuties
241             dies
242             genies
243             goalies
244             kilocalories
245             lies
246             magpies
247             menagerie
248             movies
249             neckties
250             pies
251             porkpies
252             prairies
253             quickies
254             reveries
255             rookies
256             sorties
257             stogies
258             talkies
259             ties
260             zombies
261             /);
262              
263             my %ies;
264              
265             @ies{@ies} = (1) x @ies;
266              
267             # Words which end in -se, so that we want the singular to change from
268             # -ses to -se. This also contains verbs like "deceases", so that they
269             # don't trigger spell checker errors.
270              
271             my @ses = (qw/
272             automates
273             bases
274             cases
275             causes
276             ceases
277             closes
278             cornflakes
279             creases
280             databases
281             deceases
282             flakes
283             horses
284             increases
285             mates
286             parses
287             purposes
288             races
289             releases
290             tenses
291             /);
292              
293             my %ses;
294             @ses{@ses} = (1) x @ses;
295             # A regular expression which matches the end of words like "dishes"
296             # and "sandwiches". $1 is a capture which contains the part of the
297             # word which should be kept in a substitution.
298              
299             my $es_re = qr/([^aeiou]s|ch|sh)es$/;
300              
301             # Plurals ending -i, singular is either -us, -o or something else
302             # See https://en.wiktionary.org/wiki/Category:English_irregular_plurals_ending_in_%22-i%22
303              
304             # -i to -us
305             my @i_to_us = (qw/
306             abaci
307             abaculi
308             acanthi
309             acini
310             alumni
311             anthocauli
312             bacilli
313             baetuli
314             cacti
315             calculi
316             calli
317             catheti
318             emboli
319             emeriti
320             esophagi
321             foci
322             foeti
323             fumuli
324             fungi
325             gonococci
326             hippopotami
327             homunculi
328             incubi
329             loci
330             macrofungi
331             macronuclei
332             naevi
333             nuclei
334             obeli
335             octopi
336             oeconomi
337             oesophagi
338             panni
339             periƓci
340             phocomeli
341             phoeti
342             platypi
343             polypi
344             precunei
345             radii
346             rhombi
347             sarcophagi
348             solidi
349             stimuli
350             succubi
351             syllabi
352             thesauri
353             thrombi
354             tori
355             trophi
356             uteri
357             viri
358             virii
359             xiphopagi
360             zygomatici
361             /);
362              
363             my %i_to_us;
364             @i_to_us{@i_to_us} = (1) x @i_to_us;
365              
366             # -i to -o
367             my @i_to_o = (qw/
368             alveoli
369             ghetti
370             manifesti
371             ostinati
372             pianissimi
373             scenarii
374             stiletti
375             torsi
376             /);
377              
378             my %i_to_o;
379             @i_to_o{@i_to_o} = (1) x @i_to_o;
380              
381             # -i to something else
382              
383             my %i_to_other = (
384             improvisatori => 'improvisatore',
385             rhinoceri => 'rhinoceros',
386             scaloppini => 'scaloppine'
387             );
388              
389             # See documentation below.
390              
391             sub to_singular
392             {
393 50     50 1 9443 my ($word) = @_;
394             # The return value.
395 50         46 my $singular = $word;
396 50 100       99 if (! $not_plural{$word}) {
397             # The word is not in the list of exceptions.
398 42 100       117 if ($plural{$word}) {
    100          
    100          
399             # The word has an irregular plural, like "children", or
400             # "geese", so look up the singular in the table.
401 12         15 $singular = $plural{$word};
402             }
403             elsif ($word =~ /s$/) {
404             # The word ends in "s".
405 21 100       115 if ($word =~ /'s$/) {
    100          
    50          
    50          
    100          
    100          
    100          
    100          
    100          
406             # report's, etc.
407             ;
408             }
409             elsif (length ($word) <= 2) {
410             # is, as, letter s, etc.
411             ;
412             }
413             elsif ($word =~ /ss$/) {
414             # useless, etc.
415             ;
416             }
417             elsif ($word =~ /sis$/) {
418             # basis, dialysis etc.
419             ;
420             }
421             elsif ($word =~ /ies$/) {
422             # The word ends in "ies".
423 2 100       5 if ($ies{$word}) {
424             # Lies -> lie
425 1         3 $singular =~ s/ies$/ie/;
426             }
427             else {
428             # Fries -> fry
429 1         4 $singular =~ s/ies$/y/;
430             }
431             }
432             elsif ($word =~ /oes$/) {
433             # The word ends in "oes".
434 2 100       16 if ($oes{$word}) {
435             # Toes -> toe
436 1         6 $singular =~ s/oes$/oe/;
437             }
438             else {
439             # Potatoes -> potato
440 1         3 $singular =~ s/oes$/o/;
441             }
442             }
443             elsif ($word =~ /xes$/) {
444             # The word ends in "xes".
445 2         5 $singular =~ s/xes$/x/;
446             }
447             elsif ($word =~ /ses$/) {
448 8 100       12 if ($ses{$word}) {
449 3         9 $singular =~ s/ses$/se/;
450             }
451             else {
452 5         13 $singular =~ s/ses$/s/;
453             }
454             }
455             elsif ($word =~ $es_re) {
456             # Sandwiches -> sandwich
457             # Dishes -> dish
458 1         7 $singular =~ s/$es_re/$1/;
459             }
460             else {
461             # Now the program has checked for every exception it
462             # can think of, so it assumes that it is OK to remove
463             # the "s" from the end of the word.
464 3         10 $singular =~ s/s$//;
465             }
466             }
467             elsif ($word =~ /i$/) {
468 7 100       17 if ($i_to_us{$word}) {
    100          
469 2         6 $singular =~ s/i$/us/;
470             }
471             elsif ($i_to_o{$word}) {
472 2         5 $singular =~ s/i$/o/;
473             }
474 7 100       14 if ($i_to_other{$word}) {
475 2         2 $singular = $i_to_other{$word};
476             }
477             }
478              
479             }
480 50         73 return $singular;
481             }
482              
483             sub is_plural
484             {
485 9     9 1 223 my ($word) = @_;
486 9         11 my $singular = to_singular ($word);
487 9         7 my $is_plural;
488 9 100 66     22 if ($singular ne $word) {
    100          
489 5         4 $is_plural = 1;
490             }
491             elsif ($plural{$singular} && $plural{$singular} eq $singular) {
492 1         1 $is_plural = 1;
493             }
494             else {
495 3         3 $is_plural = 0;
496             }
497 9         22 return $is_plural;
498             }
499              
500             1;