solution.scm 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. ;;; Number letter counts
  2. ;;; Problem 17
  3. ;;; If the numbers 1 to 5 are written out in words: one, two,
  4. ;;; three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19
  5. ;;; letters used in total.
  6. ;;; If all the numbers from 1 to 1000 (one thousand)
  7. ;;; inclusive were written out in words, how many letters
  8. ;;; would be used?
  9. ;;; NOTE: Do not count spaces or hyphens. For example, 342
  10. ;;; (three hundred and forty-two) contains 23 letters and 115
  11. ;;; (one hundred and fifteen) contains 20 letters. The use of
  12. ;;; "and" when writing out numbers is in compliance with
  13. ;;; British usage.
  14. (import
  15. (except (rnrs base) let-values map)
  16. (only (guile)
  17. lambda* λ
  18. ;; printing
  19. display
  20. simple-format
  21. command-line)
  22. (srfi srfi-69) ; hash tables
  23. (srfi srfi-1) ; drop
  24. (ice-9 textual-ports))
  25. (define letters
  26. (string->list "abcdefghijklmnopqrstuvwxyz"))
  27. (define special-number-names
  28. (alist->hash-table '(#;(0 . "null")
  29. (1 . "one")
  30. (2 . "two")
  31. (3 . "three")
  32. (4 . "four")
  33. (5 . "five")
  34. (6 . "six")
  35. (7 . "seven")
  36. (8 . "eight")
  37. (9 . "nine")
  38. (10 . "ten")
  39. (11 . "eleven")
  40. (12 . "twelve")
  41. (13 . "thirteen")
  42. (14 . "fourteen")
  43. (15 . "fifteen")
  44. (16 . "sixteen")
  45. (17 . "seventeen")
  46. (18 . "eighteen")
  47. (19 . "nineteen"))
  48. =))
  49. (define multiples-of-ten-number-names
  50. (alist->hash-table '((1 . "ten")
  51. (2 . "twenty")
  52. (3 . "thirty")
  53. (4 . "forty")
  54. (5 . "fifty")
  55. (6 . "sixty")
  56. (7 . "seventy")
  57. (8 . "eighty")
  58. (9 . "ninety"))
  59. =))
  60. (define count-letters
  61. (λ (words)
  62. (string-length
  63. (string-filter (λ (c) (member c letters))
  64. words))))
  65. (define one-rule
  66. (λ (digit-char)
  67. (hash-table-ref special-number-names
  68. (string->number (list->string (list digit-char))))))
  69. (define ten-rule
  70. (λ (digit-chars)
  71. "There are special rules for naming multiples of ten. Do
  72. not use this function for special numbers (1 to 13)."
  73. (display (simple-format #f "applying ten rule for digits: ~s\n" digit-chars))
  74. (let* ([leading-digit-char (car digit-chars)]
  75. [leading-digit-word (one-rule leading-digit-char)]
  76. [trailing-digits (drop digit-chars 1)])
  77. (if (all-zero-digits trailing-digits)
  78. (hash-table-ref multiples-of-ten-number-names
  79. (string->number
  80. (list->string
  81. (list (car digit-chars)))))
  82. (string-append
  83. (hash-table-ref multiples-of-ten-number-names
  84. (string->number
  85. (list->string
  86. (list (car digit-chars)))))
  87. "-")))))
  88. (define hundred-rule
  89. (λ (digit-chars)
  90. (display (simple-format #f "applying hundred rule for digits: ~s\n" digit-chars))
  91. (let* ([leading-digit-char (car digit-chars)]
  92. [leading-digit-word (one-rule leading-digit-char)]
  93. [trailing-digits (drop digit-chars 1)])
  94. (string-join
  95. (if (all-zero-digits trailing-digits)
  96. (list (one-rule leading-digit-char)
  97. "hundred")
  98. (list (one-rule leading-digit-char)
  99. "hundred"
  100. "and "))
  101. " "))))
  102. (define thousand-rule
  103. (λ (digit-chars)
  104. (display (simple-format #f "applying thousand rule for digits: ~s\n" digit-chars))
  105. (let* ([leading-digit-char (car digit-chars)]
  106. [leading-digit-word (one-rule leading-digit-char)]
  107. [trailing-digits (drop digit-chars 1)])
  108. (string-join
  109. (if (all-zero-digits trailing-digits)
  110. (list (one-rule leading-digit-char)
  111. "thousand")
  112. (list (one-rule leading-digit-char)
  113. "thousand "))
  114. " "))))
  115. (define digit-list->number
  116. (λ (digits)
  117. (string->number (list->string digits))))
  118. (define all-zero-digits
  119. (λ (digit-chars)
  120. (cond
  121. [(null? digit-chars) #t]
  122. [(char=? (car digit-chars) #\0)
  123. (all-zero-digits (cdr digit-chars))]
  124. [else #f])))
  125. (define number->words
  126. (λ (num)
  127. (call-with-output-string
  128. (λ (out-port)
  129. (let ([digits (string->list (number->string num))]
  130. [rules (list thousand-rule
  131. hundred-rule
  132. ten-rule
  133. one-rule)])
  134. (let loop ([digits digits]
  135. [rules
  136. ;; Drop rules, which are not required
  137. ;; for translating the number to
  138. ;; words. For example the number 14
  139. ;; does not need rules for thousands
  140. ;; or hundreds.
  141. (drop rules
  142. (- (length rules) (length digits)))])
  143. ;; (display (simple-format #f "digits: ~s\n" digits))
  144. ;; (display (simple-format #f "rules: ~s\n" rules))
  145. (cond
  146. [(null? digits)
  147. (display (simple-format #f "~a\n" "no more digits"))]
  148. [(null? rules)
  149. (display (simple-format #f "~a\n" "no more rules"))]
  150. ;; Check, if all remaining digits are zeros. If
  151. ;; so do not recur.
  152. [(char=? #\0 (car digits))
  153. (loop (cdr digits) (cdr rules))]
  154. ;; If there are still at least 2 digits left,
  155. ;; it could be a special name number, so look
  156. ;; that up.
  157. [(hash-table-ref special-number-names
  158. (digit-list->number digits)
  159. (λ () #f))
  160. ;; Output the special name number and do not
  161. ;; continue to iterate.
  162. (put-string out-port
  163. (hash-table-ref special-number-names
  164. (digit-list->number digits)))]
  165. ;; Otherwise apply regular rules.
  166. [else
  167. (display (simple-format #f "applying regular rules to digit: ~s\n" (car digits)))
  168. (put-string out-port ((car rules) digits))
  169. (loop (cdr digits) (cdr rules))])))))))
  170. (define calculate-letter-count
  171. (λ (start maximum)
  172. (let iter ([num start]
  173. [num-letters 0])
  174. (cond
  175. [(<= num maximum)
  176. (let ([words (number->words num)])
  177. (display (simple-format #f "number in words: ~a\n" words))
  178. (iter (+ num 1)
  179. (+ num-letters
  180. (count-letters words))))]
  181. [else num-letters]))))
  182. (let* ([args (command-line)]
  183. [start (string->number (cadr args))]
  184. [maximum (string->number (caddr args))])
  185. (display
  186. (simple-format
  187. #f "~a\n"
  188. (calculate-letter-count start maximum))))