UPDATE – This code is now available on GitHub. Click here for the repo.
I recently wanted a pluralization inflector in a C# project, one similar to the one in Ruby on Rails. Unable to find a satisfactory one, I whipped up my own. Here’s what I’ve got.
namespace MyNamespace { public class Formatting { private static readonly IList<string> Unpluralizables = new List<string> { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "deer" }; private static readonly IDictionary<string, string> Pluralizations = new Dictionary<string, string> { // Start with the rarest cases, and move to the most common { "person", "people" }, { "ox", "oxen" }, { "child", "children" }, { "foot", "feet" }, { "tooth", "teeth" }, { "goose", "geese" }, // And now the more standard rules. { "(.*)fe?", "$1ves" }, // ie, wolf, wife { "(.*)man$", "$1men" }, { "(.+[aeiou]y)$", "$1s" }, { "(.+[^aeiou])y$", "$1ies" }, { "(.+z)$", "$1zes" }, { "([m|l])ouse$", "$1ice" }, { "(.+)(e|i)x$", @"$1ices"}, // ie, Matrix, Index { "(octop|vir)us$", "$1i"}, { "(.+(s|x|sh|ch))$", @"$1es"}, { "(.+)", @"$1s" } }; public static string Pluralize(int count, string singular) { if (count == 1) return singular; if (Unpluralizables.Contains(singular)) return singular; var plural = ""; foreach (var pluralization in Pluralizations) { if (Regex.IsMatch(singular, pluralization.Key)) { plural = Regex.Replace(singular, pluralization.Key, pluralization.Value); break; } } return plural; } } }
And of course, some NUnit tests.
namespace AutomatedTests { [TestFixture] public class FormattingTests { [Test] public void StandardPluralizationTests() { var dictionary = new Dictionary<string, string>(); dictionary.Add("sausage", "sausages"); // Most words - Just add an 's' dictionary.Add("status", "statuses"); // Words that end in 's' - Add 'es' dictionary.Add("ax", "axes"); // Words that end in 'x' - Add 'es' dictionary.Add("octopus", "octopi"); // Some Words that end in 'us' - Replace 'us' with 'i' dictionary.Add("virus", "viri"); // Some Words that end in 'us' - Replace 'us' with 'i' dictionary.Add("crush", "crushes"); // Words that end in 'sh' - Add 'es' dictionary.Add("crutch", "crutches"); // Words that end in 'ch' - Add 'es' dictionary.Add("matrix", "matrices"); // Words that end in 'ix' - Replace with 'ices' dictionary.Add("index", "indices"); // Words that end in 'ex' - Replace with 'ices' dictionary.Add("mouse", "mice"); // Some Words that end in 'ouse' - Replace with 'ice' dictionary.Add("quiz", "quizzes"); // Words that end in 'z' - Add 'zes' dictionary.Add("mailman", "mailmen"); // Words that end in 'man' - Replace with 'men' dictionary.Add("man", "men"); // Words that end in 'man' - Replace with 'men' dictionary.Add("wolf", "wolves"); // Words that end in 'f' - Replace with 'ves' dictionary.Add("wife", "wives"); // Words that end in 'fe' - Replace with 'ves' dictionary.Add("day", "days"); // Words that end in '[vowel]y' - Replace with 'ys' dictionary.Add("sky", "skies"); // Words that end in '[consonant]y' - Replace with 'ies' foreach (var singular in dictionary.Keys) { var plural = dictionary[singular]; Assert.AreEqual(plural, Formatting.Pluralize(2, singular)); Assert.AreEqual(singular, Formatting.Pluralize(1, singular)); } } [Test] public void IrregularPluralizationTests() { var dictionary = new Dictionary<string, string>(); dictionary.Add("person", "people"); dictionary.Add("child", "children"); dictionary.Add("ox", "oxen"); foreach (var singular in dictionary.Keys) { var plural = dictionary[singular]; Assert.AreEqual(plural, Formatting.Pluralize(2, singular)); Assert.AreEqual(singular, Formatting.Pluralize(1, singular)); } } [Test] public void NonPluralizingPluralizationTests() { var nonPluralizingWords = new List<string> { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "deer" }; foreach (var word in nonPluralizingWords) { Assert.AreEqual(word, Formatting.Pluralize(2, word)); Assert.AreEqual(word, Formatting.Pluralize(1, word)); } } } }
And finally, usage.
var output = Formatting.Pluralization(2, "item"); // Produces "items" output = Formatting.Pluralization(5, "sheep"); // Produces "sheep" output = Formatting.Pluralization(100, "sausage"); // Produces "sausages" output = Formatting.Pluralization(1, "sausage"); // Produces "sausage"
Now, I’m sure that I’m missing some cases in there. For example, I haven’t found a good way to pluralize “proof.” If any of you wonderful people find another missing case, or if you want to add one, let me know in the comments.