From b4e1570ba62bc1de39be9340587fb82e5bc99136 Mon Sep 17 00:00:00 2001 From: Danylo Negrienko Date: Thu, 7 Mar 2024 07:33:20 -0500 Subject: [PATCH] lucas-numbers --- elixir/lucas-numbers/.exercism/config.json | 21 ++++ elixir/lucas-numbers/.exercism/metadata.json | 1 + elixir/lucas-numbers/.formatter.exs | 4 + elixir/lucas-numbers/.gitignore | 27 +++++ elixir/lucas-numbers/HELP.md | 75 ++++++++++++ elixir/lucas-numbers/HINTS.md | 29 +++++ elixir/lucas-numbers/README.md | 67 +++++++++++ elixir/lucas-numbers/lib/lucas_numbers.ex | 17 +++ elixir/lucas-numbers/mix.exs | 28 +++++ .../lucas-numbers/test/lucas_numbers_test.exs | 112 ++++++++++++++++++ elixir/lucas-numbers/test/test_helper.exs | 2 + 11 files changed, 383 insertions(+) create mode 100644 elixir/lucas-numbers/.exercism/config.json create mode 100644 elixir/lucas-numbers/.exercism/metadata.json create mode 100644 elixir/lucas-numbers/.formatter.exs create mode 100644 elixir/lucas-numbers/.gitignore create mode 100644 elixir/lucas-numbers/HELP.md create mode 100644 elixir/lucas-numbers/HINTS.md create mode 100644 elixir/lucas-numbers/README.md create mode 100644 elixir/lucas-numbers/lib/lucas_numbers.ex create mode 100644 elixir/lucas-numbers/mix.exs create mode 100644 elixir/lucas-numbers/test/lucas_numbers_test.exs create mode 100644 elixir/lucas-numbers/test/test_helper.exs diff --git a/elixir/lucas-numbers/.exercism/config.json b/elixir/lucas-numbers/.exercism/config.json new file mode 100644 index 0000000..3434023 --- /dev/null +++ b/elixir/lucas-numbers/.exercism/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "neenjaw" + ], + "contributors": [ + "angelikatyborska" + ], + "files": { + "solution": [ + "lib/lucas_numbers.ex" + ], + "test": [ + "test/lucas_numbers_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "blurb": "Learn about streams by generating the Lucas number sequence." +} diff --git a/elixir/lucas-numbers/.exercism/metadata.json b/elixir/lucas-numbers/.exercism/metadata.json new file mode 100644 index 0000000..1f7addf --- /dev/null +++ b/elixir/lucas-numbers/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"lucas-numbers","id":"b7780e9479024bcb9aedfcad6acca6e5","url":"https://exercism.org/tracks/elixir/exercises/lucas-numbers","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/lucas-numbers/.formatter.exs b/elixir/lucas-numbers/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/lucas-numbers/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/lucas-numbers/.gitignore b/elixir/lucas-numbers/.gitignore new file mode 100644 index 0000000..7b6c814 --- /dev/null +++ b/elixir/lucas-numbers/.gitignore @@ -0,0 +1,27 @@ +# 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"). +lucas_numbers-*.tar + + +# Temporary files for e.g. tests +/tmp diff --git a/elixir/lucas-numbers/HELP.md b/elixir/lucas-numbers/HELP.md new file mode 100644 index 0000000..bc0f0be --- /dev/null +++ b/elixir/lucas-numbers/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/lucas_numbers.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/lucas-numbers/HINTS.md b/elixir/lucas-numbers/HINTS.md new file mode 100644 index 0000000..818a398 --- /dev/null +++ b/elixir/lucas-numbers/HINTS.md @@ -0,0 +1,29 @@ +# Hints + +## General + +- Use the built-in [(linked) list type][list]. +- Use the built-in [`Stream`][stream] module functions to create a stream + +## 1. Generate the base cases + +- You can use multiple [function clauses][multiple-fn-clauses] and [pattern-matching][pattern-matching] to create the base case functions. + +## 2. Create the generalized case + +- Use the [`Stream.iterate/2`][stream-iterate] function to generate a sequence of numbers, with the next being created from the previous. +- The starting numbers are `2` then `1`, which you can pass in together using a tuple to make a pair `{2, 1}` +- Make sure the next number is the sum of the two numbers previous to it. +- To evaluate the stream to a list, use an [`Enum`][enum] function. + +## 3. Catch bad arguments + +- Use a [guard][guards] to catch the cases when an integer isn't passed as an argument to `generate/1`. + +[enum]: https://hexdocs.pm/elixir/Enum.html#content +[guards]: https://hexdocs.pm/elixir/patterns-and-guards.html#guards +[list]: https://hexdocs.pm/elixir/lists-and-tuples.html#linked-lists +[multiple-fn-clauses]: https://hexdocs.pm/elixir/modules-and-functions.html#function-definition +[pattern-matching]: https://hexdocs.pm/elixir/pattern-matching.html#pattern-matching +[stream]: https://hexdocs.pm/elixir/Stream.html#content +[stream-iterate]: https://hexdocs.pm/elixir/Stream.html#iterate/2 \ No newline at end of file diff --git a/elixir/lucas-numbers/README.md b/elixir/lucas-numbers/README.md new file mode 100644 index 0000000..cc41f67 --- /dev/null +++ b/elixir/lucas-numbers/README.md @@ -0,0 +1,67 @@ +# Lucas Numbers + +Welcome to Lucas Numbers on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +## Streams + +All functions in the [`Enum` module][exercism-enum] are _eager_. When performing multiple operations on enumerables with the `Enum` module, each operation is going to generate an intermediate result. + +The `Stream` module is a _lazy_ alternative to the _eager_ `Enum` module. It offers many of the same functions as `Enum`, but instead of generating intermediate results, it builds a series of computations that are only executed once the stream is passed to a function from the `Enum` module. + +Streams implement the _Enumerable [protocol][exercism-protocols]_ and are composable -- you can chain them together to create more complex functionality. + +[exercism-enum]: https://exercism.org/tracks/elixir/concepts/enum +[exercism-protocols]: https://exercism.org/tracks/elixir/concepts/protocols + +## Instructions + +You are a huge fan of the [Numberphile Youtube channel](https://www.youtube.com/watch?v=PeUbRXnbmms) and you just saw a cool video about the _Lucas Number Sequence_. You want to create this sequence using Elixir. + +While designing your function, you want to make use of _lazy evaluation_, so that you can generate as many numbers as you want, but only if you need to -- So you decide to use a stream: + +## 1. Generate the base cases + +You know that the sequence has two starting numbers which don't follow the same rule. Write two base case clauses to return these numbers: + +```elixir +LucasNumbers.generate(1) +# => [2] + +LucasNumbers.generate(2) +# => [2, 1] +``` + +## 2. Create the generalized case + +For any sequence longer than 2, you know that you need to add the previous two numbers to get the next number and so on. Write the generalized case. + +```elixir +LucasNumbers.generate(3) +# => [2, 1, 3] + +LucasNumbers.generate(4) +# => [2, 1, 3, 4] +``` + +## 3. Catch bad arguments + +Later, you find someone is using your function and having problems because they are using incorrect arguments. Add a guard clause to raise an error if a non-integer or an integer less than 1 is used to generate the sequence: + +```elixir +LucasNumbers.generate("Hello World") +# => ** (ArgumentError) count must be specified as an integer >= 1 +``` + +## Source + +### Created by + +- @neenjaw + +### Contributed to by + +- @angelikatyborska \ No newline at end of file diff --git a/elixir/lucas-numbers/lib/lucas_numbers.ex b/elixir/lucas-numbers/lib/lucas_numbers.ex new file mode 100644 index 0000000..317a8b7 --- /dev/null +++ b/elixir/lucas-numbers/lib/lucas_numbers.ex @@ -0,0 +1,17 @@ +defmodule LucasNumbers do + @moduledoc """ + Lucas numbers are an infinite sequence of numbers which build progressively + which hold a strong correlation to the golden ratio (φ or ϕ) + + E.g.: 2, 1, 3, 4, 7, 11, 18, 29, ... + """ + + def generate(count) when is_integer(count) and count > 0 do + {2, 1} + |> Stream.iterate(fn {a, b} -> {b, a + b} end) + |> Stream.map(&elem(&1, 0)) + |> Enum.take(count) + end + + def generate(_count), do: raise(ArgumentError, "count must be specified as an integer >= 1") +end diff --git a/elixir/lucas-numbers/mix.exs b/elixir/lucas-numbers/mix.exs new file mode 100644 index 0000000..8cd4830 --- /dev/null +++ b/elixir/lucas-numbers/mix.exs @@ -0,0 +1,28 @@ +defmodule LucasNumbers.MixProject do + use Mix.Project + + def project do + [ + app: :lucas_numbers, + version: "0.1.0", + # elixir: "~> 1.10", + 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/lucas-numbers/test/lucas_numbers_test.exs b/elixir/lucas-numbers/test/lucas_numbers_test.exs new file mode 100644 index 0000000..af15c08 --- /dev/null +++ b/elixir/lucas-numbers/test/lucas_numbers_test.exs @@ -0,0 +1,112 @@ +defmodule LucasNumbersTest do + use ExUnit.Case + + @tag task_id: 1 + test "generates a sequence of length 1" do + assert LucasNumbers.generate(1) == [2] + end + + @tag task_id: 1 + test "generates a sequence of length 2" do + assert LucasNumbers.generate(2) == [2, 1] + end + + @tag task_id: 2 + test "generates a sequence of length 3" do + assert LucasNumbers.generate(3) == [2, 1, 3] + end + + @tag task_id: 2 + test "generates a sequence of length 4" do + assert LucasNumbers.generate(4) == [2, 1, 3, 4] + end + + @tag task_id: 2 + test "generates a sequence of length 5" do + sequence = [2, 1, 3, 4, 7] + + assert LucasNumbers.generate(5) == sequence + end + + @tag task_id: 2 + test "generates a sequence of length 6" do + sequence = [2, 1, 3, 4, 7, 11] + + assert LucasNumbers.generate(6) == sequence + end + + @tag task_id: 2 + test "generates a sequence of length 7" do + sequence = [2, 1, 3, 4, 7, 11, 18] + + assert LucasNumbers.generate(7) == sequence + end + + @tag task_id: 2 + test "generates a sequence of length 8" do + sequence = [2, 1, 3, 4, 7, 11, 18, 29] + + assert LucasNumbers.generate(8) == sequence + end + + @tag task_id: 2 + test "generates a sequence of length 9" do + sequence = [2, 1, 3, 4, 7, 11, 18, 29, 47] + + assert LucasNumbers.generate(9) == sequence + end + + @tag task_id: 2 + test "generates a sequence of length 10" do + sequence = [2, 1, 3, 4, 7, 11, 18, 29, 47, 76] + + assert LucasNumbers.generate(10) == sequence + end + + @tag task_id: 2 + test "generates a sequence of length 25" do + sequence = [ + 2, + 1, + 3, + 4, + 7, + 11, + 18, + 29, + 47, + 76, + 123, + 199, + 322, + 521, + 843, + 1364, + 2207, + 3571, + 5778, + 9349, + 15127, + 24476, + 39603, + 64079, + 103_682 + ] + + assert LucasNumbers.generate(25) == sequence + end + + @tag task_id: 3 + test "catch incorrect non-integer arguments" do + assert_raise ArgumentError, "count must be specified as an integer >= 1", fn -> + LucasNumbers.generate("Hello world!") + end + end + + @tag task_id: 3 + test "catch incorrect integer arguments" do + assert_raise ArgumentError, "count must be specified as an integer >= 1", fn -> + LucasNumbers.generate(-1) + end + end +end diff --git a/elixir/lucas-numbers/test/test_helper.exs b/elixir/lucas-numbers/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/lucas-numbers/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0)