123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- ;;; Number letter counts
- ;;; 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.
- (import
- (except (rnrs base) let-values map)
- (only (guile)
- lambda* λ
- ;; printing
- display
- simple-format
- command-line)
- (srfi srfi-69) ; hash tables
- (srfi srfi-1) ; drop
- (ice-9 textual-ports))
- (define letters
- (string->list "abcdefghijklmnopqrstuvwxyz"))
- (define special-number-names
- (alist->hash-table '(#;(0 . "null")
- (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"))
- =))
- (define multiples-of-ten-number-names
- (alist->hash-table '((1 . "ten")
- (2 . "twenty")
- (3 . "thirty")
- (4 . "forty")
- (5 . "fifty")
- (6 . "sixty")
- (7 . "seventy")
- (8 . "eighty")
- (9 . "ninety"))
- =))
- (define count-letters
- (λ (words)
- (string-length
- (string-filter (λ (c) (member c letters))
- words))))
- (define one-rule
- (λ (digit-char)
- (hash-table-ref special-number-names
- (string->number (list->string (list digit-char))))))
- (define ten-rule
- (λ (digit-chars)
- "There are special rules for naming multiples of ten. Do
- not use this function for special numbers (1 to 13)."
- (display (simple-format #f "applying ten rule for digits: ~s\n" digit-chars))
- (let* ([leading-digit-char (car digit-chars)]
- [leading-digit-word (one-rule leading-digit-char)]
- [trailing-digits (drop digit-chars 1)])
- (if (all-zero-digits trailing-digits)
- (hash-table-ref multiples-of-ten-number-names
- (string->number
- (list->string
- (list (car digit-chars)))))
- (string-append
- (hash-table-ref multiples-of-ten-number-names
- (string->number
- (list->string
- (list (car digit-chars)))))
- "-")))))
- (define hundred-rule
- (λ (digit-chars)
- (display (simple-format #f "applying hundred rule for digits: ~s\n" digit-chars))
- (let* ([leading-digit-char (car digit-chars)]
- [leading-digit-word (one-rule leading-digit-char)]
- [trailing-digits (drop digit-chars 1)])
- (string-join
- (if (all-zero-digits trailing-digits)
- (list (one-rule leading-digit-char)
- "hundred")
- (list (one-rule leading-digit-char)
- "hundred"
- "and "))
- " "))))
- (define thousand-rule
- (λ (digit-chars)
- (display (simple-format #f "applying thousand rule for digits: ~s\n" digit-chars))
- (let* ([leading-digit-char (car digit-chars)]
- [leading-digit-word (one-rule leading-digit-char)]
- [trailing-digits (drop digit-chars 1)])
- (string-join
- (if (all-zero-digits trailing-digits)
- (list (one-rule leading-digit-char)
- "thousand")
- (list (one-rule leading-digit-char)
- "thousand "))
- " "))))
- (define digit-list->number
- (λ (digits)
- (string->number (list->string digits))))
- (define all-zero-digits
- (λ (digit-chars)
- (cond
- [(null? digit-chars) #t]
- [(char=? (car digit-chars) #\0)
- (all-zero-digits (cdr digit-chars))]
- [else #f])))
- (define number->words
- (λ (num)
- (call-with-output-string
- (λ (out-port)
- (let ([digits (string->list (number->string num))]
- [rules (list thousand-rule
- hundred-rule
- ten-rule
- one-rule)])
- (let loop ([digits digits]
- [rules
- ;; Drop rules, which are not required
- ;; for translating the number to
- ;; words. For example the number 14
- ;; does not need rules for thousands
- ;; or hundreds.
- (drop rules
- (- (length rules) (length digits)))])
- ;; (display (simple-format #f "digits: ~s\n" digits))
- ;; (display (simple-format #f "rules: ~s\n" rules))
- (cond
- [(null? digits)
- (display (simple-format #f "~a\n" "no more digits"))]
- [(null? rules)
- (display (simple-format #f "~a\n" "no more rules"))]
- ;; Check, if all remaining digits are zeros. If
- ;; so do not recur.
- [(char=? #\0 (car digits))
- (loop (cdr digits) (cdr rules))]
- ;; If there are still at least 2 digits left,
- ;; it could be a special name number, so look
- ;; that up.
- [(hash-table-ref special-number-names
- (digit-list->number digits)
- (λ () #f))
- ;; Output the special name number and do not
- ;; continue to iterate.
- (put-string out-port
- (hash-table-ref special-number-names
- (digit-list->number digits)))]
- ;; Otherwise apply regular rules.
- [else
- (display (simple-format #f "applying regular rules to digit: ~s\n" (car digits)))
- (put-string out-port ((car rules) digits))
- (loop (cdr digits) (cdr rules))])))))))
- (define calculate-letter-count
- (λ (start maximum)
- (let iter ([num start]
- [num-letters 0])
- (cond
- [(<= num maximum)
- (let ([words (number->words num)])
- (display (simple-format #f "number in words: ~a\n" words))
- (iter (+ num 1)
- (+ num-letters
- (count-letters words))))]
- [else num-letters]))))
- (let* ([args (command-line)]
- [start (string->number (cadr args))]
- [maximum (string->number (caddr args))])
- (display
- (simple-format
- #f "~a\n"
- (calculate-letter-count start maximum))))
|