Convert singular to plural

27

3

There are two forms of nouns, singular and plural. The conversion between these two is quite easy.

  1. Normally, you end it with s. ex. car => cars.

  2. If it ends with s,x,z,ch or sh, end it with es. ex. bus=>buses.

  3. If it ends with y with a consonant just before it, change the y to ies. ex. penny => pennies.

  4. If it ends with f or fe, change it to ves. ex. knife => knives.

  5. If it ends with o with a consonant just before it, change it to oes. ex.potato => potatoes.


Task

You will be given a singular noun. You have to convert the given noun to plural and output it.


Rules

  • You will not be given irregular nouns, like mouse and moose.

  • You will not be given exceptions, such as safe (safes; violating #4), piano (pianos; violating #5) and o (oes, violating #5).

  • You will not be given words which have two or more possible plural forms, such as mosquito (mosquitos or mosquitoes) and roof (roofs or rooves).

  • You will not be given uncountable nouns.

  • y doesn't count as a vowel.


Examples

car => cars
bus => buses
potato => potatoes
knife => knives
penny => pennies
exception => exceptions
wolf => wolves
eye => eyes
decoy => decoys
radio => radios

Matthew Roh

Posted 2017-03-05T08:24:31.383

Reputation: 5 043

Edited question for clarity. Feel free to rollback. – JungHwan Min – 2017-03-05T17:03:31.793

11Ahh, English - a huge pile of arbitrary rules and special cases :) – Esolanging Fruit – 2017-03-05T23:16:15.093

38@Challenger5 Yep, but you can understand it through tough thorough thoughts, though. ;) – JungHwan Min – 2017-03-05T23:20:56.930

@MatthewRoh I've edited the consonant in front rule to make it clearer. Also added a couple of test cases for the same. If I've misunderstood, please edit it to clarify. – ghosts_in_the_code – 2017-03-06T04:20:55.487

Do we need to handle o => os or oes? – DLosc – 2017-03-06T07:37:31.707

@DLosc It's stated in Rule 5. o=>oes. – Matthew Roh – 2017-03-06T07:38:40.740

2@Challenger5 If you compare English to Dutch there are barely any rules at all.. Dutch has rules and special cases, and special cases contradicting those special cases, and in some cases even special cases that contradict those special cases that those special cases contradict. ;) – Kevin Cruijssen – 2017-03-06T08:13:47.283

I guess excluding proper nouns? – Viktor Mellgren – 2017-03-07T11:20:40.180

@KevinCruijssen you guys should see French... – Quentin – 2017-03-07T12:32:39.417

@Quentin Yeah, French is a pain as well.. ;) I was glad I could drop the subject when I went to the fourth class in high school.. Always had around a 5 for French and German.. I kinda suck with languages (although I had an 8 for Dutch and English classes..) Ah, the memories.. XD – Kevin Cruijssen – 2017-03-07T12:39:02.593

Answers

46

Mathematica, 9 bytes

Pluralize

Yes, there is a built-in for this!

Sample output

Pluralize["car"]

cars

Pluralize /@ {"bus", "potato", "knife", "penny", "exception", "wolf", "eye"}

{"buses", "potatoes", "knives", "pennies", "exceptions", "wolves", "eyes"}

JungHwan Min

Posted 2017-03-05T08:24:31.383

Reputation: 13 290

6Waaaaaat! Is there something Mathematica has no built-in for? – KeyWeeUsr – 2017-03-06T20:28:13.850

2D: Builtins have attacked this challenge too – Matthew Roh – 2017-03-15T13:18:02.833

1

@KeyWeeUsr https://codegolf.stackexchange.com/a/71680/56033

– Azor Ahai – 2017-08-25T23:42:07.260

18

Retina, 57 53 56 55 58 57 bytes

Thanks to MartinEnder for some golfing suggestions

Thanks to BusinessCat for golfing 1 byte

([^aeiou]o|sh?|ch|z|x)$
$1e
fe?$
ve
([^aeiou])y$
$1ie
$
s

Try it online!

Explanation (outdated)

([^aeiou])y$
$1ie

Changes {consonant}y to {consonant}ie

([^aeiou]o|[fxzs]|[sc]h)$
$&e

Appends an e to when the word ends with an {consonant}o, f,x,z,s,sh or ch.

fe$
ve

Changes an ending fe to ve

$
s

Finally append an s to the word.

Edits

  • Added bytes because I forgot the second rule
  • Added bytes to update with eye as an example

user41805

Posted 2017-03-05T08:24:31.383

Reputation: 16 320

