Problem 17

Problem 17

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.

Fuj, mieszanie matematyki i programowania do żenującej zagadki lingwistycznej.


def to_words(num)
  a0 = %w{ one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen }
  a1 = %w{ twenty thirty forty fifty sixty seventy eighty ninety }
  case num
  when 1..19
    a0[num-1]
  when 20..99
    num % 10 != 0 ? "#{a1[(num-20)/10]}#{a0[num % 10 - 1]}" : a1[(num-20)/10]
  when 100..999
    num % 100 != 0 ? "#{to_words(num/100)}hundredand#{to_words(num % 100)}" : "#{to_words(num/100)}hundred"
  when 1000
    "onethousand"
  end
end

puts (1..1000).inject('') { |acc,n| acc << to_words(n)}.size[/code]

Dla odmiany w Pythonie:

def to_words(num):
  a0 = 'one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen'.split(' ')
  a1 = 'twenty thirty forty fifty sixty seventy eighty ninety'.split(' ')
  if 1<= num <= 19:  return a0[num-1]
  if 20 <= num <= 99: 
    return  "%s%s" % (a1[(num-20)/10], a0[num % 10 - 1]) if num % 10 != 0 else a1[(num-20)/10]
  if 100 <= num <= 999: 
    return "%shundredand%s" % (to_words(num/100), to_words(num % 100)) \
      if num % 100 != 0 else "%shundred" % to_words(num/100)
  if num == 1000: return "onethousand"

print sum([len(to_words(n)) for n in range(1,1001)])

W sumie sprawdzanie zakresów nie jest potrzebne:

def to_words(num):
  a0 = 'one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen'.split(' ')
  a1 = 'twenty thirty forty fifty sixty seventy eighty ninety'.split(' ')
  if num <= 19: return a0[num-1]
  if num <= 99: return  "%s%s" % (a1[(num-20)/10], a0[num % 10 - 1]) if num % 10 != 0 else a1[(num-20)/10]
  if num <= 999: return "%shundredand%s" % (to_words(num/100), to_words(num % 100)) if num % 100 != 0 else "%shundred" % to_words(num/100)
  if num == 1000: return "onethousand"

@jzabiello - a kiedy w clojure :>?

@teamon oraz w scali, erlangu i haskellu.

Ten sam algorytm napisany w Scali:

def to_words(num: Int): String = {
    val a0 = ("one two three four five six seven eight nine ten" + 
             "eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen")
             .split(' ')
    val a1 = "twenty thirty forty fifty sixty seventy eighty ninety".split(' ')
    if (1 to 19 contains num) return a0(num-1)
    if (20 to 99 contains num) return if (num % 10 != 0)
                                        "%s%s".format(a1((num-20)/10), a0(num % 10 - 1))
                                      else a1((num-20)/10)
    if (100 to 999 contains num) return if (num % 100 != 0)
                                          "%shundredand%s".format(to_words(num/100), to_words(num % 100))
                                        else "%shundred".format(to_words(num/100))
    if (num == 1000) "onethousand" else ""
 }

println((for(n <- 1 to 1000) yield to_words(n).length).foldLeft(0)(_ + _))

Algorytm można trochę skrócić, bo zakresy nie są potrzebne. Np. można tą funkcję zapisać tak:

def to_words(num: Int): String = {
  val a0 = "one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen".split(' ')
  val a1 = "twenty thirty forty fifty sixty seventy eighty ninety".split(' ')
  if (num <= 19) return a0(num-1)
  if (num <= 99) return if (num % 10 != 0) "%s%s".format(a1((num-20)/10), a0(num % 10 - 1)) else a1((num-20)/10)
  if (num <= 999) return if (num % 100 != 0) "%shundredand%s".format(to_words(num/100), to_words(num % 100))  else "%shundred".format(to_words(num/100))
  if (num == 1000) "onethousand" else ""
}

A tu wersja używająca pattern matching:

val to_words: Int => String = {
  val a0 = ("one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen").split(' ')
  val a1 = "twenty thirty forty fifty sixty seventy eighty ninety".split(' ') 
  
  (num: Int) => num match {
    case n if n <= 19 => a0(n-1)
    case n if n <= 99 => if (n % 10 != 0) "%s%s".format(a1((n-20)/10), a0(n % 10 - 1)) else a1((n-20)/10)
    case n if n <= 999 => if (n % 100 != 0) "%shundredand%s".format(to_words(n/100), to_words(n % 100)) else "%shundred".format(to_words(n/100))
    case n if n == 1000 => "onethousand"
    case _ => "out of 1..1000"
  }
}

Definicja zestawu słów powinna być poza funkcją (niezależnie od języka) - będzie szybsza. W Clojure np:

(def a0 '("one" "two" "three" "four" "five" "six" "seven" "eight"
             "nine" "ten" "eleven" "twelve" "thirteen" "fourteen" "fifteen"
             "sixteen" "seventeen" "eighteen" "nineteen"))
(def a1 '("twenty" "thirty" "forty" "fifty" "sixty" "seventy"
             "eighty" "ninety"))

(defn to_words [n]
  (cond
   (< n 20) (nth a0 (dec n))
   (< n 100) (if (zero? (rem n 10))
               (nth a1 (/ (- n 20) 10))
               (str (nth a1 (/ (- n 20) 10)) (nth a0 (dec (rem n 10)))))
   (< n 1000) (if (zero? (rem n 100))
                (str (to_words (/ n 100)) "hundred")
                (str (to_words (/ n 100)) "hundredand" (to_words (rem n 100))))
   (= n 1000) "onethousand"))

(.length (reduce str (map to_words (range 1 1001))))