Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plural String Formatting

Given a dictionary of ints, I'm trying to format a string with each number, and a pluralization of the item.

Sample input dict:

data = {'tree': 1, 'bush': 2, 'flower': 3, 'cactus': 0} 

Sample output str:

'My garden has 1 tree, 2 bushes, 3 flowers, and 0 cacti' 

It needs to work with an arbitrary format string.

The best solution I've come up with is a PluralItem class to store two attributes, n (the original value), and s (the string 's' if plural, empty string '' if not). Subclassed for different pluralization methods

class PluralItem(object):     def __init__(self, num):         self.n = num         self._get_s()     def _get_s(self):         self.s = '' if self.n == 1 else 's'  class PluralES(PluralItem):     def _get_s(self):         self.s = 's' if self.n == 1 else 'es'  class PluralI(PluralItem):     def _get_s(self):         self.s = 'us' if self.n == 1 else 'i' 

Then make a new dict through comprehension and a classes mapping:

classes = {'bush': PluralES, 'cactus': PluralI, None: PluralItem} plural_data = {key: classes.get(key, classes[None])(value) for key, value in data.items()} 

Lastly, the format string, and implementation:

formatter = 'My garden has {tree.n} tree{tree.s}, {bush.n} bush{bush.s}, {flower.n} flower{flower.s}, and {cactus.n} cact{cactus.s}' print(formatter.format(**plural_data)) 

Outputs the following:

My garden has 1 tree, 2 bushes, 3 flowers, and 0 cacti 

For such an undoubtedly common need, I'm hesitant to throw in the towel with such a convoluted solution.

Is there a way to format a string like this using the built-in format method, and minimal additional code? Pseudocode might be something like:

"{tree} tree{tree(s)}, {bush} bush{bush(es)}, {flower} flower{flower(s)}, {cactus} cact{cactus(i,us)}".format(data) 

where parentheses return the contents if value is plural, or if contents has comma, means plural/singular

like image 458
mhlester Avatar asked Feb 19 '14 06:02

mhlester


People also ask

How do you make something plural in Python?

For 2-character plural forms (e.g., bush/bushes), use 'es'[:2*i^2] . More generally, for an n-character plural form, replace 2 by n in the previous expression.

What is a string formatting?

String formatting is also known as String interpolation. It is the process of inserting a custom string or variable in predefined text. custom_string = "String formatting" print(f"{custom_string} is a powerful technique") Powered by Datacamp Workspace. String formatting is a powerful technique.


2 Answers

Check out the inflect package. It will pluralize things, as well as do a whole host of other linguistic trickery. There are too many situations to special-case these yourself!

From the docs at the link above:

import inflect p = inflect.engine()  # UNCONDITIONALLY FORM THE PLURAL print("The plural of ", word, " is ", p.plural(word))  # CONDITIONALLY FORM THE PLURAL print("I saw", cat_count, p.plural("cat",cat_count)) 

For your specific example:

{print(str(count) + " " + p.pluralize(string, count)) for string, count in data.items() } 
like image 69
meawoppl Avatar answered Sep 17 '22 11:09

meawoppl


When you have only two forms, and just need a quick and dirty fix, try 's'[:i^1]:

for i in range(5):     print(f"{i} bottle{'s'[:i^1]} of beer.") 

Output:

0 bottles of beer. 1 bottle of beer. 2 bottles of beer. 3 bottles of beer. 4 bottles of beer. 

Explanation:

^ is the bitwise operator XOR (exclusive disjunction).

  • When i is zero, i ^ 1 evaluates to 1. 's'[:1] gives 's'.
  • When i is one, i ^ 1 evaluates to 0. 's'[:0] gives the empty string.
  • When i is more than one, i ^ 1 evaluates to an integer greater than 1 (starting with 3, 2, 5, 4, 7, 6, 9, 8..., see https://oeis.org/A004442 for more information). Python doesn't mind and happily returns as many characters of 's' as it can, which is 's'.

My 1 cent ;)

Bonus. For 2-character plural forms (e.g., bush/bushes), use 'es'[:2*i^2]. More generally, for an n-character plural form, replace 2 by n in the previous expression.

Opposite. In the comments, user @gccallie suggests 's'[i^1:] to add an 's' to verbs in the third person singular:

for i in range(5):     print(f"{i} bottle{'s'[:i^1]} of beer lie{'s'[i^1:]} on the wall.") 

Output:

0 bottles of beer lie on the wall. 1 bottle of beer lies on the wall. 2 bottles of beer lie on the wall. 3 bottles of beer lie on the wall. 4 bottles of beer lie on the wall. 

Python interprets the first form as [:stop], and the second one as [start:].

Edit. A previous, one-character longer version of the original trick used != instead of ^.

like image 25
Aristide Avatar answered Sep 17 '22 11:09

Aristide