Created
March 5, 2021 20:39
-
-
Save mawillcockson/f3a93bd191fa3ff5c0c4edf09c9948bf to your computer and use it in GitHub Desktop.
what binary operations on positive integers result in a value that's equal to the count of characters in the names of the numbers in the calculation
This file contains hidden or 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
| # mypy: allow-any-expr | |
| """ | |
| usage: add_number_phrase [[start] stop] | |
| finds binary operations of addition, subtraction, division, or multiplication | |
| that result in a value that's equal to the number of letters of all of the | |
| names of the numbers used in the calculation | |
| start: number to start at (1 if not specified) | |
| stop: number to stop at | |
| """ | |
| import sys | |
| from fractions import Fraction | |
| from itertools import chain, combinations_with_replacement, product | |
| from operator import add, mul, sub, truediv | |
| from num2words import num2words | |
| def divides_cleanly(numerator: int, denominator: int) -> int: | |
| "if it divides cleanly, returns the result, otherwise -1" | |
| fraction = Fraction(numerator, denominator) | |
| if fraction.denominator == 1: | |
| return fraction.numerator | |
| return -1 | |
| commutative = { | |
| add: "+", | |
| sub: "-", | |
| mul: "*", | |
| } | |
| non_commutative = { | |
| # floordiv: "//", | |
| truediv: "/", | |
| # lambda left, right: round(left / right): "~/", | |
| # divides_cleanly: "/", | |
| } | |
| def find_phrases(search_range: range) -> None: | |
| """ | |
| finds binary operations of addition, subtraction, division, or | |
| multiplication that result in a value that's equal to the number of letters | |
| of all of the names of the numbers used in the calculation | |
| """ | |
| for operation, left, right in ( | |
| (operation, left, right) | |
| for (left, right) in combinations_with_replacement(search_range, 2) | |
| for operation in commutative | |
| ): | |
| result = operation(left, right) | |
| letter_count = sum(map(len, chain.from_iterable(map(num2words, [left, right])))) | |
| if result == letter_count: | |
| print(f"{left} {commutative[operation]} {right} == {letter_count}") | |
| for operation, left, right in ( | |
| (operation, left, right) | |
| for (left, right) in product(search_range, repeat=2) | |
| for operation in non_commutative | |
| ): | |
| result = operation(left, right) | |
| letter_count = sum(map(len, chain.from_iterable(map(num2words, [left, right])))) | |
| if result == letter_count: | |
| print(f"{left} {non_commutative[operation]} {right} == {letter_count}") | |
| if __name__ == "__main__": | |
| if len(sys.argv) > 3: | |
| raise NotImplementedError(__doc__) | |
| if len(sys.argv) > 1: | |
| assert all(map(str.isdecimal, sys.argv[1:])), "all arguments must be numeric" | |
| if len(sys.argv) == 3: | |
| START = int(sys.argv[1]) | |
| STOP = int(sys.argv[2]) | |
| elif len(sys.argv) == 2: | |
| START = 1 | |
| STOP = int(sys.argv[1]) | |
| else: | |
| START = 1 | |
| STOP = 100 | |
| find_phrases(range(START, STOP)) |
This file contains hidden or 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
| # mypy: allow-any-expr | |
| """ | |
| returns the number names of positive integers | |
| """ | |
| from functools import lru_cache | |
| from typing import List | |
| __all__ = ["num2words"] | |
| one_to_twenty = [ | |
| "zero", # unused; purely because python indeces start at 0 | |
| "one", | |
| "two", | |
| "three", | |
| "four", | |
| "five", | |
| "six", | |
| "seven", | |
| "eight", | |
| "nine", | |
| "ten", | |
| "eleven", | |
| "twelve", | |
| "thirteen", | |
| "fourteen", | |
| "fifteen", | |
| "sixteen", | |
| "seventeen", | |
| "eighteen", | |
| "nineteen", | |
| ] | |
| twenty_to_hundred = [ | |
| "zeroty", # unused; purely because python indeces start at 0 | |
| "tenty", # same for this | |
| "twenty", | |
| "thirty", | |
| "fourty", | |
| "fifty", | |
| "sixty", | |
| "seventy", | |
| "eighty", | |
| "ninety", | |
| ] | |
| thousands = [ | |
| "thousand", | |
| "million", | |
| "billion", | |
| ] | |
| @lru_cache(maxsize=None) | |
| def group_of_3(number: int) -> List[str]: | |
| "returns the number name of a number that's at most 3 digits" | |
| if not isinstance(number, int) or number < 1 or number > 999: | |
| raise ValueError(f"'{number}' is not a positive integer less than 999") | |
| hundreds, remainder = divmod(number, 100) | |
| words: List[str] = [] | |
| if hundreds: | |
| words.append(one_to_twenty[hundreds]) | |
| words.append("hundred") | |
| if not remainder: | |
| return words | |
| if remainder < 20: | |
| words.append(one_to_twenty[remainder]) | |
| return words | |
| tens, ones = divmod(remainder, 10) | |
| if tens: | |
| words.append(twenty_to_hundred[tens]) | |
| if ones: | |
| words.append(one_to_twenty[ones]) | |
| return words | |
| @lru_cache(maxsize=None) | |
| def num2words(number: int) -> List[str]: | |
| "returns the number name of an integer" | |
| if not isinstance(number, int) or number < 1: | |
| raise ValueError(f"'{number}' is not a positive integer") | |
| number_of_groups = (len(str(number)) - 1) // 3 | |
| if number_of_groups > len(thousands): | |
| raise NotImplementedError( | |
| f"Sorry, haven't typed in anything higher than '{thousands[-1]}'" | |
| ) | |
| words: List[str] = [] | |
| for level, level_name in reversed(list(enumerate(thousands[0:number_of_groups]))): | |
| left, right = divmod(number, 1000 ** (level + 1)) | |
| if left: | |
| words.extend(group_of_3(left)) | |
| words.append(level_name) | |
| number = right | |
| if number: | |
| words.extend(group_of_3(number)) | |
| return words |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment