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

## 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
@cardinals %{
0 => "zero",
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"
}
@factors [
[1_000_000_000, "billion"],
[1_000_000, "million"],
[1_000, "thousand"],
[100, "hundred"]
]
@doc """
Translate a positive integer into English.
"""
@spec in_english(integer) :: {atom, String.t()}
def in_english(number) when number < 0
when number > 999_999_999_999
do
{:error, "number is out of range"}
end
def in_english(number) do
{:ok, number_to_word(number)}
end

defp number_to_word(number) when is_map_key(@cardinals, number) do
@cardinals |> Map.get(number)
end
defp number_to_word(number) when number < 100 do
[tens, number] = Integer.digits(number)
"#{number_to_word(tens * 10)}-#{number_to_word(number)}"
end
defp number_to_word(number) do
[factor, postfix] = factor(number)
hundreds = div(number, factor)
rem = rem(number, factor)

case rem do
0 -> "#{number_to_word(hundreds)} #{postfix}"
_ -> "#{number_to_word(hundreds)} #{postfix} #{number_to_word(rem)}"
end
end

defp factor(number) do
@factors
|>Enum.find(fn [min, _] ->
number >= min and number < factor_max_value(min)
end)
end

defp factor_max_value(100), do: 999
defp factor_max_value(factor), do: factor * 1_000 - 1
end``````