-
-
Notifications
You must be signed in to change notification settings - Fork 62
Sync say
tests
#435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Sync say
tests
#435
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
{ | ||
"authors": [ | ||
"bennn" | ||
"bennn", | ||
"BNAndras" | ||
], | ||
"contributors": [ | ||
"arguello", | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,56 @@ | ||
#lang racket/base | ||
|
||
;; say : Convert integers to English-language descriptions | ||
|
||
;; Implements the basic algorithm. | ||
;; - Does not use the OSX "say" command to speak the number | ||
;; - Does not insert "and" between chunks | ||
|
||
(require | ||
racket/contract | ||
(only-in racket/match match-define) | ||
(only-in racket/string string-trim)) | ||
|
||
(define SCALE '#(END thousand million billion trillion)) | ||
;; Supported size classifiers | ||
|
||
(define UPPER-BOUND (sub1 (expt 10 (* (vector-length SCALE) 3)))) | ||
;; The largest printable number | ||
|
||
(define (scale? v) (for/or ([s (in-vector SCALE)]) (eq? v s))) | ||
;; Contract for scales | ||
|
||
;; Use contracts to enforce all bounds | ||
(provide (contract-out | ||
[step1 (-> (integer-in 0 99) string?)] | ||
;; Convert a positive, 2-digit number to an English string | ||
|
||
[step2 (-> natural-number/c (listof (integer-in 0 999)))] | ||
;; Divide a large positive number into a list of 3-digit (or smaller) chunks | ||
|
||
[step3 (-> (integer-in (- UPPER-BOUND) UPPER-BOUND) | ||
(listof (cons/c natural-number/c scale?)))] | ||
;; Break a number into chunks and insert scales between the chunks | ||
|
||
[step4 (-> (integer-in (- UPPER-BOUND) UPPER-BOUND) | ||
string?)] | ||
;; Convert a number to an English-language string | ||
)) | ||
|
||
;; ============================================================================= | ||
|
||
(define N<20 | ||
'#("zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten" | ||
"eleven" "twelve" "thirteen" "fourteen" "fifteen" "sixteen" "seventeen" | ||
"eighteen" "nineteen")) | ||
|
||
(define TENS>10 | ||
'#("twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety")) | ||
|
||
(define (step1 n) | ||
(cond | ||
[(< n 20) | ||
(vector-ref N<20 n)] | ||
[else | ||
(define q (quotient n 10)) | ||
(define r (modulo n 10)) | ||
(define ten-str (vector-ref TENS>10 (- q 2))) | ||
(define one-str (and (not (zero? r)) (vector-ref N<20 r))) | ||
(if one-str | ||
(string-append ten-str "-" one-str) | ||
ten-str)])) | ||
|
||
(define (step2 N) | ||
(let loop ([acc '()] | ||
[n N] ;; Starts as original & we remove 3 digits each step. | ||
[i 0]) ;; Index used to pick a scale | ||
(define q (quotient n 1000)) | ||
(define r (modulo n 1000)) | ||
#lang racket | ||
|
||
(provide say) | ||
|
||
(define/contract (say number) | ||
(-> (and/c exact-nonnegative-integer? (</c 1e12)) string?) | ||
(if (zero? number) | ||
"zero" | ||
(let* ([digits (string->list (number->string number))] | ||
[chunks (map list->string (chunk-from-right digits))] | ||
[words (map to-words chunks)] | ||
[scaled (label-magnitude (reverse words))]) | ||
(string-join (reverse scaled) " ")))) | ||
|
||
(define (chunk-from-right chars) | ||
(foldr (lambda (char acc) | ||
(cond | ||
[(empty? acc) (list (list char))] | ||
[(< (length (first acc)) 3) (cons (cons char (first acc)) (rest acc))] | ||
[else (cons (list char) acc)])) | ||
'() | ||
chars)) | ||
|
||
(define (to-words chunk) | ||
(let ([number (string->number chunk)]) | ||
(cond | ||
[(= n r) | ||
;; Reached fixpoint, stop iteration | ||
(cons r acc)] | ||
[else | ||
;; Repeat using the quotient | ||
(loop (cons r acc) q (add1 i))]))) | ||
|
||
(define (step3 n) | ||
(define (add-scale n acc+i) | ||
(match-define (cons acc i) acc+i) | ||
(define s (vector-ref SCALE i)) | ||
(define n+s (cons n s)) | ||
(cons (cons n+s acc) (add1 i))) | ||
(car (foldr add-scale (cons '() 0) (step2 n)))) | ||
|
||
(define (step4 N) | ||
;; Break N into chunks, convert each chunk+scale to a string | ||
(define str* | ||
(for/list ([n+s (in-list (step3 (abs N)))]) | ||
(match-define (cons n s) n+s) | ||
(define q (quotient n 100)) | ||
(define r (modulo n 100)) | ||
(define n-str | ||
(cond | ||
[(zero? n) | ||
""] | ||
[(< n 100) | ||
(step1 r)] | ||
[else | ||
(define hd (vector-ref N<20 q)) | ||
(define tl (step1 r)) | ||
(if (equal? "zero" tl) | ||
(string-append hd " hundred") | ||
(string-append hd " hundred " tl))])) | ||
;; Don't print a scale for zeros or the last chunk | ||
(if (or (eq? s 'END) (zero? n)) | ||
n-str | ||
(string-append n-str (format " ~a " s))))) | ||
;; Use `string-trim` to remove trailing whitespace | ||
(define n-str (string-trim (apply string-append str*))) | ||
(cond ;; Check for special cases | ||
[(zero? N) | ||
"zero"] | ||
[(negative? N) | ||
(string-append "negative " n-str)] | ||
[else | ||
n-str])) | ||
|
||
[(< number 20) (list-ref first-twenty number)] | ||
[(< number 100) | ||
(let* ([tens (quotient number 10)] | ||
[tens-word (list-ref tens-words tens)] | ||
[ones (remainder number 10)] | ||
[ones-word (list-ref first-twenty ones)]) | ||
(if (zero? ones) | ||
tens-word | ||
(string-append tens-word "-" ones-word)))] | ||
[else | ||
(let* ([hundreds (quotient number 100)] | ||
[hundreds-word (list-ref first-twenty hundreds)] | ||
[rest (remainder number 100)] | ||
[rest-word (to-words (number->string rest))]) | ||
(if (string=? rest-word "") | ||
(string-append hundreds-word " hundred") | ||
(string-append hundreds-word " hundred " rest-word)))]))) | ||
|
||
(define (label-magnitude words) | ||
(for/list ([word words] | ||
[magnitude '("" " thousand" " million" " billion")] | ||
#:unless (string=? word "")) | ||
(string-append word magnitude))) | ||
|
||
(define first-twenty | ||
'("" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" "ten" | ||
"eleven" "twelve" "thirteen" "fourteen" "fifteen" "sixteen" "seventeen" "eighteen" "nineteen")) | ||
|
||
(define tens-words | ||
'("" "tens" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,6 @@ | ||
#lang racket | ||
|
||
;; Converts integers to English-language descriptions | ||
(provide say) | ||
|
||
;; --- NOTE ------------------------------------------------------------------- | ||
;; The test cases in "say-test.rkt" assume: | ||
;; - Calling a function with an out-of-range argument triggers a contract error | ||
;; - That `step3` returns a list of (number, symbol) pairs | ||
;; | ||
;; We have provided sample contracts so the tests compile, but you | ||
;; will want to edit & strengthen these. | ||
;; | ||
;; (For example, things like 0.333 and 7/8 pass the `number?` contract | ||
;; but these functions expect integers and natural numbers) | ||
;; ---------------------------------------------------------------------------- | ||
|
||
(require racket/contract) | ||
|
||
(provide (contract-out | ||
[step1 (-> number? string?)] | ||
;; Convert a positive, 2-digit number to an English string | ||
|
||
[step2 (-> number? (listof number?))] | ||
;; Divide a large positive number into a list of 3-digit (or smaller) chunks | ||
|
||
[step3 (-> number? (listof (cons/c number? symbol?)))] | ||
;; Break a number into chunks and insert scales between the chunks | ||
|
||
[step4 (-> number? string?)] | ||
;; Convert a number to an English-language string | ||
)) | ||
|
||
;; ============================================================================= | ||
|
||
(define (step1 n) | ||
(error "Please implement 'step1'")) | ||
|
||
(define (step2 N) | ||
(error "Please implement 'step2'")) | ||
|
||
(define (step3 n) | ||
(error "Please implement 'step3'")) | ||
|
||
(define (step4 N) | ||
(error "Please implement 'step4'")) | ||
(define (say number) | ||
(error "Please implement 'say'")) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do we gain by converting the number into lists of characters here and then turning them back into numbers in
to-words
? Couldn't we get the groups of three using arithmetic, e.g., withquotient/remainder
? But since this method works, there is no need to change anything.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to do things in discrete steps, but you're right. Thinking of efficiency, I think the CI should perhaps track the time taken for each example solution as an approximate estimate of how long it'd take the test runner. The example only needs to prove the test suite is solvable, but it'd be nice to know if that's within the 20 seconds available. I've had a few example solutions I wrote elsewhere that worked great on the CI but timed out when submitted. That didn't feel too great.