git.net

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]# Ordered Ordinal number methods

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[NOTE: message is a tad long as it discusses multiple possible solutions and concerns including code.] The original question was how to do some reasonable translation from something like the "switch" statement in languages that have it, including C and R. Other languages use their own variants like cond in LISP. Some have nothing quite like it. They also differ in many ways such as the ability to handle arbitrary conditions or bundle multiple conditions to result in the same result. AS noted, Python deliberately omitted this feature entirely and it is suggested that one method is to use multiple "elif" clauses. I did a search to see if some module may play games with that notation to solve the specific issue relating to how to produce strings from the numbers in the range of 1:31 used in months on the calendar used in much of the world. (there are calendars limited to about 29 I use regularly.) Here is a page where a method is shown using that construction: https://codereview.stackexchange.com/questions/41298/producing-ordinal-numbe rs 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 The above is not my code. It is an example of how someone else solved a similar problem. It is clearly not a free function but a method. You can easily modify it and I annotate it below that way as a simple function to ask some dumb questions. But first, the IF is followed by a sequence of ELIF and no ELSE at the end. It works but is not written in a style I might choose. It does illustrate a sort of way to do cases where a switch might be used elsewhere. My earlier examples using inline and nested IF statements realistically could be expanded into something more like this, too. Here is the altered code with comments: def ordinal(num): """ Returns ordinal number string from int, e.g. 1, 2, 3 becomes 1st, 2nd, 3rd, etc. """ n = int(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 I do like the way the above function accepts any kind of argument that can be converted to an int. It does have a serious flaw in that ordinal(100) and beyond generate an error. It does return 0th on 0 and the slightly odd -1th, -2th and so on. My approach was to look for patterns and note we cared only as a first approximation at numbers ending with 1,2,3 as special and even then, only if the preceding column was a 1. The teens are an exception. The above uses a different paradigm using inequalities so anything between 4 and 20 (inclusive) is noted as a "th" then a slightly redundant check for ending in 1 is checked as the sole exception of 11 has already been handled earlier. Now is the following redundant or efficient? elif n == 1 or (n % 10) == 1: The reality is that "1 % 10 " also matches n being 1. The OR though is a short-circuit so in the case that n == 1, the mod operator and comparison is skipped. Going on, a similar check is made for numbers ending in 2 and then 3. The last condition requires the number to be less than 100. Strictly speaking, the 100th would also be fine. And, as noted in another message, simply taking any positive number modulo 100 gets the same result so 103 would follow the rule for 3 and become 103rd. But if you really wanted to emulate a more strict model, a set of elif with exact integers would be reasonable using 31 conditions. if num == 1: suff = "st" elif num == 2: suff = "nd" . # 3 through 30 elif num == 31: suff = "st" That is horrible code and in the worst case does 31 comparisons. Not sure if the "in" operator is cheap enough for this version: if num in [1,21,31]: suff = "st" elif num in [2,22]: suff = "nd" elif num in [3,23]: suff = "rd" else: suff = "th" That has fewer comparisons albeit the "in" operator does additional searches. And, it does not scale up well as continuing to 100 means adding items like 32 and 33 and 41 to the lists. I already showed ways to make a dictionary version but there seem to be an indefinite number of variations on how to solve ANYTHING in python despite the founding lie that it is designed to do everything ONE way when possible. I thought to use functions in the dictionary instead, but as these are called without any known way to include arguments, I see no advantage in making 4 functions that each just return a string like "th" when I can just store and return the same string. If something long like a chapter of a book was wanted, yes, copying that umpteen times might be an annoyance. Nested dictionaries might make some sense. Again, like everything here, this is speculating on ONE way not suggesting this is great or elegant. I want to be able to break up my day number like 12 into a 1 and a 2. You can use str(day) or sprint() or many other methods and then ask for the last digit and optionally the digit before that. However you do it, you index this way using an int in my example although '1' and '2' could easily be used if altered.: >>> dictByTwo[1][2] 'th' >>> dictByTwo[2][2] 'nd' To do the above, I made a special case for the first (tens) number to be optionally a zero, the null string, or the None or Ellipsis unique objects. Here is the definition of the required dictionaries: dictNorm = { 0: "th", 1: "st", 2: "nd", 3: "rd", 4: "th", 5: "th", 6: "th", 7: "th", 8: "th", 9: "th" } dictTeens = {0: "th", 1: "th", 2: "th", 3: "th", 4: "th", 5: "th", 6: "th", 7: "th", 8: "th", 9: "th" } dictByTwo = {'': dictNorm, None: dictNorm, ...: dictNorm, 0: dictNorm, 1: dictTeens, 2: dictNorm, 3: dictNorm, 4: dictNorm, 5: dictNorm, 6: dictNorm, 7: dictNorm, 8: dictNorm, 9: dictNorm } The latter dictionary actually expands out if shown to contain copies of the other dictionaries: {'': {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, None: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, Ellipsis: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 0: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 1: {0: 'th', 1: 'th', 2: 'th', 3: 'th', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 2: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 3: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 4: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 5: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 6: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 7: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 8: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}, 9: {0: 'th', 1: 'st', 2: 'nd', 3: 'rd', 4: 'th', 5: 'th', 6: 'th', 7: 'th', 8: 'th', 9: 'th'}} But who sees that, normally? LOL! Here are some more runs: >>> dictByTwo[''][1] 'st' >>> dictByTwo[None][2] 'nd' >>> dictByTwo[...][3] 'rd' >>> dictByTwo[6][9] 'th' >>> str(69) + dictByTwo[6][9] '69th' >>> str(63) + dictByTwo[6][3] '63rd' Again, not a serious suggestion. I prefer a function method to do it all. As an enhancement, so that multiple calls are fast, you can use some method that retains memory such as a function closure or a callable class. I am thinking a bit further out of the box here for any functionality that does significant calculations but has a great chance to reuse previously computed results. So what if you are asked to process a LONG list of dates in a DataFrame object and print them out as January 1st and January 2nd and January 3rd and January 4th and so on? (I hope the flattened file showed suffixes.) The plan would be for the function or class that has access to ongoing memory to create dictionaries like the above or of any other kinds or any sub-functions or methods when used first time. When invoked again, it skips that and uses what it created. Even worse, it may keep track of what it has been called with. If I call it with say the day 5 once, it can compute "5th" and store it in a dictionary or list. I mean as the fifth item in the list or the key value pair in a dict. If called later with the same number, a constant-time lookup is done first and only if that fails do you do another calculation and store that. Note the newer generator functions can be used this way as they can accept a new value on the "yield" command. So, yet another python object that retains memory so it can use these techniques, perhaps.

- Prev by Date:
**Implement C's Switch in Python 3** - Next by Date:
**Implement C's Switch in Python 3** - Previous by thread:
**mask and proper index** - Next by thread:
**Switch function** - Index(es):