20
\$\begingroup\$

Is it possible to write this in fewer lines of code?

If you input an integer it will output it as an ordinal number if it is less than 100. The below code works perfectly, but I'm wondering if it could be written more succinctly.

def ordinal(self, num):
    """
      Returns ordinal number string from int, e.g. 1, 2, 3 becomes 1st, 2nd, 3rd, etc.
    """
    self.num = num
    n = int(self.num)
    if 4 <= n <= 20:
      suffix = 'th'
    elif n == 1 or (n % 10) == 1:
      suffix = 'st'
    elif n == 2 or (n % 10) == 2:
      suffix = 'nd'
    elif n == 3 or (n % 10) == 3:
      suffix = 'rd'
    elif n < 100:
      suffix = 'th'
    ord_num = str(n) + suffix
    return ord_num
\$\endgroup\$
  • \$\begingroup\$ Off topic (As this is a python question, not .net), But github.com/MehdiK/Humanizer has extension methods to do this kind of thing nicely e.g., 1.Ordinalize() == "1st" or "21".Ordinalize() == "21st" \$\endgroup\$ – DarcyThomas Feb 10 '14 at 3:41
  • \$\begingroup\$ Yes, it's possible: using num2words; def ordinal(num): num2words(num, to=ordinal_num, lang=en) \$\endgroup\$ – Toby Speight Apr 17 '18 at 12:28
  • \$\begingroup\$ Fewer lines of code" is rarely a worthwhile objective; clear and efficient usually trump short. \$\endgroup\$ – Toby Speight Oct 31 '18 at 17:03
26
\$\begingroup\$
def ordinal(self, num):
    """
      Returns ordinal number string from int, e.g. 1, 2, 3 becomes 1st, 2nd, 3rd, etc.
    """

Its suspicious that this seems to be a method rather than a free standing function.

    self.num = num

Why are you storing the input here? Given the purpose of this function that seems odd.

    n = int(self.num)

Its doubtful that this is a good idea. What are you converting from? Converting to int should be really be done closer to whether this number came from.

    if 4 <= n <= 20:

You've made this case larger than necessary, many of those would be correct even with out this test, and its not clear what so special about the range 4-20.

      suffix = 'th'
    elif n == 1 or (n % 10) == 1:

You don't need the or. If n == 1, then that the second condition will be true anyways.

      suffix = 'st'
    elif n == 2 or (n % 10) == 2:
      suffix = 'nd'
    elif n == 3 or (n % 10) == 3:
      suffix = 'rd'
    elif n < 100:
      suffix = 'th'

What happens if suffix is >= 100? You'll get an error.

    ord_num = str(n) + suffix
    return ord_num

You don't need to split this across two lines.

Here is my version:

# much code can be improved by using a datastructe.
SUFFIXES = {1: 'st', 2: 'nd', 3: 'rd'}
def ordinal(num):
    # I'm checking for 10-20 because those are the digits that
    # don't follow the normal counting scheme. 
    if 10 <= num % 100 <= 20:
        suffix = 'th'
    else:
        # the second parameter is a default.
        suffix = SUFFIXES.get(num % 10, 'th')
    return str(num) + suffix
\$\endgroup\$
  • \$\begingroup\$ You are right about this looking like a method. I actually had this as part of a class. I just pulled it out and pasted it here. I should have functionalized it. Sorry about that. \$\endgroup\$ – holaymolay Feb 10 '14 at 4:57
  • \$\begingroup\$ The n was used to simplify and reduce the number of characters from "self.num" to "n". 4 to 20 is special because it is a big block of 'th' suffixes. I actually saw that in an example I found in a tutorial. \$\endgroup\$ – holaymolay Feb 10 '14 at 5:29
  • 2
    \$\begingroup\$ here it is in one line (the implementation is identical): ordinal = lambda n: str(n) + {1: 'st', 2: 'nd', 3: 'rd'}.get(10<=n%100<=20 and n or n % 10, 'th') \$\endgroup\$ – ejrb Feb 10 '14 at 13:54
  • 1
    \$\begingroup\$ It's worth mentioning that opinions vary on doing calculations in the return. Many people (myself included) tend to prefer returning only results, although the shorter the function the less it tends to matter. \$\endgroup\$ – DSM Feb 10 '14 at 16:25
  • 2
    \$\begingroup\$ @DSM, out of curiosity, why? From my perspective it seems that assigning to a local just to return seems pointless. \$\endgroup\$ – Winston Ewert Feb 11 '14 at 1:56
8
\$\begingroup\$

You can simplify the repeated n == 1 or (n % 10) == 1, as well as special-case test for 11th, 12th, and 13th, by using a ternary expression;

So:

i = n if (n < 20) else (n % 10)
if i == 1:
  suffix = 'st'
elif i == 2:
  suffix = 'nd'
elif i == 3:
  suffix = 'rd'
elif n < 100:
  suffix = 'th'

I think you can also use a dictionary:

suffixes = { 1: "st", 2: "nd", 3: "rd" }
i = n if (n < 20) else (n % 10)
if 0 < i <= 3:
  suffix = suffixes[i]
elif n < 100:
  suffix = 'th'

Or use dictionary get which lets you supply a default value:

suffixes = { 1: "st", 2: "nd", 3: "rd" }
i = n if (n < 20) else (n % 10)
suffix = suffixes.get(i, 'th')

Maybe you can reduce it to one line of code:

suffix = { 1: "st", 2: "nd", 3: "rd" }.get(n if (n < 20) else (n % 10), 'th')

(Beware the above may be buggy because I don't know Python).

\$\endgroup\$

protected by Community Oct 25 '18 at 10:56

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.