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