Skip to content

Commit c4f55b3

Browse files
authored
Sync say tests (#435)
1 parent e1a473f commit c4f55b3

File tree

5 files changed

+134
-230
lines changed

5 files changed

+134
-230
lines changed

exercises/practice/say/.meta/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"authors": [
3-
"bennn"
3+
"bennn",
4+
"BNAndras"
45
],
56
"contributors": [
67
"arguello",
Lines changed: 55 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,56 @@
1-
#lang racket/base
2-
3-
;; say : Convert integers to English-language descriptions
4-
5-
;; Implements the basic algorithm.
6-
;; - Does not use the OSX "say" command to speak the number
7-
;; - Does not insert "and" between chunks
8-
9-
(require
10-
racket/contract
11-
(only-in racket/match match-define)
12-
(only-in racket/string string-trim))
13-
14-
(define SCALE '#(END thousand million billion trillion))
15-
;; Supported size classifiers
16-
17-
(define UPPER-BOUND (sub1 (expt 10 (* (vector-length SCALE) 3))))
18-
;; The largest printable number
19-
20-
(define (scale? v) (for/or ([s (in-vector SCALE)]) (eq? v s)))
21-
;; Contract for scales
22-
23-
;; Use contracts to enforce all bounds
24-
(provide (contract-out
25-
[step1 (-> (integer-in 0 99) string?)]
26-
;; Convert a positive, 2-digit number to an English string
27-
28-
[step2 (-> natural-number/c (listof (integer-in 0 999)))]
29-
;; Divide a large positive number into a list of 3-digit (or smaller) chunks
30-
31-
[step3 (-> (integer-in (- UPPER-BOUND) UPPER-BOUND)
32-
(listof (cons/c natural-number/c scale?)))]
33-
;; Break a number into chunks and insert scales between the chunks
34-
35-
[step4 (-> (integer-in (- UPPER-BOUND) UPPER-BOUND)
36-
string?)]
37-
;; Convert a number to an English-language string
38-
))
39-
40-
;; =============================================================================
41-
42-
(define N<20
43-
'#("zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten"
44-
"eleven" "twelve" "thirteen" "fourteen" "fifteen" "sixteen" "seventeen"
45-
"eighteen" "nineteen"))
46-
47-
(define TENS>10
48-
'#("twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"))
49-
50-
(define (step1 n)
51-
(cond
52-
[(< n 20)
53-
(vector-ref N<20 n)]
54-
[else
55-
(define q (quotient n 10))
56-
(define r (modulo n 10))
57-
(define ten-str (vector-ref TENS>10 (- q 2)))
58-
(define one-str (and (not (zero? r)) (vector-ref N<20 r)))
59-
(if one-str
60-
(string-append ten-str "-" one-str)
61-
ten-str)]))
62-
63-
(define (step2 N)
64-
(let loop ([acc '()]
65-
[n N] ;; Starts as original & we remove 3 digits each step.
66-
[i 0]) ;; Index used to pick a scale
67-
(define q (quotient n 1000))
68-
(define r (modulo n 1000))
1+
#lang racket
2+
3+
(provide say)
4+
5+
(define/contract (say number)
6+
(-> (and/c exact-nonnegative-integer? (</c 1e12)) string?)
7+
(if (zero? number)
8+
"zero"
9+
(let* ([digits (string->list (number->string number))]
10+
[chunks (map list->string (chunk-from-right digits))]
11+
[words (map to-words chunks)]
12+
[scaled (label-magnitude (reverse words))])
13+
(string-join (reverse scaled) " "))))
14+
15+
(define (chunk-from-right chars)
16+
(foldr (lambda (char acc)
17+
(cond
18+
[(empty? acc) (list (list char))]
19+
[(< (length (first acc)) 3) (cons (cons char (first acc)) (rest acc))]
20+
[else (cons (list char) acc)]))
21+
'()
22+
chars))
23+
24+
(define (to-words chunk)
25+
(let ([number (string->number chunk)])
6926
(cond
70-
[(= n r)
71-
;; Reached fixpoint, stop iteration
72-
(cons r acc)]
73-
[else
74-
;; Repeat using the quotient
75-
(loop (cons r acc) q (add1 i))])))
76-
77-
(define (step3 n)
78-
(define (add-scale n acc+i)
79-
(match-define (cons acc i) acc+i)
80-
(define s (vector-ref SCALE i))
81-
(define n+s (cons n s))
82-
(cons (cons n+s acc) (add1 i)))
83-
(car (foldr add-scale (cons '() 0) (step2 n))))
84-
85-
(define (step4 N)
86-
;; Break N into chunks, convert each chunk+scale to a string
87-
(define str*
88-
(for/list ([n+s (in-list (step3 (abs N)))])
89-
(match-define (cons n s) n+s)
90-
(define q (quotient n 100))
91-
(define r (modulo n 100))
92-
(define n-str
93-
(cond
94-
[(zero? n)
95-
""]
96-
[(< n 100)
97-
(step1 r)]
98-
[else
99-
(define hd (vector-ref N<20 q))
100-
(define tl (step1 r))
101-
(if (equal? "zero" tl)
102-
(string-append hd " hundred")
103-
(string-append hd " hundred " tl))]))
104-
;; Don't print a scale for zeros or the last chunk
105-
(if (or (eq? s 'END) (zero? n))
106-
n-str
107-
(string-append n-str (format " ~a " s)))))
108-
;; Use `string-trim` to remove trailing whitespace
109-
(define n-str (string-trim (apply string-append str*)))
110-
(cond ;; Check for special cases
111-
[(zero? N)
112-
"zero"]
113-
[(negative? N)
114-
(string-append "negative " n-str)]
115-
[else
116-
n-str]))
117-
27+
[(< number 20) (list-ref first-twenty number)]
28+
[(< number 100)
29+
(let* ([tens (quotient number 10)]
30+
[tens-word (list-ref tens-words tens)]
31+
[ones (remainder number 10)]
32+
[ones-word (list-ref first-twenty ones)])
33+
(if (zero? ones)
34+
tens-word
35+
(string-append tens-word "-" ones-word)))]
36+
[else
37+
(let* ([hundreds (quotient number 100)]
38+
[hundreds-word (list-ref first-twenty hundreds)]
39+
[rest (remainder number 100)]
40+
[rest-word (to-words (number->string rest))])
41+
(if (string=? rest-word "")
42+
(string-append hundreds-word " hundred")
43+
(string-append hundreds-word " hundred " rest-word)))])))
44+
45+
(define (label-magnitude words)
46+
(for/list ([word words]
47+
[magnitude '("" " thousand" " million" " billion")]
48+
#:unless (string=? word ""))
49+
(string-append word magnitude)))
50+
51+
(define first-twenty
52+
'("" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten"
53+
"eleven" "twelve" "thirteen" "fourteen" "fifteen" "sixteen" "seventeen" "eighteen" "nineteen"))
54+
55+
(define tens-words
56+
'("" "tens" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"))

exercises/practice/say/.meta/tests.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,24 @@ description = "twenty"
2424
[d78601eb-4a84-4bfa-bf0e-665aeb8abe94]
2525
description = "twenty-two"
2626

27+
[f010d4ca-12c9-44e9-803a-27789841adb1]
28+
description = "thirty"
29+
30+
[738ce12d-ee5c-4dfb-ad26-534753a98327]
31+
description = "ninety-nine"
32+
2733
[e417d452-129e-4056-bd5b-6eb1df334dce]
2834
description = "one hundred"
2935

3036
[d6924f30-80ba-4597-acf6-ea3f16269da8]
3137
description = "one hundred twenty-three"
3238

39+
[2f061132-54bc-4fd4-b5df-0a3b778959b9]
40+
description = "two hundred"
41+
42+
[feed6627-5387-4d38-9692-87c0dbc55c33]
43+
description = "nine hundred ninety-nine"
44+
3345
[3d83da89-a372-46d3-b10d-de0c792432b3]
3446
description = "one thousand"
3547

exercises/practice/say/say-test.rkt

Lines changed: 62 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,74 +3,65 @@
33
(require "say.rkt")
44

55
(module+ test
6-
(require rackunit rackunit/text-ui)
7-
8-
(define-syntax-rule (check-equal* f [arg == val] ...)
9-
(begin (check-equal? (f arg) val) ...))
10-
11-
(define-syntax-rule (check-exn* f pat [arg] ...)
12-
(begin (check-exn exn:fail:contract? (lambda () (f arg))) ...))
13-
14-
(define step*
15-
(list
16-
(test-suite "step1"
17-
(check-equal* step1
18-
[0 == "zero"]
19-
[2 == "two"]
20-
[14 == "fourteen"]
21-
[50 == "fifty"]
22-
[98 == "ninety-eight"]
23-
[99 == "ninety-nine"])
24-
25-
(check-exn* step1
26-
[-1]
27-
[100]))
28-
29-
(test-suite "step2"
30-
(check-equal* step2
31-
[1234567890 == '(1 234 567 890)]
32-
[1000000890 == '(1 0 0 890)]
33-
[22 == '(22)]
34-
[3222 == '(3 222)]
35-
[1000231 == '(1 0 231)]
36-
[1000 == '(1 0)]))
37-
38-
(test-suite "step3"
39-
(check-equal* step3
40-
[3222 == '((3 . thousand) (222 . END))]
41-
[901003004111 == '((901 . billion) (3 . million)
42-
(4 . thousand) (111 . END))]
43-
[999 == '((999 . END))]
44-
[21 == '((21 . END))]
45-
[19 == '((19 . END))]
46-
[100 == '((100 . END))]
47-
[123 == '((123 . END))]
48-
[1234567890 == '((1 . billion) (234 . million)
49-
(567 . thousand) (890 . END))]))
50-
51-
(test-suite "step4"
52-
(check-equal* step4
53-
[10 == "ten"]
54-
[100 == "one hundred"]
55-
[10000 == "ten thousand"]
56-
[10000000 == "ten million"]
57-
[10000000000 == "ten billion"]
58-
[10000000000000 == "ten trillion"]
59-
[999000000000000 == "nine hundred ninety-nine trillion"]
60-
[0 == "zero"]
61-
[16 == "sixteen"]
62-
[300 == "three hundred"]
63-
[440 == "four hundred forty"]
64-
[999 == "nine hundred ninety-nine"]
65-
[-1 == "negative one"]
66-
[22 == "twenty-two"]
67-
[123 == "one hundred twenty-three"]
68-
[22 == "twenty-two"]
69-
[14 == "fourteen"]
70-
[50 == "fifty"]
71-
[98 == "ninety-eight"]
72-
[-432600 == "negative four hundred thirty-two thousand six hundred"]
73-
[12345 == "twelve thousand three hundred forty-five"]))))
74-
75-
(for ([suite (in-list step*)])
76-
(run-tests suite)))
6+
(require rackunit
7+
rackunit/text-ui)
8+
9+
(define suite
10+
(test-suite "say tests"
11+
12+
(test-equal? "zero" (say 0) "zero")
13+
14+
(test-equal? "one" (say 1) "one")
15+
16+
(test-equal? "two" (say 2) "two")
17+
18+
(test-equal? "fourteen" (say 14) "fourteen")
19+
20+
(test-equal? "twenty" (say 20) "twenty")
21+
22+
(test-equal? "twenty-two" (say 22) "twenty-two")
23+
24+
(test-equal? "thirty" (say 30) "thirty")
25+
26+
(test-equal? "ninety-nine" (say 99) "ninety-nine")
27+
28+
(test-equal? "one hundred" (say 100) "one hundred")
29+
30+
(test-equal? "one hundred twenty-three"
31+
(say 123)
32+
"one hundred twenty-three")
33+
34+
(test-equal? "two hundred" (say 200) "two hundred")
35+
36+
(test-equal? "nine hundred ninety-nine"
37+
(say 999)
38+
"nine hundred ninety-nine")
39+
40+
(test-equal? "one thousand" (say 1000) "one thousand")
41+
42+
(test-equal? "one thousand two hundred thirty-four"
43+
(say 1234)
44+
"one thousand two hundred thirty-four")
45+
46+
(test-equal? "one million" (say 1000000) "one million")
47+
48+
(test-equal? "one million two thousand three hundred forty-five"
49+
(say 1002345)
50+
"one million two thousand three hundred forty-five")
51+
52+
(test-equal? "one billion" (say 1000000000) "one billion")
53+
54+
(test-equal?
55+
"a big number"
56+
(say 987654321123)
57+
"nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three")
58+
59+
(test-exn "numbers below zero are out of range"
60+
exn:fail?
61+
(lambda () (say -1)))
62+
63+
(test-exn "numbers above 999,999,999,999 are out of range"
64+
exn:fail?
65+
(lambda () (say 1000000000000)))))
66+
67+
(run-tests suite))

exercises/practice/say/say.rkt

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,6 @@
11
#lang racket
22

3-
;; Converts integers to English-language descriptions
3+
(provide say)
44

5-
;; --- NOTE -------------------------------------------------------------------
6-
;; The test cases in "say-test.rkt" assume:
7-
;; - Calling a function with an out-of-range argument triggers a contract error
8-
;; - That `step3` returns a list of (number, symbol) pairs
9-
;;
10-
;; We have provided sample contracts so the tests compile, but you
11-
;; will want to edit & strengthen these.
12-
;;
13-
;; (For example, things like 0.333 and 7/8 pass the `number?` contract
14-
;; but these functions expect integers and natural numbers)
15-
;; ----------------------------------------------------------------------------
16-
17-
(require racket/contract)
18-
19-
(provide (contract-out
20-
[step1 (-> number? string?)]
21-
;; Convert a positive, 2-digit number to an English string
22-
23-
[step2 (-> number? (listof number?))]
24-
;; Divide a large positive number into a list of 3-digit (or smaller) chunks
25-
26-
[step3 (-> number? (listof (cons/c number? symbol?)))]
27-
;; Break a number into chunks and insert scales between the chunks
28-
29-
[step4 (-> number? string?)]
30-
;; Convert a number to an English-language string
31-
))
32-
33-
;; =============================================================================
34-
35-
(define (step1 n)
36-
(error "Please implement 'step1'"))
37-
38-
(define (step2 N)
39-
(error "Please implement 'step2'"))
40-
41-
(define (step3 n)
42-
(error "Please implement 'step3'"))
43-
44-
(define (step4 N)
45-
(error "Please implement 'step4'"))
5+
(define (say number)
6+
(error "Please implement 'say'"))

0 commit comments

Comments
 (0)