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   63808 use warnings;
  1         3  
  1         25  
3 1     1   4 use strict;
  1         2  
  1         953  
4             require Exporter;
5             our @ISA = qw(Exporter);
6             our @EXPORT_OK = qw/to_singular is_plural/;
7             our $VERSION = '0.21';
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             ad-men ad-man
57             admen adman
58             aircraftmen aircraftman
59             airmen airman
60             airwomen airwoman
61             alaskamen alaskaman
62             aldermen alderman
63             anchormen anchorman
64             ape-men ape-man
65             assemblymen assemblyman
66             backwoodsmen backwoodsman
67             bandsmen bandsman
68             barmen barman
69             barrow-men barrow-man
70             batmen batman
71             batsmen batsman
72             beggarmen beggarman
73             beggarwomen beggarwoman
74             behmen behman
75             boatmen boatman
76             bogeymen bogeyman
77             bowmen bowman
78             brakemen brakeman
79             bushmen bushman
80             businessmen businessman
81             businesswomen businesswoman
82             busmen busman
83             byre-men byre-man
84             cabmen cabman
85             cameramen cameraman
86             carmen carman
87             cattlemen cattleman
88             cavalrymen cavalryman
89             cavemen caveman
90             chairmen chairman
91             chairwomen chairwoman
92             chapmen chapman
93             charwomen charwoman
94             chessmen chessman
95             chinamen chinaman
96             churchmen churchman
97             clansmen clansman
98             classmen classman
99             clemen cleman
100             clergymen clergyman
101             coachmen coachman
102             coalmen coalman
103             cognomen cognoman
104             con-men con-man
105             congressmen congressman
106             congresswomen congresswoman
107             councilmen councilman
108             councilwomen councilwoman
109             countrymen countryman
110             countrywomen countrywoman
111             cowmen cowman
112             cracksmen cracksman
113             craftsmen craftsman
114             cragsmen cragsman
115             crewmen crewman
116             cyclamen cyclaman
117             dairymen dairyman
118             dalesmen dalesman
119             doormen doorman
120             draftsmen draftsman
121             draughtsmen draughtsman
122             dustmen dustman
123             dutchmen dutchman
124             englishmen englishman
125             englishwomen englishwoman
126             ex-servicemen ex-serviceman
127             excisemen exciseman
128             fellow-men fellow-man
129             ferrymen ferryman
130             fieldsmen fieldsman
131             firemen fireman
132             fishermen fisherman
133             flagmen flagman
134             footmen footman
135             foremen foreman
136             forewomen forewoman
137             freedmen freedman
138             freemen freeman
139             frenchmen frenchman
140             frenchwomen frenchwoman
141             freshmen freshman
142             frogmen frogman
143             frontiersmen frontiersman
144             g-men g-man
145             gentlemen gentleman
146             gentlewomen gentlewoman
147             germen german
148             god-men god-man
149             gombeen-men gombeen-man
150             groundsmen groundsman
151             guardsmen guardsman
152             gunmen gunman
153             handymen handyman
154             hangmen hangman
155             harmen harman
156             he-men he-man
157             headmen headman
158             helmsmen helmsman
159             hemmen hemman
160             henchmen henchman
161             herdsmen herdsman
162             highwaymen highwayman
163             horsemen horseman
164             horsewomen horsewoman
165             housemen houseman
166             huntsmen huntsman
167             husbandmen husbandman
168             hymen hyman
169             icemen iceman
170             indiamen indiaman
171             infantrymen infantryman
172             irishmen irishman
173             irishwomen irishwoman
174             jazzmen jazzman
175             journeymen journeyman
176             jurymen juryman
177             kinmen kinman
178             kinsmen kinsman
179             kinswomen kinswoman
180             klansmen klansman
181             landsmen landsman
182             laundrymen laundryman
183             laundrywomen laundrywoman
184             lawmen lawman
185             laymen layman
186             liegemen liegeman
187             liftmen liftman
188             linemen lineman
189             linesmen linesman
190             linkmen linkman
191             liverymen liveryman
192             lobstermen lobsterman
193             longshoremen longshoreman
194             lumbermen lumberman
195             madmen madman
196             madwomen madwoman
197             mailmen mailman
198             marksmen marksman
199             medicine-men medicine-man
200             men man
201             merchantmen merchantman
202             mermen merman
203             middlemen middleman
204             midshipmen midshipman
205             militiamen militiaman
206             milkmen milkman
207             minutemen minuteman
208             motormen motorman
209             muffin-men muffin-man
210             musclemen muscleman
211             needlewomen needlewoman
212             newsmen newsman
213             newspapermen newspaperman
214             newswomen newswoman
215             night-watchmen night-watchman
216             noblemen nobleman
217             nomen noman
218             norsemen norseman
219             northmen northman
220             nurserymen nurseryman
221             oarsmen oarsman
222             oarswomen oarswoman
223             oehmen oehman
224             oilmen oilman
225             ombudsmen ombudsman
226             orangemen orangeman
227             pantrymen pantryman
228             patrolmen patrolman
229             pitchmen pitchman
230             pitmen pitman
231             placemen placeman
232             plainsmen plainsman
233             ploughmen ploughman
234             pointsmen pointsman
235             policemen policeman
236             policewomen policewoman
237             postmen postman
238             potmen potman
239             pressmen pressman
240             property-men property-man
241             quarrymen quarryman
242             raftsmen raftsman
243             ragmen ragman
244             railwaymen railwayman
245             repairmen repairman
246             riflemen rifleman
247             roadmen roadman
248             roundsmen roundsman
249             salarymen salaryman
250             salesmen salesman
251             saleswomen saleswoman
252             salmen salman
253             sandwichmen sandwichman
254             schoolmen schoolman
255             scotchmen scotchman
256             scotchwomen scotchwoman
257             scotsmen scotsman
258             scotswomen scotswoman
259             seamen seaman
260             seedsmen seedsman
261             servicemen serviceman
262             showmen showman
263             sidesmen sidesman
264             signalmen signalman
265             snowmen snowman
266             specimen speciman
267             spokesmen spokesman
268             spokeswomen spokeswoman
269             sportsmen sportsman
270             stablemen stableman
271             stamen staman
272             stammen stamman
273             statesmen statesman
274             steersmen steersman
275             supermen superman
276             superwomen superwoman
277             switchmen switchman
278             swordsmen swordsman
279             t-men t-man
280             tallymen tallyman
281             taxmen taxman
282             townsmen townsman
283             tradesmen tradesman
284             trainmen trainman
285             trenchermen trencherman
286             tribesmen tribesman
287             turkmen turkman
288             tutankhamen tutankhaman
289             underclassmen underclassman
290             vestrymen vestryman
291             vonallmen vonallman
292             washerwomen washerwoman
293             watchmen watchman
294             watermen waterman
295             weathermen weatherman
296             welshmen welshman
297             women woman
298             woodmen woodman
299             woodsmen woodsman
300             workmen workman
301             yachtsmen yachtsman
302             yeomen yeoman
303             /);
304              
305             # Words ending in ves need care, since the ves may become "f" or "fe".
306              
307             # References:
308             # http://www.macmillandictionary.com/thesaurus-category/british/Irregular-plurals
309              
310             my %ves = (qw/
311             calves calf
312             dwarves dwarf
313             elves elf
314             halves half
315             knives knife
316             leaves leaf
317             lives life
318             loaves loaf
319             scarves scarf
320             sheaves sheaf
321             shelves shelf
322             wharves wharf
323             wives wife
324             wolves wolf
325             /);
326              
327             # A dictionary of plurals.
328              
329             my %plural = (
330             # Words ending in "us" which are plural, in contrast to words like
331             # "citrus" or "bogus".
332             'menus' => 'menu',
333             'buses' => 'bus',
334             %ves,
335             %irregular,
336             );
337              
338             # A store of words which are the same in both singular and plural.
339              
340             my @no_change = qw/
341             deer
342             ides
343             fish
344             means
345             offspring
346             series
347             sheep
348             species
349             /;
350              
351             @plural{@no_change} = @no_change;
352              
353             # A store of words which look like plurals but are not.
354              
355             # References:
356              
357             # http://wiki.answers.com/Q/What_are_some_examples_of_singular_nouns_ending_in_S
358             # http://virtuallinguist.typepad.com/the_virtual_linguist/2009/10/singular-nouns-ending-in-s.html
359              
360             my @not_plural = (qw/
361             Aries
362             Charles
363             Gonzales
364             Hades
365             Hercules
366             Hermes
367             Holmes
368             Hughes
369             Ives
370             Jacques
371             James
372             Keyes
373             Mercedes
374             Naples
375             Oates
376             Raines
377             Texas
378             athletics
379             bogus
380             bus
381             cactus
382             cannabis
383             caries
384             chaos
385             citrus
386             clothes
387             corps
388             corpus
389             devious
390             dias
391             facies
392             famous
393             hippopotamus
394             homunculus
395             iris
396             lens
397             mathematics
398             metaphysics
399             metropolis
400             mews
401             minus
402             miscellaneous
403             molasses
404             mrs
405             narcissus
406             news
407             octopus
408             ourselves
409             papyrus
410             perhaps
411             physics
412             platypus
413             plus
414             previous
415             pus
416             rabies
417             scabies
418             sometimes
419             stylus
420             themselves
421             this
422             thus
423             various
424             yes
425             nucleus
426             synchronous
427             /);
428              
429             my %not_plural;
430              
431             @not_plural{@not_plural} = (1) x @not_plural;
432              
433             # A store of words which end in "oe" and whose plural ends in "oes".
434              
435             # References
436             # http://www.scrabblefinder.com/ends-with/oe/
437              
438             # Also used
439              
440             # perl -n -e 'print if /oe$/' < /usr/share/dict/words
441              
442             my @oes = (qw/
443             canoes
444             does
445             foes
446             gumshoes
447             hoes
448             horseshoes
449             oboes
450             shoes
451             snowshoes
452             throes
453             toes
454             /);
455              
456             my %oes;
457              
458             @oes{@oes} = (1) x @oes;
459              
460             # A store of words which end in "ie" and whose plural ends in "ies".
461              
462             # References:
463             # http://www.scrabblefinder.com/ends-with/ie/
464             # (most of the words are invalid, the above list was manually searched
465             # for useful words).
466              
467             # Also get a good list using
468              
469             # perl -n -e 'print if /ie$/' < /usr/share/dict/words
470              
471             # There are too many obscure words there though.
472              
473             # Also, I'm deliberately not including "Bernie" and "Bessie" since the
474             # plurals are rare I think.
475              
476             my @ies = (qw/
477             Aussies
478             Valkryies
479             aunties
480             bogies
481             brownies
482             calories
483             charlies
484             coolies
485             coteries
486             curies
487             cuties
488             dies
489             genies
490             goalies
491             kilocalories
492             lies
493             magpies
494             menagerie
495             movies
496             neckties
497             pies
498             porkpies
499             prairies
500             quickies
501             reveries
502             rookies
503             sorties
504             stogies
505             talkies
506             ties
507             zombies
508             /);
509              
510             my %ies;
511              
512             @ies{@ies} = (1) x @ies;
513              
514             # Words which end in -se, so that we want the singular to change from
515             # -ses to -se. This also contains verbs like "deceases", so that they
516             # don't trigger spell checker errors.
517              
518             my @ses = (qw/
519             automates
520             bases
521             cases
522             causes
523             ceases
524             closes
525             cornflakes
526             creases
527             databases
528             deceases
529             flakes
530             horses
531             increases
532             mates
533             parses
534             purposes
535             races
536             releases
537             tenses
538             /);
539              
540             my %ses;
541             @ses{@ses} = (1) x @ses;
542             # A regular expression which matches the end of words like "dishes"
543             # and "sandwiches". $1 is a capture which contains the part of the
544             # word which should be kept in a substitution.
545              
546             my $es_re = qr/([^aeiou]s|ch|sh)es$/;
547              
548             # Plurals ending -i, singular is either -us, -o or something else
549             # See https://en.wiktionary.org/wiki/Category:English_irregular_plurals_ending_in_%22-i%22
550              
551             # -i to -us
552             my @i_to_us = (qw/
553             abaci
554             abaculi
555             acanthi
556             acini
557             alumni
558             anthocauli
559             bacilli
560             baetuli
561             cacti
562             calculi
563             calli
564             catheti
565             emboli
566             emeriti
567             esophagi
568             foci
569             foeti
570             fumuli
571             fungi
572             gonococci
573             hippopotami
574             homunculi
575             incubi
576             loci
577             macrofungi
578             macronuclei
579             naevi
580             nuclei
581             obeli
582             octopi
583             oeconomi
584             oesophagi
585             panni
586             periƓci
587             phocomeli
588             phoeti
589             platypi
590             polypi
591             precunei
592             radii
593             rhombi
594             sarcophagi
595             solidi
596             stimuli
597             succubi
598             syllabi
599             thesauri
600             thrombi
601             tori
602             trophi
603             uteri
604             viri
605             virii
606             xiphopagi
607             zygomatici
608             /);
609              
610             my %i_to_us;
611             @i_to_us{@i_to_us} = (1) x @i_to_us;
612              
613             # -i to -o
614             my @i_to_o = (qw/
615             alveoli
616             ghetti
617             manifesti
618             ostinati
619             pianissimi
620             scenarii
621             stiletti
622             torsi
623             /);
624              
625             my %i_to_o;
626             @i_to_o{@i_to_o} = (1) x @i_to_o;
627              
628             # -i to something else
629              
630             my %i_to_other = (
631             improvisatori => 'improvisatore',
632             rhinoceri => 'rhinoceros',
633             scaloppini => 'scaloppine'
634             );
635              
636             # See documentation below.
637              
638             sub to_singular
639             {
640 50     50 1 16779 my ($word) = @_;
641             # The return value.
642 50         69 my $singular = $word;
643 50 100       107 if (! $not_plural{$word}) {
644             # The word is not in the list of exceptions.
645 42 100       169 if ($plural{$word}) {
    100          
    100          
646             # The word has an irregular plural, like "children", or
647             # "geese", so look up the singular in the table.
648 12         30 $singular = $plural{$word};
649             }
650             elsif ($word =~ /s$/) {
651             # The word ends in "s".
652 21 100       114 if ($word =~ /'s$/) {
    100          
    50          
    50          
    100          
    100          
    100          
    100          
    100          
653             # report's, etc.
654             ;
655             }
656             elsif (length ($word) <= 2) {
657             # is, as, letter s, etc.
658             ;
659             }
660             elsif ($word =~ /ss$/) {
661             # useless, etc.
662             ;
663             }
664             elsif ($word =~ /sis$/) {
665             # basis, dialysis etc.
666             ;
667             }
668             elsif ($word =~ /ies$/) {
669             # The word ends in "ies".
670 2 100       6 if ($ies{$word}) {
671             # Lies -> lie
672 1         4 $singular =~ s/ies$/ie/;
673             }
674             else {
675             # Fries -> fry
676 1         4 $singular =~ s/ies$/y/;
677             }
678             }
679             elsif ($word =~ /oes$/) {
680             # The word ends in "oes".
681 2 100       6 if ($oes{$word}) {
682             # Toes -> toe
683 1         3 $singular =~ s/oes$/oe/;
684             }
685             else {
686             # Potatoes -> potato
687 1         3 $singular =~ s/oes$/o/;
688             }
689             }
690             elsif ($word =~ /xes$/) {
691             # The word ends in "xes".
692 2         17 $singular =~ s/xes$/x/;
693             }
694             elsif ($word =~ /ses$/) {
695 8 100       17 if ($ses{$word}) {
696 3         10 $singular =~ s/ses$/se/;
697             }
698             else {
699 5         17 $singular =~ s/ses$/s/;
700             }
701             }
702             elsif ($word =~ $es_re) {
703             # Sandwiches -> sandwich
704             # Dishes -> dish
705 1         8 $singular =~ s/$es_re/$1/;
706             }
707             else {
708             # Now the program has checked for every exception it
709             # can think of, so it assumes that it is OK to remove
710             # the "s" from the end of the word.
711 3         11 $singular =~ s/s$//;
712             }
713             }
714             elsif ($word =~ /i$/) {
715 7 100       23 if ($i_to_us{$word}) {
    100          
716 2         7 $singular =~ s/i$/us/;
717             }
718             elsif ($i_to_o{$word}) {
719 2         7 $singular =~ s/i$/o/;
720             }
721 7 100       17 if ($i_to_other{$word}) {
722 2         5 $singular = $i_to_other{$word};
723             }
724             }
725              
726             }
727 50         92 return $singular;
728             }
729              
730             sub is_plural
731             {
732 9     9 1 395 my ($word) = @_;
733 9         13 my $singular = to_singular ($word);
734 9         11 my $is_plural;
735 9 100 66     26 if ($singular ne $word) {
    100          
736 5         7 $is_plural = 1;
737             }
738             elsif ($plural{$singular} && $plural{$singular} eq $singular) {
739 1         1 $is_plural = 1;
740             }
741             else {
742 3         4 $is_plural = 0;
743             }
744 9         29 return $is_plural;
745             }
746              
747             1;