From b21db6b2f9aefd1ce9bf79928ba50de24b04340e Mon Sep 17 00:00:00 2001 From: Danylo Negrienko Date: Wed, 21 Aug 2024 17:59:36 -0400 Subject: [PATCH] say --- elixir/say/.exercism/config.json | 29 +++++++++++ elixir/say/.exercism/metadata.json | 1 + elixir/say/.formatter.exs | 4 ++ elixir/say/.gitignore | 24 +++++++++ elixir/say/HELP.md | 75 +++++++++++++++++++++++++++ elixir/say/README.md | 74 +++++++++++++++++++++++++++ elixir/say/lib/say.ex | 74 +++++++++++++++++++++++++++ elixir/say/mix.exs | 28 +++++++++++ elixir/say/test/say_test.exs | 81 ++++++++++++++++++++++++++++++ elixir/say/test/test_helper.exs | 2 + 10 files changed, 392 insertions(+) create mode 100644 elixir/say/.exercism/config.json create mode 100644 elixir/say/.exercism/metadata.json create mode 100644 elixir/say/.formatter.exs create mode 100644 elixir/say/.gitignore create mode 100644 elixir/say/HELP.md create mode 100644 elixir/say/README.md create mode 100644 elixir/say/lib/say.ex create mode 100644 elixir/say/mix.exs create mode 100644 elixir/say/test/say_test.exs create mode 100644 elixir/say/test/test_helper.exs diff --git a/elixir/say/.exercism/config.json b/elixir/say/.exercism/config.json new file mode 100644 index 0000000..a43f309 --- /dev/null +++ b/elixir/say/.exercism/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + "zporter" + ], + "contributors": [ + "aifrak", + "amencarini", + "angelikatyborska", + "Cohen-Carlisle", + "devonestes", + "neenjaw", + "parkerl", + "sotojuan" + ], + "files": { + "solution": [ + "lib/say.ex" + ], + "test": [ + "test/say_test.exs" + ], + "example": [ + ".meta/example.ex" + ] + }, + "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", + "source": "A variation on the JavaRanch CattleDrive, Assignment 4", + "source_url": "https://coderanch.com/wiki/718804" +} diff --git a/elixir/say/.exercism/metadata.json b/elixir/say/.exercism/metadata.json new file mode 100644 index 0000000..29e3a3a --- /dev/null +++ b/elixir/say/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"say","id":"85975fde45c04f6b8433af76568462eb","url":"https://exercism.org/tracks/elixir/exercises/say","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/say/.formatter.exs b/elixir/say/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/say/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/say/.gitignore b/elixir/say/.gitignore new file mode 100644 index 0000000..57fd105 --- /dev/null +++ b/elixir/say/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +say-*.tar + diff --git a/elixir/say/HELP.md b/elixir/say/HELP.md new file mode 100644 index 0000000..92dc684 --- /dev/null +++ b/elixir/say/HELP.md @@ -0,0 +1,75 @@ +# Help + +## Running the tests + +From the terminal, change to the base directory of the exercise then execute the tests with: + +```bash +$ mix test +``` + +This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` + +Documentation: + +* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) +* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) + +## Pending tests + +In test suites of practice exercises, all but the first test have been tagged to be 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: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: + +```bash +$ mix test --include pending +``` + +Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. + +```elixir +# ExUnit.configure(exclude: :pending, trace: true) +``` + +## Useful `mix test` options + +* `test/.exs:LINENUM` - runs only a single test, the test from `.exs` whose definition is on line `LINENUM` +* `--failed` - runs only tests that failed the last time they ran +* `--max-failures` - the suite stops evaluating tests when this number of test failures +is reached +* `--seed 0` - disables randomization so the tests in a single file will always be ran +in the same order they were defined in + +## Submitting your solution + +You can submit your solution using the `exercism submit lib/say.ex` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) +- The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. \ No newline at end of file diff --git a/elixir/say/README.md b/elixir/say/README.md new file mode 100644 index 0000000..0dde248 --- /dev/null +++ b/elixir/say/README.md @@ -0,0 +1,74 @@ +# Say + +Welcome to Say on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +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. + +## 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. + +## Source + +### Created by + +- @zporter + +### Contributed to by + +- @aifrak +- @amencarini +- @angelikatyborska +- @Cohen-Carlisle +- @devonestes +- @neenjaw +- @parkerl +- @sotojuan + +### Based on + +A variation on the JavaRanch CattleDrive, Assignment 4 - https://coderanch.com/wiki/718804 \ No newline at end of file diff --git a/elixir/say/lib/say.ex b/elixir/say/lib/say.ex new file mode 100644 index 0000000..3cf0451 --- /dev/null +++ b/elixir/say/lib/say.ex @@ -0,0 +1,74 @@ +defmodule Say do + @in_english_19 %{ + 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" + } + + @in_english_100 %{ + 2 => "twenty", + 3 => "thirty", + 4 => "forty", + 5 => "fifty", + 6 => "sixty", + 7 => "seventy", + 8 => "eighty", + 9 => "ninety" + } + + @doc """ + Translate a positive integer into English. + """ + @spec in_english(integer) :: {atom, String.t()} + def in_english(number) when number < 0 or number > 999_999_999_999, + do: {:error, "number is out of range"} + + def in_english(0), do: {:ok, "zero"} + + def in_english(number) do + {:ok, + number + |> Integer.digits() + |> Enum.reverse() + |> Enum.chunk_every(3) + |> Enum.with_index(fn element, index -> in_english_part(element, index) end) + |> Enum.reverse() + |> List.flatten() + |> Enum.reject(&is_nil/1) + |> Enum.join(" ") + } + end + + @spec in_english_part([integer], integer) :: String.t() + defp in_english_part([0, 0, 0], _index), do: [] + defp in_english_part(element, 3), do: [do_in_english_part(element) | ["billion"]] + defp in_english_part(element, 2), do: [do_in_english_part(element) | ["million"]] + defp in_english_part(element, 1), do: [do_in_english_part(element) | ["thousand"]] + defp in_english_part(element, 0), do: [do_in_english_part(element)] + + defp do_in_english_part([d1, d2, d3]) when d3 != 0, do: [@in_english_19[d3], "hundred" | [do_in_english_part([d1, d2])]] + defp do_in_english_part([d1, d2, 0]), do: do_in_english_part([d1, d2]) + defp do_in_english_part([d1, d2]) when d2 < 2, do: @in_english_19[Integer.undigits([d2, d1])] + defp do_in_english_part([0, d2]) when d2 > 1, do: @in_english_100[d2] + + defp do_in_english_part([d1, d2]) when d2 > 1, + do: @in_english_100[d2] <> "-" <> @in_english_19[d1] + + defp do_in_english_part([d1]), do: @in_english_19[d1] +end diff --git a/elixir/say/mix.exs b/elixir/say/mix.exs new file mode 100644 index 0000000..da2bf58 --- /dev/null +++ b/elixir/say/mix.exs @@ -0,0 +1,28 @@ +defmodule Say.MixProject do + use Mix.Project + + def project do + [ + app: :say, + version: "0.1.0", + # elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/elixir/say/test/say_test.exs b/elixir/say/test/say_test.exs new file mode 100644 index 0000000..0498881 --- /dev/null +++ b/elixir/say/test/say_test.exs @@ -0,0 +1,81 @@ +defmodule SayTest do + use ExUnit.Case + + test "zero" do + assert Say.in_english(0) == {:ok, "zero"} + end + + test "one" do + assert Say.in_english(1) == {:ok, "one"} + end + + test "fourteen" do + assert Say.in_english(14) == {:ok, "fourteen"} + end + + test "twenty" do + assert Say.in_english(20) == {:ok, "twenty"} + end + + test "twenty-two" do + assert Say.in_english(22) == {:ok, "twenty-two"} + end + + test "thirty" do + assert Say.in_english(30) == {:ok, "thirty"} + end + + test "ninety-nine" do + assert Say.in_english(99) == {:ok, "ninety-nine"} + end + + test "one hundred" do + assert Say.in_english(100) == {:ok, "one hundred"} + end + + test "one hundred twenty-three" do + assert Say.in_english(123) == {:ok, "one hundred twenty-three"} + end + + test "two hundred" do + assert Say.in_english(200) == {:ok, "two hundred"} + end + + test "nine hundred ninety-nine" do + assert Say.in_english(999) == {:ok, "nine hundred ninety-nine"} + end + + test "one thousand" do + assert Say.in_english(1_000) == {:ok, "one thousand"} + end + + test "one thousand two hundred thirty-four" do + assert Say.in_english(1_234) == {:ok, "one thousand two hundred thirty-four"} + end + + test "one million" do + assert Say.in_english(1_000_000) == {:ok, "one million"} + end + + 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 + + test "one billion" do + assert Say.in_english(1_000_000_000) == {:ok, "one billion"} + end + + 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 + + test "numbers below zero are out of range" do + assert Say.in_english(-1) == {:error, "number is out of range"} + end + + 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 diff --git a/elixir/say/test/test_helper.exs b/elixir/say/test/test_helper.exs new file mode 100644 index 0000000..35fc5bf --- /dev/null +++ b/elixir/say/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true)