1pub struct Pluralizer;
7
8impl Pluralizer {
9 pub fn pluralize(singular: &str) -> String {
22 if singular.is_empty() {
24 return singular.to_string();
25 }
26
27 match singular {
28 s if s.ends_with("y")
30 && !s.ends_with("ay")
31 && !s.ends_with("ey")
32 && !s.ends_with("iy")
33 && !s.ends_with("oy")
34 && !s.ends_with("uy")
35 && s.len() > 1 =>
36 {
37 format!("{}ies", &s[..s.len() - 1])
38 }
39
40 s if s.ends_with("s")
42 || s.ends_with("ss")
43 || s.ends_with("sh")
44 || s.ends_with("ch")
45 || s.ends_with("x")
46 || s.ends_with("z") =>
47 {
48 format!("{}es", s)
49 }
50
51 s if s.ends_with("f") && s.len() > 1 => {
53 format!("{}ves", &s[..s.len() - 1])
54 }
55
56 s if s.ends_with("fe") && s.len() > 2 => {
58 format!("{}ves", &s[..s.len() - 2])
59 }
60
61 s if s.ends_with("o") && s.len() > 1 => {
63 let before_o = s.chars().nth(s.len() - 2).unwrap();
64 if matches!(before_o, 'a' | 'e' | 'i' | 'o' | 'u') {
65 format!("{}s", s)
66 } else {
67 match s {
69 "photo" | "piano" | "halo" => format!("{}s", s),
70 _ => format!("{}es", s),
71 }
72 }
73 }
74
75 s => format!("{}s", s),
77 }
78 }
79
80 pub fn singularize(plural: &str) -> String {
92 if plural.is_empty() {
94 return plural.to_string();
95 }
96
97 match plural {
98 s if s.ends_with("ies") && s.len() > 3 => {
100 format!("{}y", &s[..s.len() - 3])
101 }
102
103 s if s.ends_with("ves") && s.len() > 3 => {
105 format!("{}f", &s[..s.len() - 3])
106 }
107
108 s if s.len() > 3
110 && (s.ends_with("ses")
111 || s.ends_with("shes")
112 || s.ends_with("ches")
113 || s.ends_with("xes")
114 || s.ends_with("zes")) =>
115 {
116 s[..s.len() - 2].to_string()
117 }
118
119 s if s.ends_with("oes") && s.len() > 3 => s[..s.len() - 2].to_string(),
121
122 s if s.ends_with("s") && s.len() > 1 => s[..s.len() - 1].to_string(),
124
125 s => s.to_string(),
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_pluralize_regular() {
137 assert_eq!(Pluralizer::pluralize("user"), "users");
138 assert_eq!(Pluralizer::pluralize("car"), "cars");
139 assert_eq!(Pluralizer::pluralize("dog"), "dogs");
140 }
141
142 #[test]
143 fn test_pluralize_y_ending() {
144 assert_eq!(Pluralizer::pluralize("company"), "companies");
145 assert_eq!(Pluralizer::pluralize("category"), "categories");
146 assert_eq!(Pluralizer::pluralize("fly"), "flies");
147
148 assert_eq!(Pluralizer::pluralize("day"), "days");
150 assert_eq!(Pluralizer::pluralize("key"), "keys");
151 }
152
153 #[test]
154 fn test_pluralize_sibilants() {
155 assert_eq!(Pluralizer::pluralize("address"), "addresses");
156 assert_eq!(Pluralizer::pluralize("box"), "boxes");
157 assert_eq!(Pluralizer::pluralize("buzz"), "buzzes");
158 assert_eq!(Pluralizer::pluralize("church"), "churches");
159 assert_eq!(Pluralizer::pluralize("dish"), "dishes");
160 }
161
162 #[test]
163 fn test_pluralize_f_endings() {
164 assert_eq!(Pluralizer::pluralize("knife"), "knives");
165 assert_eq!(Pluralizer::pluralize("life"), "lives");
166 assert_eq!(Pluralizer::pluralize("wolf"), "wolves");
167 }
168
169 #[test]
170 fn test_pluralize_o_endings() {
171 assert_eq!(Pluralizer::pluralize("hero"), "heroes");
172 assert_eq!(Pluralizer::pluralize("potato"), "potatoes");
173
174 assert_eq!(Pluralizer::pluralize("photo"), "photos");
176 assert_eq!(Pluralizer::pluralize("piano"), "pianos");
177 }
178
179 #[test]
180 fn test_singularize_regular() {
181 assert_eq!(Pluralizer::singularize("users"), "user");
182 assert_eq!(Pluralizer::singularize("cars"), "car");
183 assert_eq!(Pluralizer::singularize("dogs"), "dog");
184 }
185
186 #[test]
187 fn test_singularize_ies() {
188 assert_eq!(Pluralizer::singularize("companies"), "company");
189 assert_eq!(Pluralizer::singularize("categories"), "category");
190 assert_eq!(Pluralizer::singularize("flies"), "fly");
191 }
192
193 #[test]
194 fn test_singularize_sibilants() {
195 assert_eq!(Pluralizer::singularize("addresses"), "address");
196 assert_eq!(Pluralizer::singularize("boxes"), "box");
197 assert_eq!(Pluralizer::singularize("buzzes"), "buzz");
198 }
199
200 #[test]
201 fn test_singularize_ves() {
202 assert_eq!(Pluralizer::singularize("knives"), "knif");
203 assert_eq!(Pluralizer::singularize("lives"), "lif");
204 }
205
206 #[test]
207 fn test_roundtrip() {
208 let words = vec!["user", "company", "address", "box", "day"];
209 for word in words {
210 let plural = Pluralizer::pluralize(word);
211 let back_to_singular = Pluralizer::singularize(&plural);
212 assert_eq!(word, back_to_singular, "Roundtrip failed for: {}", word);
213 }
214 }
215}