diff --git a/exercises/practice/raindrops/.approaches/check-every-possibility/content.md b/exercises/practice/raindrops/.approaches/check-every-possibility/content.md new file mode 100644 index 0000000000..dfb0866142 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/check-every-possibility/content.md @@ -0,0 +1,75 @@ +# Pattern Matching + +```elixir +defmodule Raindrops do + @spec convert(pos_integer) :: String.t() + def convert(number) do + case {rem(number, 3), rem(number, 5), rem(number, 7)} do + {0, 0, 0} -> "PlingPlangPlong" + {0, 0, _} -> "PlingPlang" + {0, _, 0} -> "PlingPlong" + {_, 0, 0} -> "PlangPlong" + {0, _, _} -> "Pling" + {_, 0, _} -> "Plang" + {_, _, 0} -> "Plong" + _ -> Integer.to_string(number) + end + end +end +``` + +## Case +The case allows us to evaluate if a number is divisible by 3, 5 and 7 once, and then match the results to various combinations of the possible outcomes. +The advantage of using a `case` on a tuple, like in the example above, is that the `rem` functions are executed only once. + +## Cond +We can use `cond do`, too. +However, first, let's look at the maths to make the solution more compact. + +A number is divisible by `a`, `b`, and `c` only when it is divisible by `a*b*c`. +So, instead of +```elixir +rem(number, 3) == 0 and rem(number, 5) == 0 and rem(number, 7) == 0 +``` +we can write +```elixir +rem(number, 3*5*7) == 0 +``` + +Now, let's look at this pattern matching with `cond`. + +```elixir +def convert(number) do + cond do + rem(number, 3*5*7) == 0 -> "PlingPlangPlong" + rem(number, 3*5) == 0 -> "PlingPlang" + rem(number, 3*7) == 0 -> "PlingPlong" + rem(number, 5*7) == 0 -> "PlangPlong" + rem(number, 3) == 0 -> "Pling" + rem(number, 5) == 0 -> "Plang" + rem(number, 7) == 0 -> "Plong" + true -> Integer.to_string(number) + end +end +``` + +## Multiple-clause functions +We can do something very similar by using guards in multi-clause functions. +We use different feautre of the language, but at its core, the approach is the same. + +```elixir +defmodule Raindrops do + @spec convert(pos_integer) :: String.t() + def convert(number) when rem(number, 3*5*7) == 0, do: "PlingPlangPlong" + def convert(number) when rem(number, 3*5) == 0, do: "PlingPlang" + def convert(number) when rem(number, 3*7) == 0, do: "PlingPlong" + def convert(number) when rem(number, 5*7) == 0, do: "PlangPlong" + def convert(number) when rem(number, 3) == 0, do: "Pling" + def convert(number) when rem(number, 5) == 0, do: "Plang" + def convert(number) when rem(number, 7) == 0, do: "Plong" + def convert(number), do: Integer.to_string(number) +end +``` + +We can use different features of the language, but at its core, the approach is the same. +We check a set of conditions that leads us to the exact answer. \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/check-every-possibility/snippet.txt b/exercises/practice/raindrops/.approaches/check-every-possibility/snippet.txt new file mode 100644 index 0000000000..1fea8073e3 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/check-every-possibility/snippet.txt @@ -0,0 +1,12 @@ +def convert(number) do + case {rem(number, 3), rem(number, 5), rem(number, 7)} do + {0, 0, 0} -> "PlingPlangPlong" + {0, 0, _} -> "PlingPlang" + {0, _, 0} -> "PlingPlong" + {_, 0, 0} -> "PlangPlong" + {0, _, _} -> "Pling" + {_, 0, _} -> "Plang" + {_, _, 0} -> "Plong" + _ -> Integer.to_string(number) + end +end diff --git a/exercises/practice/raindrops/.approaches/config.json b/exercises/practice/raindrops/.approaches/config.json new file mode 100644 index 0000000000..0710c3bccb --- /dev/null +++ b/exercises/practice/raindrops/.approaches/config.json @@ -0,0 +1,27 @@ +{ + "introduction": { + "authors": [ + "michalporeba" + ] + }, + "approaches": [ + { + "uuid": "fbfbabb4-f4e6-4329-b2f5-a93e3199b809", + "slug": "check-every-possibility", + "title": "Every Possibility", + "blurb": "Check every possibility.", + "authors": [ + "michalporeba" + ] + }, + { + "uuid": "925ccb59-3414-472b-9054-1cdfc5e44fad", + "slug": "step-by-step", + "title": "Step By Step", + "blurb": "Perform the checks one by one, step by step.", + "authors": [ + "michalporeba" + ] + } + ] +} diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md new file mode 100644 index 0000000000..fff010bf15 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -0,0 +1,49 @@ +# Introduction + +## Check every possibility + +The output of the `convert` method depends on three conditions which can be either true or false. +This gives only eight possibilities and we can check them all. + +```elixir +def convert(number) do + case {rem(number, 3), rem(number, 5), rem(number, 7)} do + {0, 0, 0} -> "PlingPlangPlong" + {0, 0, _} -> "PlingPlang" + {0, _, 0} -> "PlingPlong" + {_, 0, 0} -> "PlangPlong" + {0, _, _} -> "Pling" + {_, 0, _} -> "Plang" + {_, _, 0} -> "Plong" + _ -> Integer.to_string(number) + end +end +``` + +We can use a few Elixir features to do more or less the same and we explore them in the [check every possibility approach][check-every-possibility-approach]. + +## Step by step + +An alternative approach is to consider each condition one at a time. +At each step we return either a sound (i.e. "Pling", "Plang", or "Plong"), or an empty string. +We can then concatenate the strings together. + +```elixir +def convert(number) do + pling = if rem(number, 3) == 0, do: "Pling", else: "" + plang = if rem(number, 5) == 0, do: "Plang", else: "" + plong = if rem(number, 7) == 0, do: "Plong", else: "" + result = pling <> plang <> plong + + if result == "" do + Integer.to_string(number) + else + result + end +end +``` + +Let's have a look at a few variations of this [step by step approach][step-by-step-approach]. + +[check-every-possibility-approach]: https://exercism.org/tracks/elixir/exercises/raindrops/approaches/check-every-possibility +[step-by-step-approach]: https://exercism.org/tracks/elixir/exercises/raindrops/approaches/step-by-step \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/step-by-step/content.md b/exercises/practice/raindrops/.approaches/step-by-step/content.md new file mode 100644 index 0000000000..0bbdf724cd --- /dev/null +++ b/exercises/practice/raindrops/.approaches/step-by-step/content.md @@ -0,0 +1,77 @@ +# Step By Step + +```elixir +defmodule Raindrops do + @spec convert(pos_integer) :: String.t() + def convert(number) do + pling = if rem(number, 3) == 0, do: "Pling", else: "" + plang = if rem(number, 5) == 0, do: "Plang", else: "" + plong = if rem(number, 7) == 0, do: "Plong", else: "" + sound = pling <> plang <> plong + + if sound == "" do + Integer.to_string(number) + else + sound + end + end +end +``` + +In this approach, we test each condition only once, similar to using the `case` on a tuple in the [pattern matching approach][pattern-matching-approach]. +However, this time, if a condition is true, we capture the sound component, and if it is not true, we capture the sound as an empty string. + +Once this is done, we can concatenate all three conditions to get the full sound. +Finally, if the `sound` is empty, we can return the number or, alternatively, the calculated `sound`. + +## Functions + +We can create private functions to test for component sounds. + +```elixir +defp pling(n) when rem(n, 3) == 0, do: "Pling" +defp pling(_), do: "" +defp plang(n) when rem(n, 5) == 0, do: "Plang" +defp plang(_), do: "" +defp plong(n) when rem(n, 7) == 0, do: "Plong" +defp plong(_), do: "" +defp sound(sound, number) when sound == "", do: Integer.to_string(number) +defp sound(sound, _number), do: sound +``` + +Now the solution can look like this: +```elixir +def convert(number) do + sound(pling(number) <> plang(number) <> plong(number), number) +end +``` + +## The pipe operator + +With a slightly different design of the functions we can use the pipe operator to have a very clean-looking code + +```elixir +@spec convert(pos_integer) :: String.t() +def convert(number) do + {"", number} + |> pling + |> plang + |> plong + |> sound +end +``` +At least in the `convert` functions. The `pling`, `plang`, `plong` become a bit more complex: +```elixir +defp pling({ s, n }) when rem(n, 3) == 0, do: { s <> "Pling", n } +defp pling({ s, n }), do: { s, n } +defp plang({ s, n }) when rem(n, 5) == 0, do: { s <> "Plang", n } +defp plang({ s, n }), do: { s, n } +defp plong({ s, n }) when rem(n, 7) == 0, do: { s <> "Plong", n } +defp plong({ s, n }), do: { s, n } +defp sound({ s, n }) when s == "" , do: n |> Integer.to_string +defp sound({ s, _ }), do: s +``` + +All the examples above, at their core, represent the same approach of doing the check step by step. + +[pattern-matching-approach]: https://exercism.org/tracks/elixir/exercises/raindrops/approaches/pattern-matching \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/step-by-step/snippet.txt b/exercises/practice/raindrops/.approaches/step-by-step/snippet.txt new file mode 100644 index 0000000000..7b9903f613 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/step-by-step/snippet.txt @@ -0,0 +1,12 @@ +def convert(number) do + pling = if rem(number, 3) == 0, do: "Pling", else: "" + plang = if rem(number, 5) == 0, do: "Plang", else: "" + plong = if rem(number, 7) == 0, do: "Plong", else: "" + result = pling <> plang <> plong + + if result == "" do + Integer.to_string(number) + else + result + end +end diff --git a/exercises/practice/raindrops/.meta/config.json b/exercises/practice/raindrops/.meta/config.json index bb893dc8e7..8301000d94 100644 --- a/exercises/practice/raindrops/.meta/config.json +++ b/exercises/practice/raindrops/.meta/config.json @@ -14,7 +14,8 @@ "parkerl", "sotojuan", "Teapane", - "waiting-for-dev" + "waiting-for-dev", + "michalporeba" ], "files": { "solution": [