Project Euler Solution 17: Number letter counts

Project Euler Problem 16: Power digit sum is about expressing number as English words and counting their characters.

If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.

If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used?

Note: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The use of "and" when writing out numbers is in compliance with British usage.

This is a straightforward problem, one just has to deal with the irregularities of the English language. I have written a recursive function that puts numbers into words. One can nicely see the special cases up to 19, and then the irregular words for the multiples of then. After that it becomes more regular.

def number_to_word(number: int) -> str:
    special_cases = {
        1: "one",
        2: "two",
        3: "three",
        4: "four",
        5: "five",
        6: "six",
        7: "seven",
        8: "eight",
        9: "nine",
        10: "ten",
        11: "eleven",
        12: "twelve",
        13: "thirteen",
        14: "fourteen",
        15: "fifteen",
        16: "sixteen",
        17: "seventeen",
        18: "eighteen",
        19: "nineteen",
    }
    if number in special_cases:
        return special_cases[number]

    if number < 100:
        tens = {
            2: "twenty",
            3: "thirty",
            4: "forty",
            5: "fifty",
            6: "sixty",
            7: "seventy",
            8: "eighty",
            9: "ninety",
        }
        return tens[number // 10] + (
            "" if number % 10 == 0 else number_to_word(number % 10)
        )

    if number < 1000:
        return (
            number_to_word(number // 100)
            + "hundred"
            + ("" if number % 100 == 0 else "and" + number_to_word(number % 100))
        )

    if number < 10000:
        return (
            number_to_word(number // 1000)
            + "thousand"
            + ("" if number % 1000 == 0 else "and" + number_to_word(number % 1000))
        )

As we are not to count spaces, the function doesn't even generate them. having that function it is easy to just sum up all characters of all words from the numbers from 1 to 1000:

def solution() -> int:
    words = [number_to_word(number) for number in range(1, 1001)]
    return sum(map(len, words))

And that produces 21,124 in 3.6 ms because there wasn't so much to compute. The main work was just to get all the words and rules right.