ðŸŽ‰ Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io ðŸŽ‰

# MechimCook's solution

## to Say in the Elixir Track

Published at Jun 05 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
@doc """
Translate a positive integer into English.
"""
@under_20 [
"",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"ten",
"eleven",
"twelve",
"thirteen",
"fourteen",
"fifteen",
"sixteen",
"seventeen",
"eighteen",
"nineteen"
]
@over_19 ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
@over_100 ["", "thousand", "million", "billion"]
@spec in_english(integer) :: {atom, String.t()}

def in_english(number) when number > 999_999_999_999, do: {:error, "number is out of range"}
def in_english(number) when number < 0, do: {:error, "number is out of range"}
def in_english(number) when number == 0, do: {:ok, "zero"}

def in_english(number) do
english =
Integer.digits(number)
|> to_hundreds([], 0)
|> String.trim()

{:ok, english}
end

defp to_hundreds([], sorted, count), do: sorted

defp to_hundreds(unsorted, sorted, count) do
{remaining, grouped} = Enum.split(unsorted, -3)
next = hundreds_to_english(grouped)

if next == "" do
to_hundreds(remaining, "#{sorted}", count + 1)
else
to_hundreds(remaining, "#{next} #{Enum.at(@over_100, count)} #{sorted}", count + 1)
end
end

defp hundreds_to_english([0, 0, 0]), do: ""

defp hundreds_to_english(numbers) do
case numbers do
[0, y, z] ->
teens(y, z)

[x, y, z] ->
["#{Enum.at(@under_20, x)} hundred " | teens(y, z)]

[x, y] ->
teens(x, y)

[x] ->
[Enum.at(@under_20, x)]
end
end

defp teens(tenz, singlez) do
case tenz do
1 ->
Enum.at(@under_20, singlez + 10)

0 ->
Enum.at(@under_20, singlez)

_ ->
if singlez == 0 do
Enum.at(@over_19, tenz)
else
"#{Enum.at(@over_19, tenz)}-#{Enum.at(@under_20, singlez)}"
end
end
end
end``````