1Sorry if this is a stupid question, I've not used Retina. Why are the round brackets needed in the first line? – user2390246 – 2017-03-06T07:21:47.240

Never mind, I think I've answered my own question. It's because of the lookback reference in the following line. – user2390246 – 2017-03-06T08:45:48.140

Yeah, it's because we want to capture the character before the y using $1 – user41805 – 2017-03-06T11:00:29.277

I think I got it in 57 bytes: Try it online

– Business Cat – 2017-03-06T15:56:02.477

16

JavaScript (ES6),  109  97 bytes

s=>s[R='replace'](/([^aeiou])y$/,'$1ie')[R](/fe?$/,'ve')[R](/([^aeiou]o|[sxz]|[cs]h)$/,'$1e')+'s'

Try it online!

Arnauld

Posted 2017-03-05T08:24:31.383

Reputation: 111 334

Why do you have a () in front of fe? – Kodos Johnson – 2017-03-05T19:05:17.687

1@KodosJohnson All replace() iterations include a reference to the first matching group (with $1). That's why I need an empty matching group here. – Arnauld – 2017-03-05T19:11:36.847

Have you tried (?<![aeiou])y? – Titus – 2017-03-06T09:25:09.273

@Titus Unfortunately, JS doesn't implement lookbehind assertions. – Arnauld – 2017-03-06T11:45:18.043

11

Batch, 325 bytes

@set/ps=
@for %%v in (a e i o u)do @(
for %%e in (o y)do @if %s:~-2%==%%v%%e goto s
if %s:~-2%==%%vf set s=%s:~,-1%ve&goto s
if %s:~-3%==%%vfe set s=%s:~,-2%ve&goto s
)
@if %s:~-1%==y set s=%s:~,-1%ie
@for %%e in (o s x z)do @if %s:~-1%==%%e set s=%s%e
@for %%e in (c s)do @if %s:~-2%==%%eh set s=%s%e
:s
@echo %s%s

Neil

Posted 2017-03-05T08:24:31.383

Reputation: 95 035

What about @echo off at the beginning rather than @ everywhere? Also, @set/ps= seems a little bit rusty from a phone. Won't the s variable accept the slicing values anyway? – KeyWeeUsr – 2017-03-09T07:44:33.573

@KeyWeeUsr @echo off is already 9 bytes without the newline, so it doesn't save me anything. Also, @set/ps= is needed to input the value in the first place. – Neil – 2017-03-09T08:46:38.140

7

Haskell, 216 207 205 bytes

Thanks to @Lynn, @user1472751 and @Laikoni for the help!

import Data.List
(!)s=or.map(\x->x`isSuffixOf`s)
c=['b'..'z']\\"eiou"
p s|s!(words"s x z ch sh"++map(:"o")c)=s++"es"|s!map(:"y")c=init s++"ies"|s!["f"]=init s++"ves"|s!["fe"]=(init.init)s++"ves"|0<1=s++"s"

Readable

import Data.List;

endsWithOneOf :: String -> [String] -> Bool
endsWithOneOf str ends = (or . map (\end -> end `isSuffixOf` str)) ends 

consonants :: [Char]
consonants = ['a'..'z'] \\ "aeiou"

pluralize :: String -> String
pluralize str
    | str `endsWithOneOf` (words "s x z ch sh" ++ (map (:"o") consonants)) = str ++ "es"
    | str `endsWithOneOf` (map (:"y") consonants) = init str ++ "ies"
    | str `endsWithOneOf` ["f"] = init str ++ "ves"
    | str `endsWithOneOf` ["fe"] = (init.init) str ++ "ves"
    | otherwise = str ++ "s"

Explanation

import Data.List for the function isSuffixOf. endsWithOneOf ( in the golfed version) returns whether one of the list elements is an ending of the string. consonants(c) is just a list of all consonants.

Finally, pluralize(p) checks for the endings and returns the proper pluralization.

Example:

p "potato" == "potatoes"

Eisfunke

Posted 2017-03-05T08:24:31.383

Reputation: 101

1Nice solution!

This is 216 characters, but is multiple bytes long, making your solution 226 bytes. (Code golf challenges are explicitly scored in bytes, because counting characters lets you cheat sometimes.) You can just rename it to !, though!

Also, words"s x z ch sh" saves 5 bytes. Removing parens around (map(:"o")c)) and (map(:"y")c)) saves 4 more. – Lynn – 2017-03-06T09:58:08.290

Thanks for the help, @Lynn! I implemented your suggestions. – Eisfunke – 2017-03-06T14:56:05.920

2You can save one byte by using c=['b'..'z']\\"eiou" since 'a' is always removed. – user1472751 – 2017-03-06T15:59:20.943

