 # blasphemetheus's solution

## to Say in the Elixir Track

Published at Jun 19 2020 · 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
defguard is_billion(val) when val >= 1_000_000_000
defguard is_million(val) when val >= 1_000_000
defguard is_thousand(val) when val >= 1_000 and val < 1_000_000
defguard is_hundred(val) when val >= 100 and val < 1_000
defguard is_ty(val) when val >= 20 and val < 100
defguard is_teen(val) when val >= 10 and val < 20
defguard is_literal(val) when val >= 1 and val < 10
defguard is_negative(val) when val < 0
defguard is_too_big(val) when val > 999_999_999_999

defguard is_even_hundred(num) when rem(num, 100) == 0
defguard is_even_ten(num) when rem(num, 10) == 0

@doc """
For enunciate to work, the System outside of elixir will need to have
espeak installed (on some linux systems, running `sudo apt-get espeak`
should do the trick)

Enunciate takes a range or integer and tells the outside operating system
to say that integer or that range.
"""
def enunciate(first .. last) do
for i <- first .. last, do: enunciate(i)
end

def enunciate(number) do
case in_english(number) do
{:ok, text} -> System.cmd("espeak", [text])
{:error, error_text} -> System.cmd("espeak", ["error! " <> error_text])
end
end

@doc """
Translate a positive integer into English.
"""
@spec in_english(integer) :: {atom, String.t()}
def in_english(num) when not is_integer(num), do: {:error, "not an integer"}
def in_english(num) when is_negative(num) or is_too_big(num), do: {:error, "number is out of range"}
def in_english(0), do: {:ok, "zero"}
def in_english(num) when is_integer(num) do
result = num |> chunkify() |> scale_words()

{:ok, result}
end

defp chunkify(number) do
number
|> Integer.digits()
|> chunk_in_threes_from_decimal()
|> Enum.map(&Integer.undigits/1)
end

defp chunk_in_threes_from_decimal(digit_list) do
digit_list
|> Enum.reverse()
|> Enum.chunk_every(3)
|> Enum.map(&Enum.reverse/1)
|> Enum.reverse()
end

def scale_words([first | ]), do: translate(first) <> " thousand"

def scale_words([first | [0, 0]]), do: translate(first) <> " million"

def scale_words([first | [0, 0, 0]]), do: translate(first) <> " billion"

def scale_words([first | rest] = chunks) do
case length(chunks) do
4 -> translate(first) <> " billion " <> scale_words(rest)
3 -> translate(first) <> " million " <> scale_words(rest)
2 -> translate(first) <> " thousand " <> scale_words(rest)
1 -> translate(first)
end
end

def translate(number) when is_hundred(number), do: meld_hundred(number)
def translate(number) when is_ty(number), do: meld_ty(number)
def translate(number) when is_teen(number), do: meld_tens(number)
def translate(number) when is_literal(number), do: literal(number)
def translate(0), do: ""

def literal(1), do: "one"
def literal(2), do: "two"
def literal(3), do: "three"
def literal(4), do: "four"
def literal(5), do: "five"
def literal(6), do: "six"
def literal(7), do: "seven"
def literal(8), do: "eight"
def literal(9), do: "nine"

defp meld_tens(10), do: "ten"
defp meld_tens(11), do: "eleven"
defp meld_tens(12), do: "twelve"
defp meld_tens(13), do: "thirteen"
defp meld_tens(15), do: "fifteen"
defp meld_tens(18), do: "eighteen"
defp meld_tens(num) when is_teen(num), do: translate(num - 10) <> "teen"
defp meld_tens(num) when is_ty(num), do: meld_ty(num)

defp meld_ty(20), do: "twenty"
defp meld_ty(30), do: "thirty"
defp meld_ty(40), do: "forty"
defp meld_ty(50), do: "fifty"
defp meld_ty(80), do: "eighty"
defp meld_ty(num) when is_even_ten(num) do
ten_magnigtude = num / 10 |> round() |> translate()
ten_magnigtude <> "ty"
end

defp meld_ty(num) do
tens_num = rem(num, 10) |> round()
translate(num - tens_num) <> "-" <> translate(tens_num)
end

defp meld_hundred(num) when is_even_hundred(num) do
hundred_magnitude = num / 100 |> round() |> translate()
hundred_magnitude <> " hundred"
end

defp meld_hundred(num) do
hundreds_num = rem(num, 100) |> round()
translate(num - hundreds_num) <> " " <> translate(hundreds_num)
end
end``````