Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making sentence/word plural based on value

Tags:

I'm wondering if there is a preferred or at least more readable/pretty/pythonic way to make a sentence plural based on data being put into it.

Here's how I'm doing it now

ret = f'Done! {deleted} entr{"y was" if deleted == 1 else "ies were"} removed.'

I know this works, but it just doesn't sit well when I go and read it. I've thought of either making a function or using a dict-switch (which is something I kinda like) but maybe someone knows a better way.

Solutions I've tried:

using a dict

plural = {1:'y was'}
ret = f'Done! {deleted} entr{plural.get(deleted, "ies were")} removed.'

using a function

def plural(num: int):
    return 'y was' if num == 1 else 'ies were'

using boolean operators

ret = f'Done! {deleted} entr{deleted != 1 and "ies were" or "y was"} removed.'

Those are really the only other ways I can think of right now that makes sense, also the reason I want this is because I have multiple places where I'd need to make words plural. The issue is, I may want to use different wording in places and would like to keep from repeating myself with making word(s) plural for human-readable format.

like image 673
Jab Avatar asked Dec 03 '18 08:12

Jab


2 Answers

Maybe in this single instance (if we are talking a few strings to be declinated) then such approaches are sufficient.

But in general case, localization (L10N) and internationalization (I12N) is best handled with suitable libraries.

In case of python we have a standard library gettext that can be used for this purpose. It is effectively a python API to GNU gettext which is a neat option for a start. There are supplementary resources:

  1. Step-by-step guide how to localize your python script.
  2. A more in-depth introduction to GNU gettext in the context if python.
  3. Or another one if the previous does not suit you.
  4. And finally, much more involved but comprehensive python internationalization guide.

In short, there is much more to L10N and I12N than plurals...

like image 157
sophros Avatar answered Nov 14 '22 11:11

sophros


While I found @sophros answer very knowledgeable and informative, I decided it was more than I needed. I decided to write my own custom class as I needed something cheap and easy and that I can reuse for different words/sentences. Feel free to use yourself if you like it!

class Plural:
    __slots__ = 'word', 'value', 'singular', 'plural', 'zero', 'ignore_negatives'

    def __init__(self, value: int, word: str = "", **kwargs):
        """
        Parameters
        ----------
        value : int
            The determining value
        word : str, optional
            The word to make plural. (defaults to "")
        singular : str, optional
            Appended to `word` if `value` == 1. (defaults to '')
        plural : str, optional
            Appended to `word` if `value` > 1. (defaults to 's')
        zero : str, optional
            Replaces `value` if `value` == 0. (defaults to 0)
        ignore_negatives : bool, optional
            This will raise ValueError if `value` is negative. (defaults to False)
            Set to True if you don't care about negative values.
        """

        self.value, self.word = value, word
        self.singular = kwargs.pop('singular', '')
        self.plural = kwargs.pop('plural', 's')
        self.zero = kwargs.pop('zero', 0)
        self.ignore_negatives = kwargs.pop('ignore_negatives', False)

    def __str__(self):
        v = self.value
        pluralizer = self.plural if abs(v) > 1 else self.singular

        if v < 0 and not self.ignore_negatives:
            raise ValueError

        return f"{v or self.zero} {self.word}{pluralizer}"

Test that it works

print(Plural(-2, singular="entry", plural="entries", ignore_negatives = True))
#-2 entries
print(Plural(-1, singular="entry", plural="entries", ignore_negatives = True))
#-1 entry
print(Plural(0, singular="entry", plural="entries"))
#0 entries
print(Plural(1, singular="entry", plural="entries"))
#1 entry
print(Plural(2, singular="entry", plural="entries"))
#2 entries

With Negative Value

print(Plural(-1, singular="entry", plural="entries"))
#Traceback (most recent call last):                                                                                            
#File "/home/main.py", line 53, in <module>                                                                                  
#    print(Plural(-1, singular="entry", plural="entries"))                                                                     
#  File "/home/main.py", line 43, in __str__                                                                                   
#    raise ValueError                                                                                                          
#ValueError

Other use cases

print(Plural(1, "entr", singular="y", plural="ies"))
#1 entry
print(Plural(2, "entr", singular="y", plural="ies"))
#2 entries
print(Plural(0, "value", zero="No"))
#No value
print(Plural(1, "value"))
#1 Value
print(Plural(2, "value"))
#2 Values

If you just want a quick and dirty fix

Either use one of my examples in the question or make a method like in my question but here's a tweaked version as @Ev. Kounis and @9769953 suggested in the comments (hope ya'll don't mind me putting ur suggestions in the answer)

def plural(num: int, word: str = "", single: str = "", mult: str = 's'):
    return f"{num} {(plural, singular)[abs(num) == 1]}"
like image 32
Jab Avatar answered Nov 14 '22 09:11

Jab