10<1 is one byte shorter than True. Also newlines are the same byte count as ; but make the golfed code a bit better readable. – Laikoni – 2017-03-07T19:13:35.263

5

PHP, 103 100 bytes

<?=preg_replace(['/([^aeiou]o|sh?|x|z|ch)$/','/(?<![aeiou])y$/','/fe?$/'],['\1e',ie,ve],$argv[1]).s;

Try it online!

The preg_replace function takes in an array of patterns and replacements.

  • Saved 2 bytes thanks to Titus.
  • Saved 1 byte thanks to Dewi Morgan.

Kodos Johnson

Posted 2017-03-05T08:24:31.383

Reputation: 776

2I think You can save one byte with -R and $argn. And using an assertion with y saves two: (?<![aeiou])y$ allows ie as replacement: no \1, no quotes. – Titus – 2017-03-06T09:17:36.287

1Another byte from ([^aeiou]o|sh?|x|z|ch)$ – Dewi Morgan – 2017-03-06T17:54:53.287

@Titus Actually it looks like there is a 1 byte penalty for using -R (but not -r) so that doesn't change the byte count, unfortunately. But the lookbehind suggestion works great. Thanks.

– Kodos Johnson – 2017-03-06T19:32:29.093

5

Röda, 80 bytes

f&s{s~="([^aeiou])y$","$1ie","([sxz]|[cs]h|[^aeiuo]o)$","$1e","fe?$","ve"s.="s"}

The function modifies its argument. Usage: main word { f word; print word } Here's a version that uses a return value (83 bytes):

f s{s~="([^aeiou])y$","$1ie","([sxz]|[cs]h|[^aeiuo]o)$","$1e","fe?$","ve";[s.."s"]}

And below is a function that reads infinitely many values from the input stream and pushes plural forms to the output stream (87 83 bytes):

{replace"([^aeiou])y$","$1ie","([sxz]|[cs]h|[^aeiuo]o)$","$1e","fe?$","ve","$","s"}

It's an anonymous function, as that is shorter than creating a named function.

fergusq

Posted 2017-03-05T08:24:31.383

Reputation: 4 867

How do you get to display the result of the first function (the one starting with f&s)? Simply f("word") doesn't seem to display anything – user41805 – 2017-03-05T19:02:10.707

@KritixiLithos The parameter is a reference, so the argument must be a variable. – fergusq – 2017-03-05T19:04:02.433

5

Perl, 66 + 2 (-pl flag) = 68 bytes

$_.=/(ch|sh?|x|z|[^aeiou]o)$/+s/([^aeiou])y$/$1i/+s/fe?$/v/?es:"s"

Using:

perl -ple '$_.=/(ch|sh?|x|z|[^aeiou]o)$/+s/([^aeiou])y$/$1i/+s/fe?$/v/?es:"s"' <<< car

Try it on Ideone.

Denis Ibaev

Posted 2017-03-05T08:24:31.383

Reputation: 876

4

Python 3, 271 239 199 bytes

Thanks to @ovs for reducing it by 72 bytes!

lambda s,v="aeiou":(s[-2:]=="fe"and s[:-2]+"ve"or s[:-1]+((s[-1]=="y"and s[-2]not in v)*"ie"or s[-1]=="f"and"ve"or s[-1]+((s[-1]in"sxz"or s[-2:]in["ch","sh"])+(s[-1]=="o"and s[-2]not in v))*"e"))+"s"

Try it online!

numbermaniac

Posted 2017-03-05T08:24:31.383

Reputation: 639

1

You can remove some unnecessary whitespaces and combine the first and last elif. The single character lists can be replaced by strings. Switching to python saves additional 3 bytes. tio

– ovs – 2017-03-06T06:37:57.467

@ovs Done, thanks! I didn't combine the elifs however, because that means potato becomes potaties. – numbermaniac – 2017-03-06T06:47:17.950

1

I looked in the wrong line ;). You can combine the if with the last elif. To save some more bytes replace the last line with print(s+"s") and remove the else case as well every s you are appending to the word. Tio

– ovs – 2017-03-06T07:06:22.193

1

