Skip to content

Instantly share code, notes, and snippets.

@mawillcockson
Created March 5, 2021 20:39
Show Gist options
  • Select an option

  • Save mawillcockson/f3a93bd191fa3ff5c0c4edf09c9948bf to your computer and use it in GitHub Desktop.

Select an option

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
# 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))
# 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