# paulfioravanti's solution

## to Say in the Elixir Track

Published at Aug 04 2019 · 0 comments
Instructions
Test suite
Solution

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

## Step 1

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

### Extension

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`.

## Step 2

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.

## Step 3

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".

## Step 4

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.

### Extensions

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".

## Running tests

Execute the tests with:

``````\$ mix test
``````

### Pending tests

In the test suites, all but the first test have been skipped.

Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.

For example:

``````# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
``````

Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the test suite.

``````# ExUnit.configure exclude: :pending, trace: true
``````

If you're stuck on something, it may help to look at some of the available resources out there where answers might be found.

## Source

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

## Submitting Incomplete Solutions

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

### say_test.exs

``````defmodule SayTest do
use ExUnit.Case

# @tag :pending
test "zero" do
assert Say.in_english(0) == {:ok, "zero"}
end

@tag :pending
test "one" do
assert Say.in_english(1) == {:ok, "one"}
end

@tag :pending
test "fourteen" do
assert Say.in_english(14) == {:ok, "fourteen"}
end

@tag :pending
test "twenty" do
assert Say.in_english(20) == {:ok, "twenty"}
end

@tag :pending
test "twenty-two" do
assert Say.in_english(22) == {:ok, "twenty-two"}
end

@tag :pending
test "one hundred" do
assert Say.in_english(100) == {:ok, "one hundred"}
end

@tag :pending
test "one hundred twenty-three" do
assert Say.in_english(123) == {:ok, "one hundred twenty-three"}
end

@tag :pending
test "one thousand" do
assert Say.in_english(1_000) == {:ok, "one thousand"}
end

@tag :pending
test "one thousand two hundred thirty-four" do
assert Say.in_english(1_234) == {:ok, "one thousand two hundred thirty-four"}
end

@tag :pending
test "one million" do
assert Say.in_english(1_000_000) == {:ok, "one million"}
end

@tag :pending
test "one million two thousand three hundred forty-five" do
assert Say.in_english(1_002_345) == {:ok, "one million two thousand three hundred forty-five"}
end

@tag :pending
test "one billion" do
assert Say.in_english(1_000_000_000) == {:ok, "one billion"}
end

@tag :pending
test "a big number" do
assert Say.in_english(987_654_321_123) ==
{:ok,
"nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three"}
end

@tag :pending
test "numbers below zero are out of range" do
assert Say.in_english(-1) == {:error, "number is out of range"}
end

@tag :pending
test "numbers above 999,999,999,999 are out of range" do
assert Say.in_english(1_000_000_000_000) == {:error, "number is out of range"}
end
end``````

### test_helper.exs

``````ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)``````
``````defmodule Say do
@number_words %{
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"
}

@thousand 3
@million 6
@billion 9

@scales %{
2 => "hundred",
@thousand => "thousand",
@million => "million",
@billion => "billion"
}

defguardp out_of_range?(number) when number < 0 or number > 999_999_999_999
defguardp up_to_twenty?(number) when number < 21
defguardp up_to_ninety_nine?(number) when number < 100

defguardp ten_thousand_up_to_one_million?(number)
when number > 9_999 and number < 1_000_000

defguardp ten_million_up_to_one_billion?(number)
when number > 9_999_999 and number < 1_000_000_000

defguardp ten_billion_up_to_one_trillion?(number)
when number > 9_999_999_999 and number < 1_000_000_000_000

@doc """
Translate a positive integer into English.
"""
@spec in_english(integer) :: {atom, String.t()}
def in_english(number) when out_of_range?(number) do
{:error, "number is out of range"}
end

def in_english(0), do: {:ok, "zero"}

def in_english(number) when up_to_twenty?(number) do
{:ok, @number_words[number]}
end

def in_english(number) when up_to_ninety_nine?(number) do
{:ok, hypenated_word(number)}
end

def in_english(number), do: {:ok, full_word(number)}

defp hypenated_word(number) do
[_tens, ones] = Integer.digits(number)
@number_words[number - ones] <> "-" <> @number_words[ones]
end

defp full_word(0), do: ""

defp full_word(number) when up_to_twenty?(number), do: @number_words[number]

defp full_word(number) when up_to_ninety_nine?(number) do
hypenated_word(number)
end

defp full_word(number) when ten_thousand_up_to_one_million?(number) do
split_list_by_scale(number, @thousand)
end

defp full_word(number) when ten_million_up_to_one_billion?(number) do
split_list_by_scale(number, @million)
end

defp full_word(number) when ten_billion_up_to_one_trillion?(number) do
split_list_by_scale(number, @billion)
end

defp full_word(number) do

scale =
tail
|> length()
|> (fn length -> @scales[length] end).()

tail_words =
tail
|> Integer.undigits()
|> full_word()
|> format_tail_word()

head_word <> " " <> scale <> tail_words
end

defp format_tail_word(""), do: ""
defp format_tail_word(word), do: " " <> word

defp split_list_by_scale(number, scale) do
number
|> Integer.digits()
|> Enum.split(-scale)

|> Integer.undigits()
|> full_word()

tail =
tail_list
|> Integer.undigits()
|> full_word()

head <> " " <> @scales[scale] <> " " <> tail
end
end``````