When you replace your if/elif logic with and/* and or/+ and make an unnamed lambda function you can get it under 200 bytes (I swapped the cases a little bit)

– ovs – 2017-03-06T07:35:08.547

@ovs Ooh, that print(s+"s") is clever. All changed; you pretty much rewrote the whole thing lol. Thanks! (I didn't even know you could do True and "string" like that) – numbermaniac – 2017-03-06T07:51:06.737

2

sed, 70 79 bytes

69 78 + 1 for -E (BSD)/-r (GNU) flag

s/([^aeiou])y$/\1ie/
s/([^aeiou]o|[fxzs]|[sc]h)$/&e/
s/fe/ve/
s/$/s/

Direct port of the retina answer.

Kevin

Posted 2017-03-05T08:24:31.383

Reputation: 501

2

Pip, 63 61 bytes

Y`[^aeiou]`OaR[C`sh?|x|z|ch`Cy.'y`fe?`y.'o].'$[_B.'i'v_].'e's

So close to catching Retina! But it's probably not going to happen. :(

Try it online!

Explanation

Basic strategy: Replace performs several replacements one after the other when given lists of patterns and replacements. We want to make the following replacements:

  • (sh?|x|z|ch)$ -> add an e
  • [^aeiou]y -> change the y to i and add an e
  • fe? -> change to v and add an e
  • [^aeiou]o -> add an e

Then we want to tack on an s regardless.

Tricks:

  • The C operator, given a regex, wraps it in a capturing group; C`xyz` is one byte shorter than `(xyz)`.
  • A list of regexes or replacements that all end with the same character can be created by concatenating the character to the list instead of including it in all the items. Concatenating a Scalar (string) to a Pattern (regex/replacement) coerces to a Pattern.
  • Instead of concatenating the s (and having to deal with the precedence ordering of R and .), we can simply Output the main part of the word and then print the s separately.

Spaced and commented code:

                  a is 1st cmdline input (implicit)
Y`[^aeiou]`       Yank the consonant regex into the y variable
O a R             Output (without newline): a, with the following replacements:
 [                List of regexes to replace:
  C `sh?|x|z|ch`    (sh?|x|z|ch)
  Cy . 'y           ([^aeiou])y
  `fe?`             fe?
  y . 'o            [^aeiou]o
 ] . '$           End of list; concatenate $ to each item
 [                List of replacements:
  _                 Identity function (replace with whole match)
  B                 B is short for {b}, a function returning its second argument; as a
                    callback function for regex replacement, the second argument is
                    the value of capturing group 1 (the consonant before y)
    . 'i            To that, concatenate i
  'v                Scalar literal v
  _                 Identity function
 ] . 'e           End of list; concatenate e to each item
's                Return Scalar literal s, which is autoprinted

DLosc

Posted 2017-03-05T08:24:31.383

Reputation: 21 213

2

C#, 73 163 bytes:

Func<string,string>p=System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(System.Globalization.CultureInfo.CurrentCulture).Pluralize

Yes, another language with it built-in (although you need to add a reference to System.Data.Entity.Design.dll)

To use:

var words = new[] { "car", "bus", "potato", "knife", "penny", "exception", "wolf", "eye", "decoy", "radio" };
foreach (var word in words)
{
    var plural = p(word);
    Console.Out.WriteLine($"{word} => {plural}");
}

Output:

car => cars
bus => buses
potato => potatoes
knife => knives
penny => pennies
exception => exceptions
wolf => wolves
eye => eyes
decoy => decoys
radio => radios

RoadieRich

Posted 2017-03-05T08:24:31.383

Reputation: 121

Welcome to the site. How do I run this code? – Post Rock Garf Hunter – 2017-03-06T16:06:50.037

@WheatWizard updated. Should I have included more detail (using statements etc) in the byte count? – RoadieRich – 2017-03-06T18:28:51.907

Interesting bit of trivia, the reverse of this (Singularize) fails quite a few simple test cases. For example, it's convinced the singular of "courses" is "cours". – Morgan Thrapp – 2017-03-06T21:42:27.627

I think the namespaces needs to be included in this one's byte count, especially given that it's not one of the 'normal' ones. But I think you also need to at least wrap this in a lambda, passing the argument to the method. As is this is just a method group – pinkfloydx33 – 2017-03-07T00:34:45.963

@pinkfloydx33 better now? – RoadieRich – 2017-03-07T16:18:57.427

2

Python 199 187 176 Bytes

lambda s:s+'\bve'*(s[-1]=='f')+'\b\bve'*(s[-2:]=='fe')+'e'*(s[-1]in'sxz'or s[-2:]in('ch','sh')or s[-1]=='o'and s[-2]not in'aiueo')+'\bie'*(s[-1]=='y'and s[-2]not in'aiueo')+'s'

Felipe Nardi Batista

Posted 2017-03-05T08:24:31.383

Reputation: 2 345

2

Rails runner, 18 bytes

$><<gets.pluralize

Example:

$ echo knife | rails r filename.rb
knives

SztupY

Posted 2017-03-05T08:24:31.383

Reputation: 3 639

Now that's an esoteric language. – Ven – 2017-03-09T09:24:07.967

2

Python, 296 bytes

z = input()
if z[-1]in['s','x','z','ch','sh']:print(z+'es')
elif z[-1]=='y'and z[-2]not in['a','e','i','o','u']:print(z[:-1]+'ies')
elif z[-2:]=='fe':print(z[:-2]+'ves')
elif z[-1]=='f':print(z[:-1]+'ves')
elif z[-1]=='o'and z[-2]not in['a','e','i','o','u']:print(z[:-1]+'oes')
else:print(z+'s')

just_floating

Posted 2017-03-05T08:24:31.383

Reputation: 41

0

C, 321 bytes

#define E else if(
#define C unsigned char
C*p(C*b){static C r[999],i,w,n,m;for(n=w=i=0;r[i]=b[i];n=w,w=b[i++]);m=!strchr("aeiou",n);if(strchr("sxz",w)||(w=='h'&&strchr("cs",n))||(w=='o'&&m))r[i++]='e';E'y'==w&&m)r[i-1]='i',r[i++]='e';E'f'==w)r[i-1]='v',r[i++]='e';E'f'==n&&w=='e')r[i-2]='v';r[i++]='s';r[i]=0;return r;}

test:

C*mx[]={"car","bus","potato","knife","penny","exception","wolf","eye","decoy","radio",0};

main()
{unsigned i;
 for(i=0;mx[i];++i)
    printf("[%s] [%s]\n", mx[i], p(mx[i]));
 return 0;
}

results:

[car] [cars]
[bus] [buses]
[potato] [potatoes]
[knife] [knives]
[penny] [pennies]
[exception] [exceptions]
[wolf] [wolves]
[eye] [eyes]
[decoy] [decoys]
[radio] [radios]
[radio] [radios]

RosLuP

Posted 2017-03-05T08:24:31.383

Reputation: 3 036

It should be wolves not wolfves. – mbomb007 – 2017-04-06T18:07:20.947

@ceilingcat what about "static C r[256],/Z="aeiou",i=0,w,n;" in the place of "static C r[256];C/Z="aeiou",i=0,w,n;"? – RosLuP – 2019-12-04T11:08:15.647

1260 bytes – ceilingcat – 2019-12-05T09:54:44.037

0

Direct port of Retina:

Ruby, 111 bytes

'sub(/([^aeiou])y/){"#{$1}ie"};sub(/(.*)([^aeiou]o|[fxzs]|[sc]h)$/){"#{$1}#{$2}e"};sub(/fe/,"ve");sub(/$/,"s")'

Try it online!

Invoke via ruby -lpe and supply a file as input.txt for first CLI argument.

stephanmg

Posted 2017-03-05T08:24:31.383

Reputation: 261

Can probably be more 'golfed'. Btw.: Can one add files to TIO? – stephanmg – 2019-12-03T13:31:52.447

-1

Java 7, 408 bytes

Golfed:

boolean b="bcdfghjklmnpqrstvwxyzs".contains(String.valueOf(s.charAt(s.length()-2))); String x=s.substring(0,s.length()-1);if(s.endsWith("s")||s.endsWith("x")||s.endsWith("z")||s.endsWith("ch")||s.endsWith("sh"))return s+"es";if(s.endsWith("y")&&b)return x+"ies";if(s.endsWith("f")) return x+"ves";if(s.endsWith("fe"))return s.substring(0,s.length()-2)+"ves";if(s.endsWith("o")&&b)return s+"es";return s+="s";

Basically testing what the end of the String is and adding / replacing letters depending on what case it is. The boolean and String at the beginning are just for removing repetition in the test cases and making the code smaller.

Readable version:

public static String pluralize(String s){

// Consonant at the 2nd last position?
boolean b = "bcdfghjklmnpqrstvwxyzs".contains(String.valueOf(s.charAt(s.length()-2))); 

// Substring for cases where last letter needs to be replaced
String x = s.substring(0,s.length()-1);

if(s.endsWith("s") || s.endsWith("x") || s.endsWith("z") || s.endsWith("ch") || s.endsWith("sh"))
    return s + "es";
if(s.endsWith("y") && b)
    return x + "ies";
if(s.endsWith("f")) 
    return x + "ves";
if(s.endsWith("fe"))
    return s.substring(0,s.length()-2) + "ves";
if(s.endsWith("o") && b)
    return s + "es";

return s += "s";
}

Nick

Posted 2017-03-05T08:24:31.383

Reputation: 15

6You can't use a snippet. – Okx – 2017-03-06T10:10:18.103