Project Euler: 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.

http://projecteuler.net/index.php?section=problems&id=17

1 から 1000 までの自然数列をその英単語で置き換えたうえ "two"、みたいな。">*1、その文字列の合計長を求めよ (※ただしアルファベットに限る) とのこと。

Gauche で書いてみた。

#!/usr/bin/env gosh

(use srfi-1)

(define *number-table*
  (let [(tab (make-hash-table))]
    (let loop [(lis '((0  . "zero")
                      (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")
                      (20 . "twenty")
                      (30 . "thirty")
                      (40 . "forty")
                      (50 . "fifty")
                      (60 . "sixty")
                      (70 . "seventy")
                      (80 . "eighty")
                      (90 . "ninety")))]
      (if (pair? lis)
        [begin
          (hash-table-put! tab (car (car lis)) (cdr (car lis)))
          (loop (cdr lis))]
        tab))))

(define (number->english n)
  (if (hash-table-exists? *number-table* n)
    (hash-table-get *number-table* n)
    (cond [(< n 100)
           (string-append (number->english (* (quotient n 10) 10))
                          (if (zero? (remainder n 10))
                            ""
                            (string-append "-"
                                           (number->english (remainder n 10)))))]
           [(< n 1000)
            (string-append (number->english (quotient n 100))
                           (if (zero? (remainder n 100))
                             " hundred"
                             (string-append " hundred and "
                                            (number->english (remainder n 100)))))]
            [(< n 10000)
             (cond [(<= 11 (quotient n 100) 12) ; ==> 1192 as "eleven hundred and ninety-two"
                    (string-append (number->english (quotient n 100))
                                   (if (zero? (remainder n 100))
                                     " hundred"
                                     (string-append " hundred and "
                                                    (number->english (remainder n 100)))))]
                   [(<= (remainder n 100) 12) ; ==> 1001 as "one thousand and one"
                    (string-append (number->english (quotient n 1000))
                                   (if (zero? (remainder n 1000))
                                     " thousand"
                                     (string-append " thousand and "
                                                    (number->english (remainder n 1000)))))]
                   [else ; ==> 1969 as "nineteen sixty-nine"
                     (let* [(d (remainder n 100))
                            (a (quotient n 100))]
                       (string-append (number->english a)
                                      " "
                                      (number->english d)))])]
            [else (error "number too big")])))

(define (main args)
  (let loop [(num 1)
             (lis '())]
    (if (<= num 1000)
      (let [(eng (number->english num))]
;       (format #t "~4@a:~a\n" num eng)
        (loop (+ num 1)
              (cons eng lis)))
      (print (fold +
                   0
                   (map (lambda (s)
                          (length (filter char-alphabetic?
                                          (string->list s))))
                        (reverse lis))))))
  0)

自然数を受け取って英語の文字列表現を返す number->english を定義して、あとはそのまんま長さを求めている。

ちょっとだけ気を利かせて 1000 < n < 10000 の範囲でも英語っぽく返せるように number->english を作ってみたけど、よく考えたら英語にあんまり強くないので正しい表現かどうかは分かりません。"twenty" みたいな単語は普通にハッシュテーブルな定数にぶち込んでいるので問題ないんだけど、"hundred" や "thousand" を number->english の中に埋め込んでしまったのが気持ち悪い。

*1:e.g. 1 -> "one", 2 -> "two"、みたいな。