Instructions

Test suite

Solution

Given a number from 0 to 999,999,999,999, spell out that number in English.

Handle the basic case of 0 through 99.

If the input to the program is `22`

, then the output should be
`'twenty-two'`

.

Your program should complain loudly if given a number outside the blessed range.

Some good test cases for this program are:

- 0
- 14
- 50
- 98
- -1
- 100

If you're on a Mac, shell out to Mac OS X's `say`

program to talk out
loud. If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`

.

Implement breaking a number up into chunks of thousands.

So `1234567890`

should yield a list like 1, 234, 567, and 890, while the
far simpler `1000`

should yield just 1 and 0.

The program must also report any values that are out of range.

Now handle inserting the appropriate scale word between those chunks.

So `1234567890`

should yield `'1 billion 234 million 567 thousand 890'`

The program must also report any values that are out of range. It's fine to stop at "trillion".

Put it all together to get nothing but plain English.

`12345`

should give `twelve thousand three hundred forty-five`

.

The program must also report any values that are out of range.

Use *and* (correctly) when spelling out the number in English:

- 14 becomes "fourteen".
- 100 becomes "one hundred".
- 120 becomes "one hundred and twenty".
- 1002 becomes "one thousand and two".
- 1323 becomes "one thousand three hundred and twenty-three".

Refer to the Installing Elm page for information about installing elm.

The first time you start an exercise, you'll need to ensure you have the appropriate dependencies installed. Thankfully, Elm makes that easy for you and will install dependencies when you try to run tests or build the code.

Execute the tests with:

```
$ elm-test
```

Automatically run tests again when you save changes:

```
$ elm-test --watch
```

As you work your way through the test suite, be sure to remove the `skip <|`

calls from each test until you get them all passing!

A variation on JavaRanch CattleDrive, exercise 4a http://www.javaranch.com/say.jsp

It is possible to submit an incomplete solution so you can see how others have completed the exercise.

```
module Tests exposing (tests)
import Expect
import Say exposing (SayError(..), say)
import Test exposing (..)
tests : Test
tests =
describe "Series"
[ test "one" <|
\() ->
Expect.equal (Ok "one")
(say 1)
, skip <|
test "fourteen" <|
\() ->
Expect.equal (Ok "fourteen")
(say 14)
, skip <|
test "twenty" <|
\() ->
Expect.equal (Ok "twenty")
(say 20)
, skip <|
test "twenty-two" <|
\() ->
Expect.equal (Ok "twenty-two")
(say 22)
, skip <|
test "one hundred" <|
\() ->
Expect.equal (Ok "one hundred")
(say 100)
, skip <|
test "one hundred twenty" <|
\() ->
Expect.equal (Ok "one hundred and twenty")
(say 120)
, skip <|
test "one hundred twenty-three" <|
\() ->
Expect.equal (Ok "one hundred and twenty-three")
(say 123)
, skip <|
test "one thousand" <|
\() ->
Expect.equal (Ok "one thousand")
(say 1000)
, skip <|
test "one thousand two hundred thirty-four" <|
\() ->
Expect.equal (Ok "one thousand two hundred and thirty-four")
(say 1234)
, skip <|
test "one million" <|
\() ->
Expect.equal (Ok "one million")
(say 1000000)
, skip <|
test "one million two" <|
\() ->
Expect.equal (Ok "one million and two")
(say 1000002)
, skip <|
test "1002345" <|
\() ->
Expect.equal (Ok "one million two thousand three hundred and forty-five")
(say 1002345)
, skip <|
test "one billion" <|
\() ->
Expect.equal (Ok "one billion")
(say 1000000000)
, skip <|
test "number too large" <|
\() ->
Expect.equal (Err TooLarge)
(say 10000000000000000)
, skip <|
test "negative number" <|
\() ->
Expect.equal (Err Negative)
(say -42)
, skip <|
test "zero" <|
\() ->
Expect.equal (Ok "zero")
(say 0)
, skip <|
test "987654321123" <|
\() ->
Expect.equal
(Ok
("nine hundred and eighty-seven billion "
++ "six hundred and fifty-four million "
++ "three hundred and twenty-one thousand "
++ "one hundred and twenty-three"
)
)
(say 987654321123)
]
```

```
module Say exposing (SayError(..), say)
import Dict exposing (Dict)
type SayError
= Negative
| TooLarge
say : Int -> Result SayError String
say number =
if isTooSmall number then
Err Negative
else if isTooLarge number then
Err TooLarge
else if isZero number then
Ok "zero"
else if isUpToTwenty number then
numberWords
|> Dict.get number
|> Result.fromMaybe Negative
else if isUpToNinetyNine number then
Ok (hypenatedWord number)
else
Ok (fullWord number)
-- PRIVATE
hypenatedWord : Int -> String
hypenatedWord number =
let
onesValue =
case digits [] number of
_ :: ones :: [] ->
ones
_ ->
0
tensWord =
numberWords
|> Dict.get (number - onesValue)
|> Maybe.withDefault ""
onesWord =
numberWords
|> Dict.get onesValue
|> Maybe.withDefault ""
in
tensWord ++ "-" ++ onesWord
fullWord : Int -> String
fullWord number =
if isZero number then
""
else if isUpToTwenty number then
numberWords
|> Dict.get number
|> Maybe.withDefault ""
else if isUpToNinetyNine number then
hypenatedWord number
else if isTenThousandUpToOneMillion number then
splitListByScale 3 number
else if isTenMillionUpToOneBillion number then
splitListByScale 6 number
else if isTenBillionUpToOneTrillion number then
splitListByScale 9 number
else
constructFullWord number
constructFullWord : Int -> String
constructFullWord number =
let
( headDigit, tailDigits ) =
case digits [] number of
head :: tail ->
( head, tail )
_ ->
( 0, [] )
headWord =
numberWords
|> Dict.get headDigit
|> Maybe.withDefault ""
scale =
scales
|> Dict.get (List.length tailDigits)
|> Maybe.withDefault ""
tailWords =
tailDigits
|> undigits 0
|> fullWord
|> formatTailWord tailDigits
in
headWord ++ " " ++ scale ++ tailWords
formatTailWord : List Int -> String -> String
formatTailWord tail word =
let
numRemainingNonZeroDigits =
tail
|> dropWhile (\int -> int == 0)
|> List.length
in
case numRemainingNonZeroDigits of
0 ->
""
1 ->
" and " ++ word
2 ->
" and " ++ word
_ ->
" " ++ word
splitListByScale : Int -> Int -> String
splitListByScale scale number =
let
digitList =
digits [] number
scaleChunk =
List.length digitList - scale
head =
digitList
|> List.take scaleChunk
|> undigits 0
|> fullWord
tail =
digitList
|> List.drop scaleChunk
|> undigits 0
|> fullWord
scaleWord =
scales
|> Dict.get scale
|> Maybe.withDefault ""
in
head ++ " " ++ scaleWord ++ " " ++ tail
digits : List Int -> Int -> List Int
digits acc int =
let
base =
10
in
if abs int < base then
int :: acc
else
let
{- Integer division won't work because of this weirdness:
> 987654321123 // 10
-18815696
-}
flooredInt =
floor (toFloat int / base)
in
digits (remainderBy base int :: acc) flooredInt
undigits : Int -> List Int -> Int
undigits acc digitList =
let
base =
10
in
case digitList of
[] ->
acc
head :: tail ->
undigits (acc * base + head) tail
dropWhile : (a -> Bool) -> List a -> List a
dropWhile predicate list =
case list of
[] ->
[]
head :: tail ->
if predicate head then
dropWhile predicate tail
else
list
isTooSmall : Int -> Bool
isTooSmall number =
number < 0
isTooLarge : Int -> Bool
isTooLarge number =
number > 999999999999
isZero : Int -> Bool
isZero number =
number == 0
isUpToTwenty : Int -> Bool
isUpToTwenty number =
number < 21
isUpToNinetyNine : Int -> Bool
isUpToNinetyNine number =
number < 100
isTenThousandUpToOneMillion : Int -> Bool
isTenThousandUpToOneMillion number =
number > 9999 && number < 1000000
isTenMillionUpToOneBillion : Int -> Bool
isTenMillionUpToOneBillion number =
number > 9999999 && number < 1000000000
isTenBillionUpToOneTrillion : Int -> Bool
isTenBillionUpToOneTrillion number =
number > 9999999999 && number < 1000000000000
scales : Dict Int String
scales =
Dict.fromList
[ ( 2, "hundred" )
, ( 3, "thousand" )
, ( 6, "million" )
, ( 9, "billion" )
]
numberWords : Dict Int String
numberWords =
Dict.fromList
[ ( 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" )
]
```

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

- What compromises have been made?
- Are there new concepts here that you could read more about to improve your understanding?

Level up your programming skills with 3,373 exercises across 50 languages, and insightful discussion with our volunteer team of welcoming mentors.
Exercism is
**100% free forever**.

## Community comments