Created
August 20, 2016 04:04
-
-
Save brighid/91db733e5f3a0bd5a5e77e55ee53f8f0 to your computer and use it in GitHub Desktop.
Coder calisthenics: a recursive function for naming numbers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def words_from_int(int_or_string): | |
""" | |
Given a positive integer (or a string that looks like one), returns a | |
string that represents the spoken American English (short scale) form of | |
the given number. | |
""" | |
# Numbers' names | |
ones_place = [None, "one", "two", "three", "four", | |
"five", "six", "seven", "eight", "nine"] | |
teens_place = [None, "eleven", "twelve", "thirteen", "fourteen", | |
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen"] | |
tens_place = [None, "ten", "twenty", "thirty", "forty", | |
"fifty", "sixty", "seventy", "eighty", "ninety"] | |
hundreds_marker = "hundred" | |
exponent_place = [None, "thousand", "million", "billion", "trillion", | |
"quadrillion", "quintillion", "sextillion", "septillion"] | |
# Basic "can we work with this?" checks | |
digit_string = str(int_or_string) | |
try: | |
digit_list = map(int, list(digit_string)) | |
while digit_list[0] == 0: | |
digit_list.pop(0) | |
except ValueError: | |
# Raises if we got a negative int or a float. | |
raise ValueError("{!r} isn't a positive number.".format(int_or_string)) | |
except IndexError: | |
# Raises if digit_list is empty, which happens if it was all zeros. | |
raise ValueError("Zero isn't a positive number.") | |
else: | |
if (len(digit_list) // 3) > len(exponent_place): | |
raise ValueError("Can't handle numbers bigger than {}s.".format( | |
exponent_place[-1])) | |
# Now that we've checked, let's process our digits. | |
def _word_from_triplets(digits): | |
"""Recursively turns a list of digits into a string describing them.""" | |
# Base cases: zeros, ones, tens | |
# Return early if all digits in this chunk are zero | |
if sum(digits) == 0: | |
exponent = (len(digits) // 3) - 1 | |
return ("", exponent) | |
exponent = 0 | |
# Ones | |
if len(digits) == 1: | |
return (ones_place[digits[0]], exponent) | |
# Tens | |
if len(digits) == 2: | |
tens, ones = digits | |
if tens == 0: | |
return (ones_place[ones], exponent) | |
if ones == 0: | |
return (tens_place[tens], exponent) | |
if tens == 1: | |
return (teens_place[ones], exponent) | |
return ("{}-{}".format(tens_place[tens], | |
ones_place[ones]), exponent) | |
# "Half-recursive" case: hundreds | |
if len(digits) == 3: | |
if sum(digits) == digits[0]: | |
# i.e. if the other two digits are zero | |
return ("{} {}".format(ones_place[digits[0]], | |
hundreds_marker), exponent) | |
hundreds = ones_place[digits[0]] | |
rest, _ = _word_from_triplets(digits[1:]) | |
if hundreds is None: | |
return (rest, exponent) | |
else: | |
return ( | |
"{} {} {}".format(hundreds, hundreds_marker, rest), | |
exponent) | |
# Recursive case: thousands and up | |
head, rest = digits[:3], digits[3:] | |
head_words, _ = _word_from_triplets(head) | |
rest_words, rest_exp = _word_from_triplets(rest) | |
exponent = rest_exp + 1 | |
exp_word = exponent_place[exponent] | |
all_words = "{head} {exp}{sep}{rest}".format( | |
head=head_words, | |
exp=exp_word, | |
sep=", " if rest_words != "" else "", | |
rest=rest_words) | |
return (all_words, exponent) | |
# Short numbers we process in one chunk. | |
if len(digit_list) < 4: | |
all_words, _ = _word_from_triplets(digit_list) | |
print(all_words) | |
return all_words | |
# If the length of our digit list isn't evenly divisible by three, take | |
# some numbers from the head until we have a tail that's divisible by | |
# three. Recursively process the tail, combine it with the head. | |
d = len(digit_list) % 3 | |
head, rest = digit_list[:d], digit_list[d:] | |
head_words, _ = _word_from_triplets(head) | |
rest_words, rest_exp = _word_from_triplets(rest) | |
exponent = rest_exp + 1 | |
exp_word = exponent_place[exponent] | |
if head_words == "": | |
all_words = rest_words | |
elif rest_words == "": | |
all_words = "{} {}".format(head_words, exp_word) | |
else: | |
rest_sep = " " if len(digit_list) < 5 else ", " | |
all_words = "{head} {exp}{rest_sep}{rest}".format( | |
head=head_words, | |
exp=exp_word, | |
rest_sep=rest_sep, | |
rest=rest_words) | |
print(all_words) | |
return all_words |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment