commit 59d97be20c55f704ba21fc3b56c432c7a60d3467 Author: Danylo Negrienko Date: Sun Dec 17 00:26:14 2023 -0500 Initial commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..6cde098 Binary files /dev/null and b/.DS_Store differ diff --git a/elixir/.DS_Store b/elixir/.DS_Store new file mode 100644 index 0000000..fbfd094 Binary files /dev/null and b/elixir/.DS_Store differ diff --git a/elixir/darts/.exercism/config.json b/elixir/darts/.exercism/config.json new file mode 100644 index 0000000..65c9d2c --- /dev/null +++ b/elixir/darts/.exercism/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "jiegillet" + ], + "contributors": [ + "angelikatyborska" + ], + "files": { + "solution": [ + "lib/darts.ex" + ], + "test": [ + "test/darts_test.exs" + ], + "example": [ + ".meta/example.ex" + ] + }, + "blurb": "Write a function that returns the earned points in a single toss of a Darts game.", + "source": "Inspired by an exercise created by a professor Della Paolera in Argentina" +} diff --git a/elixir/darts/.exercism/metadata.json b/elixir/darts/.exercism/metadata.json new file mode 100644 index 0000000..2798e4c --- /dev/null +++ b/elixir/darts/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"darts","id":"1062d062de474a2ab65b96bb7027ec45","url":"https://exercism.org/tracks/elixir/exercises/darts","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/darts/.formatter.exs b/elixir/darts/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/darts/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/darts/.gitignore b/elixir/darts/.gitignore new file mode 100644 index 0000000..49ee5e4 --- /dev/null +++ b/elixir/darts/.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"). +darts-*.tar + diff --git a/elixir/darts/HELP.md b/elixir/darts/HELP.md new file mode 100644 index 0000000..be3f1d3 --- /dev/null +++ b/elixir/darts/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/darts.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/darts/HINTS.md b/elixir/darts/HINTS.md new file mode 100644 index 0000000..18659c5 --- /dev/null +++ b/elixir/darts/HINTS.md @@ -0,0 +1,7 @@ +# Hints + +## General + +- The distance of a point `(x, y)` from the center of the target `(0, 0)` can be calculated with `sqrt(x*x + y *y)` + +- You can calculate the square root of `x` by raising it to the power of `1/2`. In other words, `sqrt(x) == pow(x, 0.5)` \ No newline at end of file diff --git a/elixir/darts/README.md b/elixir/darts/README.md new file mode 100644 index 0000000..17da9c4 --- /dev/null +++ b/elixir/darts/README.md @@ -0,0 +1,51 @@ +# Darts + +Welcome to Darts 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 :) + +## Instructions + +Write a function that returns the earned points in a single toss of a Darts game. + +[Darts][darts] is a game where players throw darts at a [target][darts-target]. + +In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands: + +![Our dart scoreboard with values from a complete miss to a bullseye](https://assets.exercism.org/images/exercises/darts/darts-scoreboard.svg) + +- If the dart lands outside the target, player earns no points (0 points). +- If the dart lands in the outer circle of the target, player earns 1 point. +- If the dart lands in the middle circle of the target, player earns 5 points. +- If the dart lands in the inner circle of the target, player earns 10 points. + +The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. +Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0). + +Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point. + +## Credit + +The scoreboard image was created by [habere-et-dispertire][habere-et-dispertire] using [Inkscape][inkscape]. + +[darts]: https://en.wikipedia.org/wiki/Darts +[darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg +[concentric]: https://mathworld.wolfram.com/ConcentricCircles.html +[cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html +[real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[inkscape]: https://en.wikipedia.org/wiki/Inkscape + +## Source + +### Created by + +- @jiegillet + +### Contributed to by + +- @angelikatyborska + +### Based on + +Inspired by an exercise created by a professor Della Paolera in Argentina \ No newline at end of file diff --git a/elixir/darts/lib/darts.ex b/elixir/darts/lib/darts.ex new file mode 100644 index 0000000..08f46ce --- /dev/null +++ b/elixir/darts/lib/darts.ex @@ -0,0 +1,17 @@ +defmodule Darts do + @type position :: {number, number} + + @doc """ + Calculate the score of a single dart hitting a target + """ + @spec score(position) :: integer + def score({x, y}) do + in_radius = (x ** 2 + y ** 2) ** 0.5 + cond do + in_radius <= 1 -> 10 + in_radius <= 5 -> 5 + in_radius <= 10 -> 1 + true -> 0 + end + end +end diff --git a/elixir/darts/mix.exs b/elixir/darts/mix.exs new file mode 100644 index 0000000..6946283 --- /dev/null +++ b/elixir/darts/mix.exs @@ -0,0 +1,28 @@ +defmodule Darts.MixProject do + use Mix.Project + + def project do + [ + app: :darts, + 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/darts/test/darts_test.exs b/elixir/darts/test/darts_test.exs new file mode 100644 index 0000000..7233e88 --- /dev/null +++ b/elixir/darts/test/darts_test.exs @@ -0,0 +1,68 @@ +defmodule DartsTest do + use ExUnit.Case + + # @tag :pending + test "Missed target" do + assert Darts.score({-9, 9}) == 0 + end + + # @tag :pending + test "On the outer circle" do + assert Darts.score({0, 10}) == 1 + end + + # @tag :pending + test "On the middle circle" do + assert Darts.score({-5, 0}) == 5 + end + + # @tag :pending + test "On the inner circle" do + assert Darts.score({0, -1}) == 10 + end + + # @tag :pending + test "Exactly on centre" do + assert Darts.score({0, 0}) == 10 + end + + # @tag :pending + test "Near the centre" do + assert Darts.score({-0.1, -0.1}) == 10 + end + + # @tag :pending + test "Just within the inner circle" do + assert Darts.score({0.7, 0.7}) == 10 + end + + # @tag :pending + test "Just outside the inner circle" do + assert Darts.score({0.8, -0.8}) == 5 + end + + # @tag :pending + test "Just within the middle circle" do + assert Darts.score({-3.5, 3.5}) == 5 + end + + # @tag :pending + test "Just outside the middle circle" do + assert Darts.score({-3.6, -3.6}) == 1 + end + + # @tag :pending + test "Just within the outer circle" do + assert Darts.score({-7.0, 7.0}) == 1 + end + + # @tag :pending + test "Just outside the outer circle" do + assert Darts.score({7.1, -7.1}) == 0 + end + + # @tag :pending + test "Asymmetric position between the inner and middle circles" do + assert Darts.score({0.5, -4}) == 5 + end +end diff --git a/elixir/darts/test/test_helper.exs b/elixir/darts/test/test_helper.exs new file mode 100644 index 0000000..35fc5bf --- /dev/null +++ b/elixir/darts/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true) diff --git a/elixir/freelancer-rates/.exercism/config.json b/elixir/freelancer-rates/.exercism/config.json new file mode 100644 index 0000000..541b4f4 --- /dev/null +++ b/elixir/freelancer-rates/.exercism/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "angelikatyborska" + ], + "contributors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/freelancer_rates.ex" + ], + "test": [ + "test/freelancer_rates_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "forked_from": [ + "javascript/freelancer-rates" + ], + "blurb": "Learn about integers and floating point numbers by helping a freelancer communicate with a project manager about billing." +} diff --git a/elixir/freelancer-rates/.exercism/metadata.json b/elixir/freelancer-rates/.exercism/metadata.json new file mode 100644 index 0000000..6e5f88b --- /dev/null +++ b/elixir/freelancer-rates/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"freelancer-rates","id":"25db5ef05b9b40a09965866b08a13f3f","url":"https://exercism.org/tracks/elixir/exercises/freelancer-rates","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/freelancer-rates/.formatter.exs b/elixir/freelancer-rates/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/freelancer-rates/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/freelancer-rates/.gitignore b/elixir/freelancer-rates/.gitignore new file mode 100644 index 0000000..d7c8d40 --- /dev/null +++ b/elixir/freelancer-rates/.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"). +numbers-*.tar + diff --git a/elixir/freelancer-rates/HELP.md b/elixir/freelancer-rates/HELP.md new file mode 100644 index 0000000..24d28cd --- /dev/null +++ b/elixir/freelancer-rates/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/freelancer_rates.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/freelancer-rates/HINTS.md b/elixir/freelancer-rates/HINTS.md new file mode 100644 index 0000000..1b52c78 --- /dev/null +++ b/elixir/freelancer-rates/HINTS.md @@ -0,0 +1,30 @@ +# Hints + +## General + +- Read about basic arithmetic in the official [Getting Started guide][getting-started-basic-arithmetic]. +- Browse the [`Float`][float-functions] and [`Kernel`][kernel-arithmetic-operators] modules to learn about functions and operators that work with floats. + +## 1. Calculate the daily rate given an hourly rate + +- [Basic arithmetic operations][kernel-arithmetic-operators] where one argument is an integer, and the other is a float, will return a float. + +## 2. Calculate a discounted price + +- [Basic arithmetic operations][kernel-arithmetic-operators] where one argument is an integer, and the other is a float, will return a float. + +## 3. Calculate the monthly rate, given an hourly rate and a discount + +- There is a [built-in function][kernel-trunc] for changing floats to integers. +- There is a [built-in function][float-ceil] for rounding floats up. + +## 4. Calculate the number of workdays given a budget, hourly rate and discount + +- There is a [built-in function][float-floor] for rounding floats down with desired precision. + +[getting-started-basic-arithmetic]: https://elixir-lang.org/getting-started/basic-types.html#basic-arithmetic +[kernel-arithmetic-operators]: https://hexdocs.pm/elixir/Kernel.html#*/2 +[kernel-trunc]: https://hexdocs.pm/elixir/Kernel.html#trunc/1 +[float-functions]: https://hexdocs.pm/elixir/Float.html#functions +[float-ceil]: https://hexdocs.pm/elixir/Float.html#ceil/2 +[float-floor]: https://hexdocs.pm/elixir/Float.html#floor/2 \ No newline at end of file diff --git a/elixir/freelancer-rates/README.md b/elixir/freelancer-rates/README.md new file mode 100644 index 0000000..ea8f040 --- /dev/null +++ b/elixir/freelancer-rates/README.md @@ -0,0 +1,125 @@ +# Freelancer Rates + +Welcome to Freelancer Rates 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 + +## Integers + +There are two different kinds of numbers in Elixir - integers and floats. + +Integers are whole numbers. + +```elixir +integer = 3 +# => 3 +``` + +## Floating Point Numbers + +Floats are numbers with one or more digits behind the decimal separator. They use the 64-bit double precision floating-point format. + +```elixir +float = 3.45 +# => 3.45 +``` + +### Working with numbers + +In the [`Integer`][integer-functions] and [`Float`][float-functions] modules you can find some useful functions for working with those types. Basic arithmetic operators are defined in the [`Kernel`][kernel-arithmetic-operators] module. + +### Conversion + +Integers and floats can be mixed together in a single arithmetic expression. Using a float in an expression ensures the result will be a float too. + +```elixir +2 * 3 +# => 6 + +2 * 3.0 +# => 6.0 +``` + +However, when doing division, the result will always be a float, even if only integers are used. + +```elixir +6 / 2 +# => 3.0 +``` + +To convert a float to an integer, you can discard the decimal part with [`trunc/1`][trunc-1]. + +[integer-functions]: https://hexdocs.pm/elixir/Integer.html#functions +[float-functions]: https://hexdocs.pm/elixir/Float.html#functions +[kernel-arithmetic-operators]: https://hexdocs.pm/elixir/Kernel.html#*/2 +[trunc-1]: https://hexdocs.pm/elixir/Kernel.html#trunc/1 + +## Instructions + +In this exercise you'll be writing code to help a freelancer communicate with a project manager by providing a few utilities to quickly calculate daily and +monthly rates, optionally with a given discount. + +We first establish a few rules between the freelancer and the project manager: + +- The daily rate is 8 times the hourly rate. +- A month has 22 billable days. + +Sometimes, the freelancer is offering to apply a discount on their daily rate (for example for their most loyal customers or for non-for-profit customers). + +Discounts are modeled as fractional numbers representing percentage, for example `25.0` (25%). + +## 1. Calculate the daily rate given an hourly rate + +Implement a function to calculate the daily rate given an hourly rate: + +```elixir +FreelancerRates.daily_rate(60) +# => 480.0 +``` + +The returned daily rate should be a float. + +## 2. Calculate a discounted price + +Implement a function to calculate the price after a discount. + +```elixir +FreelancerRates.apply_discount(150, 10) +# => 135.0 +``` + +The returned value should always be a float, not rounded in any way. + +## 3. Calculate the monthly rate, given an hourly rate and a discount + +Implement a function to calculate the monthly rate, and apply a discount: + +```elixir +FreelancerRates.monthly_rate(77, 10.5) +# => 12130 +``` + +The returned monthly rate should be rounded up (take the ceiling) to the nearest integer. + +## 4. Calculate the number of workdays given a budget, hourly rate and discount + +Implement a function that takes a budget, an hourly rate, and a discount, and calculates how many days of work that covers. + +```elixir +FreelancerRates.days_in_budget(20000, 80, 11.0) +# => 35.1 +``` + +The returned number of days should be rounded down (take the floor) to one decimal place. + +## Source + +### Created by + +- @angelikatyborska + +### Contributed to by + +- @neenjaw \ No newline at end of file diff --git a/elixir/freelancer-rates/lib/freelancer_rates.ex b/elixir/freelancer-rates/lib/freelancer_rates.ex new file mode 100644 index 0000000..7e0ca89 --- /dev/null +++ b/elixir/freelancer-rates/lib/freelancer_rates.ex @@ -0,0 +1,28 @@ +defmodule FreelancerRates do + def daily_rate(hourly_rate) do + 8.0 * hourly_rate + end + + def monthly_rate(hourly_rate) do + 22.0 * daily_rate(hourly_rate) + end + + def monthly_rate(hourly_rate, discount) do + hourly_rate + |> monthly_rate() + |> apply_discount(discount) + |> ceil() + end + + def apply_discount(before_discount, discount) do + before_discount - (before_discount * (discount / 100)) + end + + def days_in_budget(budget, hourly_rate, discount) do + hourly_rate + |> daily_rate() + |> apply_discount(discount) + |> then(fn rate -> budget / rate end) + |> Float.floor(1) + end +end diff --git a/elixir/freelancer-rates/mix.exs b/elixir/freelancer-rates/mix.exs new file mode 100644 index 0000000..830f405 --- /dev/null +++ b/elixir/freelancer-rates/mix.exs @@ -0,0 +1,28 @@ +defmodule FreelancerRates.MixProject do + use Mix.Project + + def project do + [ + app: :freelancer_rates, + 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/freelancer-rates/test/freelancer_rates_test.exs b/elixir/freelancer-rates/test/freelancer_rates_test.exs new file mode 100644 index 0000000..331a2bc --- /dev/null +++ b/elixir/freelancer-rates/test/freelancer_rates_test.exs @@ -0,0 +1,89 @@ +defmodule FreelancerRatesTest do + use ExUnit.Case + + describe "daily_rate/1" do + @tag task_id: 1 + test "it's the hourly_rate times 8" do + assert FreelancerRates.daily_rate(50) == 400.0 + end + + @tag task_id: 1 + test "it always returns a float" do + assert FreelancerRates.daily_rate(60) === 480.0 + end + + @tag task_id: 1 + test "it does not round" do + assert FreelancerRates.daily_rate(55.1) == 440.8 + end + end + + describe "apply_discount/2" do + @tag task_id: 2 + test "a discount of 10% leaves 90% of the original price" do + assert FreelancerRates.apply_discount(140.0, 10) == 126.0 + end + + @tag task_id: 2 + test "it always returns a float" do + assert FreelancerRates.apply_discount(100, 10) == 90.0 + end + + @tag task_id: 2 + test "it doesn't round" do + assert_in_delta FreelancerRates.apply_discount(111.11, 13.5), 96.11015, 0.000001 + end + end + + describe "monthly_rate/2" do + @tag task_id: 3 + test "it's the daily_rate times 22" do + assert FreelancerRates.monthly_rate(62, 0.0) == 10_912 + end + + @tag task_id: 3 + test "it always returns an integer" do + assert FreelancerRates.monthly_rate(70, 0.0) === 12_320 + end + + @tag task_id: 3 + test "the result is rounded up" do + # 11_052.8 + assert FreelancerRates.monthly_rate(62.8, 0.0) == 11_053 + # 11_475.2 + assert FreelancerRates.monthly_rate(65.2, 0.0) == 11_476 + end + + @tag task_id: 3 + test "gives a discount" do + # 11_792 - 12% * 11_792 = 10_376.96 + assert FreelancerRates.monthly_rate(67, 12.0) == 10_377 + end + end + + describe "days_in_budget/3" do + @tag task_id: 4 + test "it's the budget divided by the daily rate" do + assert FreelancerRates.days_in_budget(1_600, 50, 0.0) == 4 + end + + @tag task_id: 4 + test "it always returns a float" do + assert FreelancerRates.days_in_budget(520, 65, 0.0) === 1.0 + end + + @tag task_id: 4 + test "it rounds down to one decimal place" do + # 10.02273 + assert FreelancerRates.days_in_budget(4_410, 55, 0.0) == 10.0 + # 10.18182 + assert FreelancerRates.days_in_budget(4_480, 55, 0.0) == 10.1 + end + + @tag task_id: 4 + test "it applies the discount" do + # 1.25 + assert FreelancerRates.days_in_budget(480, 60, 20) == 1.2 + end + end +end diff --git a/elixir/freelancer-rates/test/test_helper.exs b/elixir/freelancer-rates/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/freelancer-rates/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/elixir/hello-world/.exercism/config.json b/elixir/hello-world/.exercism/config.json new file mode 100644 index 0000000..dd6d7d9 --- /dev/null +++ b/elixir/hello-world/.exercism/config.json @@ -0,0 +1,34 @@ +{ + "authors": [], + "contributors": [ + "angelikatyborska", + "Cohen-Carlisle", + "dalexj", + "devonestes", + "George-Hudson", + "hanmd82", + "lpil", + "neenjaw", + "parkerl", + "rawkode", + "rubysolo", + "sotojuan", + "Teapane", + "Thrillberg", + "waiting-for-dev" + ], + "files": { + "solution": [ + "lib/hello_world.ex" + ], + "test": [ + "test/hello_world_test.exs" + ], + "example": [ + ".meta/example.ex" + ] + }, + "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", + "source": "This is an exercise to introduce users to using Exercism", + "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" +} diff --git a/elixir/hello-world/.exercism/metadata.json b/elixir/hello-world/.exercism/metadata.json new file mode 100644 index 0000000..29a1a79 --- /dev/null +++ b/elixir/hello-world/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"hello-world","id":"340d85562fad435f8086f4f8ab9cd916","url":"https://exercism.org/tracks/elixir/exercises/hello-world","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/hello-world/.formatter.exs b/elixir/hello-world/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/hello-world/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/hello-world/.gitignore b/elixir/hello-world/.gitignore new file mode 100644 index 0000000..b72d66b --- /dev/null +++ b/elixir/hello-world/.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"). +hello_world-*.tar + diff --git a/elixir/hello-world/HELP.md b/elixir/hello-world/HELP.md new file mode 100644 index 0000000..8435398 --- /dev/null +++ b/elixir/hello-world/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/hello_world.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/hello-world/README.md b/elixir/hello-world/README.md new file mode 100644 index 0000000..b60a684 --- /dev/null +++ b/elixir/hello-world/README.md @@ -0,0 +1,45 @@ +# Hello World + +Welcome to Hello World on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +The classical introductory exercise. +Just say "Hello, World!". + +["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. + +The objectives are simple: + +- Modify the provided code so that it produces the string "Hello, World!". +- Run the test suite and make sure that it succeeds. +- Submit your solution and check it at the website. + +If everything goes well, you will be ready to fetch your first real exercise. + +[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program + +## Source + +### Contributed to by + +- @angelikatyborska +- @Cohen-Carlisle +- @dalexj +- @devonestes +- @George-Hudson +- @hanmd82 +- @lpil +- @neenjaw +- @parkerl +- @rawkode +- @rubysolo +- @sotojuan +- @Teapane +- @Thrillberg +- @waiting-for-dev + +### Based on + +This is an exercise to introduce users to using Exercism - https://en.wikipedia.org/wiki/%22Hello,_world!%22_program \ No newline at end of file diff --git a/elixir/hello-world/lib/hello_world.ex b/elixir/hello-world/lib/hello_world.ex new file mode 100644 index 0000000..3edfef7 --- /dev/null +++ b/elixir/hello-world/lib/hello_world.ex @@ -0,0 +1,9 @@ +defmodule HelloWorld do + @doc """ + Simply returns "Hello, World!" + """ + @spec hello :: String.t() + def hello do + "Hello, World!" + end +end diff --git a/elixir/hello-world/mix.exs b/elixir/hello-world/mix.exs new file mode 100644 index 0000000..2ba5afc --- /dev/null +++ b/elixir/hello-world/mix.exs @@ -0,0 +1,28 @@ +defmodule HelloWorld.MixProject do + use Mix.Project + + def project do + [ + app: :hello_world, + 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/hello-world/test/hello_world_test.exs b/elixir/hello-world/test/hello_world_test.exs new file mode 100644 index 0000000..cfd7952 --- /dev/null +++ b/elixir/hello-world/test/hello_world_test.exs @@ -0,0 +1,7 @@ +defmodule HelloWorldTest do + use ExUnit.Case + + test "says 'Hello, World!'" do + assert HelloWorld.hello() == "Hello, World!" + end +end diff --git a/elixir/hello-world/test/test_helper.exs b/elixir/hello-world/test/test_helper.exs new file mode 100644 index 0000000..35fc5bf --- /dev/null +++ b/elixir/hello-world/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true) diff --git a/elixir/language-list/.exercism/config.json b/elixir/language-list/.exercism/config.json new file mode 100644 index 0000000..c87ffb7 --- /dev/null +++ b/elixir/language-list/.exercism/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "neenjaw" + ], + "contributors": [ + "fireproofsocks" + ], + "files": { + "solution": [ + "lib/language_list.ex" + ], + "test": [ + "test/language_list_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "forked_from": [ + "clojure/tracks-on-tracks-on-tracks" + ], + "blurb": "Learn about lists by keeping track of the programming languages you're currently learning on Exercism." +} diff --git a/elixir/language-list/.exercism/metadata.json b/elixir/language-list/.exercism/metadata.json new file mode 100644 index 0000000..7db0140 --- /dev/null +++ b/elixir/language-list/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"language-list","id":"1fc6fd5b991141188a10438edd755faa","url":"https://exercism.org/tracks/elixir/exercises/language-list","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/language-list/.formatter.exs b/elixir/language-list/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/language-list/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/language-list/.gitignore b/elixir/language-list/.gitignore new file mode 100644 index 0000000..2b994ee --- /dev/null +++ b/elixir/language-list/.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"). +strings-*.tar + diff --git a/elixir/language-list/HELP.md b/elixir/language-list/HELP.md new file mode 100644 index 0000000..5f805ba --- /dev/null +++ b/elixir/language-list/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/language_list.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/language-list/HINTS.md b/elixir/language-list/HINTS.md new file mode 100644 index 0000000..dbfd1bd --- /dev/null +++ b/elixir/language-list/HINTS.md @@ -0,0 +1,35 @@ +# Hints + +## General + +- Use the built-in [(linked) list type][list]. + +## 1. Define a function to return an empty language list + +- The function needs to return `[]`. + +## 2. Define a function to add a language to the list + +- An element can be prepended to a list using `|`. + +## 3. Define a function to remove a language from the list + +- Elixir [provides a function][tl] to return a list with the first item removed. + +## 4. Define a function to return the first item in the list + +- Elixir [provides a function][hd] to get the first item from a list. + +## 5. Define a function to return how many languages are in the list + +- Elixir [provides a function][length] to count the length of a list. + +## 6. Define a function to determine if the list includes a functional language + +- Your function should return a boolean value indicating whether `"Elixir"` is a member of the list. Elixir [provides a function][in] to test list membership. + +[list]: https://elixir-lang.org/getting-started/basic-types.html#linked-lists +[hd]: https://hexdocs.pm/elixir/Kernel.html#hd/1 +[tl]: https://hexdocs.pm/elixir/Kernel.html#tl/1 +[length]: https://hexdocs.pm/elixir/Kernel.html#length/1 +[in]: https://hexdocs.pm/elixir/Kernel.html#in/2 \ No newline at end of file diff --git a/elixir/language-list/README.md b/elixir/language-list/README.md new file mode 100644 index 0000000..386c3b5 --- /dev/null +++ b/elixir/language-list/README.md @@ -0,0 +1,140 @@ +# Language List + +Welcome to Language List 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 + +## Lists + +Lists are built-in to the Elixir language. They are considered a basic type, denoted by square brackets. Lists may be empty or hold any number of items of any type. For example: + +```elixir +empty_list = [] +one_item_list = [1] +two_item_list = [1, 2] +multiple_type_list = [1, :pi, 3.14, "four"] +``` + +Elixir implements lists as a linked list, where each node stores two values: the first item and another list with all the remaining items. The first item in the list is referred to as the _head_ and the remaining list of items is called the _tail_. We can use this notation in code: + +```elixir +# [1] represented in [head | tail] notation +[1 | []] + +# [1, 2, 3] represented in [head | tail] notation +[1 | [2 | [3 | []]]] +``` + +We can use _`[head | tail]`_ notation to prepend elements to a list: + +```elixir +# Suppose +list = [2, 1] + +[3, 2, 1] == [3 | list] +# => true +``` + +There are several functions in the `Kernel` module for working with lists, as well as the whole `List` module. + +```elixir +# Check if 1 is a member of the list +1 in [1, 2, 3, 4] +# => true +``` + +## Instructions + +In this exercise you need to implement some functions to manipulate a list of programming languages. + +## 1. Define a function to return an empty language list + +Define the `new/0` function that takes no arguments and returns an empty list. + +```elixir +LanguageList.new() +# => [] +``` + +## 2. Define a function to add a language to the list + +Define the `add/2` function that takes 2 arguments (a _language list_ and a string literal of a _language_). It should return the resulting list with the new language prepended to the given list. + +```elixir +language_list = LanguageList.new() +# => [] +language_list = LanguageList.add(language_list, "Clojure") +# => ["Clojure"] +language_list = LanguageList.add(language_list, "Haskell") +# => ["Haskell", "Clojure"] +``` + +## 3. Define a function to remove a language from the list + +Define the `remove/1` function that takes 1 argument (a _language list_). It should return the list without the first item. Assume the list will always have at least one item. + +```elixir +language_list = LanguageList.new() +# => [] +language_list = LanguageList.add(language_list, "Clojure") +# => ["Clojure"] +language_list = LanguageList.add(language_list, "Haskell") +# => ["Haskell", "Clojure"] +language_list = LanguageList.remove(language_list) +# => ["Clojure"] +``` + +## 4. Define a function to return the first item in the list + +Define the `first/1` function that takes 1 argument (a _language list_). It should return the first language in the list. Assume the list will always have at least one item. + +```elixir +language_list = LanguageList.new() +# => [] +language_list = LanguageList.add(language_list, "Elm") +# => ["Elm"] +language_list = LanguageList.add(language_list, "Prolog") +# => ["Prolog", "Elm"] +LanguageList.first(language_list) +# => "Prolog" +``` + +## 5. Define a function to return how many languages are in the list + +Define the `count/1` function that takes 1 argument (a _language list_). It should return the number of languages in the list. + +```elixir +language_list = LanguageList.new() +# => [] +language_list = LanguageList.add(language_list, "Elm") +# => ["Elm"] +language_list = LanguageList.add(language_list, "Prolog") +# => ["Prolog", "Elm"] +LanguageList.count(language_list) +# => 2 +``` + +## 6. Define a function to determine if the list includes a functional language + +Define the `functional_list?/1` function which takes 1 argument (a _language list_). It should return a boolean value. It should return true if _"Elixir"_ is one of the languages in the list. + +```elixir +language_list = LanguageList.new() +# => [] +language_list = LanguageList.add(language_list, "Elixir") +# => ["Elixir"] +LanguageList.functional_list?(language_list) +# => true +``` + +## Source + +### Created by + +- @neenjaw + +### Contributed to by + +- @fireproofsocks \ No newline at end of file diff --git a/elixir/language-list/lib/language_list.ex b/elixir/language-list/lib/language_list.ex new file mode 100644 index 0000000..f0e2153 --- /dev/null +++ b/elixir/language-list/lib/language_list.ex @@ -0,0 +1,13 @@ +defmodule LanguageList do + def new(), do: [] + + def add(list, language), do: [language | list] + + def remove(list), do: tl(list) + + def first(list), do: hd(list) + + def count(list), do: length(list) + + def functional_list?(list), do: "Elixir" in list +end diff --git a/elixir/language-list/mix.exs b/elixir/language-list/mix.exs new file mode 100644 index 0000000..2d50d7a --- /dev/null +++ b/elixir/language-list/mix.exs @@ -0,0 +1,28 @@ +defmodule LanguageList.MixProject do + use Mix.Project + + def project do + [ + app: :language_list, + 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/language-list/test/language_list_test.exs b/elixir/language-list/test/language_list_test.exs new file mode 100644 index 0000000..9334b14 --- /dev/null +++ b/elixir/language-list/test/language_list_test.exs @@ -0,0 +1,116 @@ +defmodule LanguageListTest do + use ExUnit.Case + + describe "new/0" do + @tag task_id: 1 + test "new list" do + assert LanguageList.new() == [] + end + end + + describe "add/2" do + @tag task_id: 2 + test "add a language to a list" do + language = "Elixir" + list = [language] + + assert LanguageList.new() |> LanguageList.add(language) == list + end + + @tag task_id: 2 + test "add several languages to a list" do + list = + LanguageList.new() + |> LanguageList.add("Clojure") + |> LanguageList.add("Haskell") + |> LanguageList.add("Erlang") + |> LanguageList.add("F#") + |> LanguageList.add("Elixir") + + assert list == ["Elixir", "F#", "Erlang", "Haskell", "Clojure"] + end + end + + describe "remove/1" do + @tag task_id: 3 + test "add then remove results in empty list" do + list = + LanguageList.new() + |> LanguageList.add("Elixir") + |> LanguageList.remove() + + assert list == [] + end + + @tag task_id: 3 + test "adding two languages, when removed, removes first item" do + list = + LanguageList.new() + |> LanguageList.add("F#") + |> LanguageList.add("Elixir") + |> LanguageList.remove() + + assert list == ["F#"] + end + end + + describe "first/1" do + @tag task_id: 4 + test "add one language, then get the first" do + assert LanguageList.new() |> LanguageList.add("Elixir") |> LanguageList.first() == "Elixir" + end + + @tag task_id: 4 + test "add a few languages, then get the first" do + first = + LanguageList.new() + |> LanguageList.add("Elixir") + |> LanguageList.add("Prolog") + |> LanguageList.add("F#") + |> LanguageList.first() + + assert first == "F#" + end + end + + describe "count/1" do + @tag task_id: 5 + test "the count of a new list is 0" do + assert LanguageList.new() |> LanguageList.count() == 0 + end + + @tag task_id: 5 + test "the count of a one-language list is 1" do + count = + LanguageList.new() + |> LanguageList.add("Elixir") + |> LanguageList.count() + + assert count == 1 + end + + @tag task_id: 5 + test "the count of a multiple-item list is equal to its length" do + count = + LanguageList.new() + |> LanguageList.add("Elixir") + |> LanguageList.add("Prolog") + |> LanguageList.add("F#") + |> LanguageList.count() + + assert count == 3 + end + end + + describe "functional_list?/1" do + @tag task_id: 6 + test "a functional language list" do + assert LanguageList.functional_list?(["Clojure", "Haskell", "Erlang", "F#", "Elixir"]) + end + + @tag task_id: 6 + test "not a functional language list" do + refute LanguageList.functional_list?(["Java", "C", "JavaScript"]) + end + end +end diff --git a/elixir/language-list/test/test_helper.exs b/elixir/language-list/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/language-list/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/elixir/lasagna/.exercism/config.json b/elixir/lasagna/.exercism/config.json new file mode 100644 index 0000000..2672002 --- /dev/null +++ b/elixir/lasagna/.exercism/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "neenjaw" + ], + "contributors": [ + "angelikatyborska" + ], + "files": { + "solution": [ + "lib/lasagna.ex" + ], + "test": [ + "test/lasagna_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "forked_from": [ + "csharp/lucians-luscious-lasagna" + ], + "blurb": "Learn about the basics of Elixir by following a lasagna recipe." +} diff --git a/elixir/lasagna/.exercism/metadata.json b/elixir/lasagna/.exercism/metadata.json new file mode 100644 index 0000000..7bad7e1 --- /dev/null +++ b/elixir/lasagna/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"lasagna","id":"88d20645e744455789c938d6243dee99","url":"https://exercism.org/tracks/elixir/exercises/lasagna","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/lasagna/.formatter.exs b/elixir/lasagna/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/lasagna/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/lasagna/.gitignore b/elixir/lasagna/.gitignore new file mode 100644 index 0000000..682b719 --- /dev/null +++ b/elixir/lasagna/.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"). +basics-*.tar + diff --git a/elixir/lasagna/HELP.md b/elixir/lasagna/HELP.md new file mode 100644 index 0000000..ba730e8 --- /dev/null +++ b/elixir/lasagna/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/lasagna.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/lasagna/HINTS.md b/elixir/lasagna/HINTS.md new file mode 100644 index 0000000..849f600 --- /dev/null +++ b/elixir/lasagna/HINTS.md @@ -0,0 +1,44 @@ +# Hints + +## General + +- An [integer value][integers] can be defined as one or more consecutive digits. +- [String][string] literals are a sequence of characters surrounded by double quotes. + +## 1. Define the expected oven time in minutes + +- You need to define a [function][functions] without any arguments. +- You need to return an [integer][integers]. + +## 2. Calculate the remaining oven time in minutes + +- You need to define a [function][functions] with a single argument. +- You have to [implicitly return an integer][return] from a function. +- The function's argument is an [integer][integers]. +- You can use the [mathematical operator for subtraction][operators] to subtract values. + +## 3. Calculate the preparation time in minutes + +- You need to define a [function][functions] with a single argument. +- You have to [implicitly return an integer][return] from a function. +- The function's argument is an [integer][integers]. +- You can use the [mathematical operator for multiplication][operators] to multiply values. + +## 4. Calculate the total working time in minutes + +- You need to define a [function][functions] with two arguments. +- You have to [implicitly return an integer][return] from a function. +- The function's argument is an [integer][integers]. +- You can invoke one of the other functions you've defined previously. +- You can use the [mathematical operator for addition][operators] to add values. + +## 5. Create a notification that the lasagna is ready + +- You need to define a [function][functions] without any arguments. +- You need to return a [string][string]. + +[functions]: https://elixir-lang.org/getting-started/modules-and-functions.html#named-functions +[return]: https://stackoverflow.com/questions/37445838/returning-values-in-elixir +[operators]: https://elixir-lang.org/getting-started/basic-types.html#basic-arithmetic +[integers]: https://elixir-lang.org/getting-started/basic-types.html +[string]: https://elixir-lang.org/getting-started/basic-types.html#strings \ No newline at end of file diff --git a/elixir/lasagna/README.md b/elixir/lasagna/README.md new file mode 100644 index 0000000..a62b341 --- /dev/null +++ b/elixir/lasagna/README.md @@ -0,0 +1,169 @@ +# Lasagna + +Welcome to Lasagna 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 + +## Basics + +### Variables + +Elixir is a dynamically-typed language, meaning that the type of a variable is only checked at runtime. Using the match `=` operator, we can bind a value of any type to a variable name: + +```elixir +count = 1 # Bound an integer value of 1 +count = 2 # You may re-bind variables + +count = false # You may re-bind any type to a variable + +message = "Success!" # Strings can be created by enclosing characters within double quotes +``` + +### Modules + +Elixir is a [functional-programming language][functional-programming] and requires all named functions to be defined in a _module_. The `defmodule` keyword is used to define a module. All modules are available to all other modules at runtime and do not require an _access modifier_ to make them visible to other parts of the program. A _module_ is analogous to a _class_ in other programming languages. + +```elixir +defmodule Calculator do + # ... +end +``` + +### Named functions + +_Named Functions_ must be defined in a module. The `def` keyword is used to define a _public_ named function. + +Each function can have zero or more arguments. The value of the last expression in a function is always _implicitly returned_. + +```elixir +defmodule Calculator do + def add(x, y) do + x + y + end +end +``` + +Invoking a function is done by specifying its module and function name and passing arguments for each of the function's arguments. + +```elixir +sum = Calculator.add(1, 2) +# => 3 +``` + +The `defp` keyword can be used instead of `def` to define a _private_ function. Private functions can only be used from within the same module that defined them. + +When invoking a function inside the same module where it's defined, the module name can be omitted. + +You may also write short functions using a one-line syntax (note the comma `,` and the colon `:` around the keyword `do`). + +```elixir +defmodule Calculator do + def subtract(x, y) do + private_subtract(x, y) + end + + defp private_subtract(x, y), do: x - y +end + +difference = Calculator.subtract(7, 2) +# => 5 + +difference = Calculator.private_subtract(7, 2) +# => ** (UndefinedFunctionError) function Calculator.private_subtract/2 is undefined or private +# Calculator.private_subtract(7, 2) +``` + +### Arity of functions + +It is common to refer to functions with their _arity_. The _arity_ of a function is the number of arguments it accepts. + +```elixir +# add/3 because this function has three arguments, thus an arity of 3 +def add(x, y, z) do + x + y + z +end +``` + +### Naming conventions + +Module names should use `PascalCase`. A module name must start with an uppercase letter `A-Z` and can contain letters `a-zA-Z`, numbers `0-9`, and underscores `_`. + +Variable and function names should use `snake_case`. A variable or function name must start with a lowercase letter `a-z` or an underscore `_`, can contain letters `a-zA-Z`, numbers `0-9`, and underscores `_`, and might end with a question mark `?` or an exclamation mark `!`. + +### Standard library + +Elixir has a very rich and well-documented standard library. The documentation is available online at [hexdocs.pm/elixir][docs]. Save this link somewhere - you will use it a lot! + +Most built-in data types have a corresponding module that offers functions for working with that data type, e.g. there's the `Integer` module for integers, `String` module for strings, `List` module for lists and so on. + +A notable module is the `Kernel` module. It provides the basic capabilities on top of which the rest of the standard library is built, like arithmetic operators, control-flow macros, and much more. Functions for the `Kernel` module are automatically imported, so you can use them without the `Kernel.` prefix. + +### Code comments + +Comments can be used to leave notes for other developers reading the source code. Single line comments in Elixir are preceded by `#`. + +[functional-programming]: https://en.wikipedia.org/wiki/Functional_programming +[docs]: https://hexdocs.pm/elixir/Kernel.html#content + +## Instructions + +In this exercise you're going to write some code to help you cook a brilliant lasagna from your favorite cooking book. + +You have five tasks, all related to the time spent cooking the lasagna. + +## 1. Define the expected oven time in minutes + +Define the `Lasagna.expected_minutes_in_oven/0` function that does not take any arguments and returns how many minutes the lasagna should be in the oven. According to the cooking book, the expected oven time in minutes is 40: + +```elixir +Lasagna.expected_minutes_in_oven() +# => 40 +``` + +## 2. Calculate the remaining oven time in minutes + +Define the `Lasagna.remaining_minutes_in_oven/1` function that takes the actual minutes the lasagna has been in the oven as an argument and returns how many minutes the lasagna still has to remain in the oven, based on the expected oven time in minutes from the previous task. + +```elixir +Lasagna.remaining_minutes_in_oven(30) +# => 10 +``` + +## 3. Calculate the preparation time in minutes + +Define the `Lasagna.preparation_time_in_minutes/1` function that takes the number of layers you added to the lasagna as an argument and returns how many minutes you spent preparing the lasagna, assuming each layer takes you 2 minutes to prepare. + +```elixir +Lasagna.preparation_time_in_minutes(2) +# => 4 +``` + +## 4. Calculate the total working time in minutes + +Define the `Lasagna.total_time_in_minutes/2` function that takes two arguments: the first argument is the number of layers you added to the lasagna, and the second argument is the number of minutes the lasagna has been in the oven. The function should return how many minutes in total you've worked on cooking the lasagna, which is the sum of the preparation time in minutes, and the time in minutes the lasagna has spent in the oven at the moment. + +```elixir +Lasagna.total_time_in_minutes(3, 20) +# => 26 +``` + +## 5. Create a notification that the lasagna is ready + +Define the `Lasagna.alarm/0` function that does not take any arguments and returns a message indicating that the lasagna is ready to eat. + +```elixir +Lasagna.alarm() +# => "Ding!" +``` + +## Source + +### Created by + +- @neenjaw + +### Contributed to by + +- @angelikatyborska \ No newline at end of file diff --git a/elixir/lasagna/lib/lasagna.ex b/elixir/lasagna/lib/lasagna.ex new file mode 100644 index 0000000..ae3a7ba --- /dev/null +++ b/elixir/lasagna/lib/lasagna.ex @@ -0,0 +1,19 @@ +defmodule Lasagna do + def expected_minutes_in_oven(), do: 40 + + def remaining_minutes_in_oven(minutes) do + expected_minutes_in_oven() - minutes + end + + def preparation_time_in_minutes(layers) do + layers * 2 + end + + def total_time_in_minutes(layers, duration) do + preparation_time_in_minutes(layers) + duration + end + + def alarm() do + "Ding!" + end +end diff --git a/elixir/lasagna/mix.exs b/elixir/lasagna/mix.exs new file mode 100644 index 0000000..0970583 --- /dev/null +++ b/elixir/lasagna/mix.exs @@ -0,0 +1,28 @@ +defmodule Lasagna.MixProject do + use Mix.Project + + def project do + [ + app: :lasagna, + 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/lasagna/test/lasagna_test.exs b/elixir/lasagna/test/lasagna_test.exs new file mode 100644 index 0000000..d42fc85 --- /dev/null +++ b/elixir/lasagna/test/lasagna_test.exs @@ -0,0 +1,44 @@ +defmodule LasagnaTest do + use ExUnit.Case + doctest Lasagna + + @tag task_id: 1 + test "expected minutes in oven" do + assert Lasagna.expected_minutes_in_oven() === 40 + end + + @tag task_id: 2 + test "remaining minutes in oven" do + assert Lasagna.remaining_minutes_in_oven(25) === 15 + end + + @tag task_id: 2 + test "remaining minutes in oven, a few minutes later" do + assert Lasagna.remaining_minutes_in_oven(30) === 10 + end + + @tag task_id: 3 + test "preparation time in minutes for one layer" do + assert Lasagna.preparation_time_in_minutes(1) === 2 + end + + @tag task_id: 3 + test "preparation time in minutes for multiple layers" do + assert Lasagna.preparation_time_in_minutes(4) === 8 + end + + @tag task_id: 4 + test "total time in minutes for one layer" do + assert Lasagna.total_time_in_minutes(1, 30) === 32 + end + + @tag task_id: 4 + test "total time in minutes for multiple layers" do + assert Lasagna.total_time_in_minutes(4, 8) === 16 + end + + @tag task_id: 5 + test "notification message" do + assert Lasagna.alarm() === "Ding!" + end +end diff --git a/elixir/lasagna/test/test_helper.exs b/elixir/lasagna/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/lasagna/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/elixir/log-level/.exercism/config.json b/elixir/log-level/.exercism/config.json new file mode 100644 index 0000000..bd25fce --- /dev/null +++ b/elixir/log-level/.exercism/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/log_level.ex" + ], + "test": [ + "test/log_level_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "forked_from": [ + "csharp/logs-logs-logs" + ], + "icon": "log-levels", + "blurb": "Learn about atoms and the cond conditional expression by aggregating application logs." +} diff --git a/elixir/log-level/.exercism/metadata.json b/elixir/log-level/.exercism/metadata.json new file mode 100644 index 0000000..edd5038 --- /dev/null +++ b/elixir/log-level/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"log-level","id":"51d3c4c93e0b4f5482f5f444b6af0121","url":"https://exercism.org/tracks/elixir/exercises/log-level","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/log-level/.formatter.exs b/elixir/log-level/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/log-level/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/log-level/.gitignore b/elixir/log-level/.gitignore new file mode 100644 index 0000000..cd58551 --- /dev/null +++ b/elixir/log-level/.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"). +cond-*.tar + diff --git a/elixir/log-level/HELP.md b/elixir/log-level/HELP.md new file mode 100644 index 0000000..9d0605e --- /dev/null +++ b/elixir/log-level/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/log_level.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/log-level/HINTS.md b/elixir/log-level/HINTS.md new file mode 100644 index 0000000..be72d0e --- /dev/null +++ b/elixir/log-level/HINTS.md @@ -0,0 +1,20 @@ +# Hints + +## General + +- The [atom type is described here][atom]. + +## 1. Determine the log label + +- You can use the [`cond/1` special form][cond] to elegantly handle the various log codes. +- You can use [equality operators][equality] to compare integers for strict type equality. +- There is a [way to specify a default branch][cond] in a cond expression that can be used to catch unspecified cases. + +## 2. Send an alert + +- You can use the [`cond/1` special form][cond] to decide if an alert should be sent. +- You can use [equality operators][equality] to compare atoms for equality. + +[equality]: https://elixir-lang.org/getting-started/basic-operators.html +[atom]: https://elixir-lang.org/getting-started/basic-types.html#atoms +[cond]: https://elixir-lang.org/getting-started/case-cond-and-if.html#cond \ No newline at end of file diff --git a/elixir/log-level/README.md b/elixir/log-level/README.md new file mode 100644 index 0000000..69c7eba --- /dev/null +++ b/elixir/log-level/README.md @@ -0,0 +1,84 @@ +# Log Level + +Welcome to Log Level 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 + +## Atoms + +Elixir's `atom` type represents a fixed constant. An atom's value is simply its own name. This gives us a type-safe way to interact with data. Atoms can be defined as follows: + +```elixir +# All atoms are preceded with a ':' then follow with alphanumeric snake-cased characters +variable = :an_atom +``` + +_Atoms_ are internally represented by an integer in a lookup table, which are set automatically. It is not possible to change this internal value. + +## Cond + +Often, we want to write code that can branch based on a condition. While there are many ways to do this in Elixir, one of the simplest ways is using `cond/1`. + +At its simplest, `cond` follows the first path that evaluates to `true` with one or more branches: + +```elixir +cond do + x > 10 -> :this_might_be_the_way + y < 7 -> :or_that_might_be_the_way + true -> :this_is_the_default_way +end +``` + +If no path evaluates to `true`, an error is raised by the runtime. + +## Instructions + +You are running a system that consists of a few applications producing many logs. You want to write a small program that will aggregate those logs and give them labels according to their severity level. All applications in your system use the same log codes, but some of the legacy applications don't support all the codes. + +| Log code | Log label | Supported in legacy apps? | +|-----------------------| --------- | ------------------------- | +| 0 | trace | no | +| 1 | debug | yes | +| 2 | info | yes | +| 3 | warning | yes | +| 4 | error | yes | +| 5 | fatal | no | +| other / not supported | unknown | - | + +## 1. Determine the log label + +Implement the `LogLevel.to_label/2` function. It should take an integer code and a boolean flag telling you if the log comes from a legacy app, and return the label of a log line as an atom. + +Log codes not specified in the table should return an _unknown_ label. Log codes specified in the table as not supported in legacy apps should also return an _unknown_ label if the log came from a legacy app. + +```elixir +LogLevel.to_label(0, false) +# => :trace + +LogLevel.to_label(0, true) +# => :unknown +``` + +## 2. Send an alert + +Somebody has to be notified when unexpected things happen. + +Implement the `LogLevel.alert_recipient/2` function to determine to whom the alert needs to be sent. The function should take an integer code and a boolean flag telling you if the log comes from a legacy app, and return the name of the recipient as an atom. + +Use the `LogLevel.to_label/2` function from the previous task. If the log label is _error_ or _fatal_, send the alert to the _ops_ team. If you receive a log with an _unknown_ label from a legacy system, send the alert to the _dev1_ team, other unknown labels should be sent to the _dev2_ team. All other log labels can be safely ignored by returning _false_. + +```elixir +LogLevel.alert_recipient(-1, true) +# => :dev1 + +LogLevel.alert_recipient(0, false) +# => false +``` + +## Source + +### Created by + +- @neenjaw \ No newline at end of file diff --git a/elixir/log-level/lib/log_level.ex b/elixir/log-level/lib/log_level.ex new file mode 100644 index 0000000..57ba291 --- /dev/null +++ b/elixir/log-level/lib/log_level.ex @@ -0,0 +1,47 @@ +defmodule LogLevel do + @doc """ + Log code Log label Supported in legacy apps? + 0 trace no + 1 debug yes + 2 info yes + 3 warning yes + 4 error yes + 5 fatal no + other unknown - + + case {level, legacy?} do + {0, false} -> :trace + {1, _} -> :debug + {2, _} -> :info + {3, _} -> :warning + {4, _} -> :error + {5, false} -> :fatal + _ -> :unknown + end + + """ + def to_label(level, legacy?) do + cond do + level == 0 and not legacy? -> :trace + level == 1 -> :debug + level == 2 -> :info + level == 3 -> :warning + level == 4 -> :error + level == 5 and not legacy? -> :fatal + true -> :unknown + end + end + + @doc """ + If the log label is error or fatal, send the alert to the ops team. If you receive a log with an unknown label from a legacy system, send the alert to the dev1 team, other unknown labels should be sent to the dev2 team. All other log labels can be safely ignored by returning false. + """ + def alert_recipient(level, legacy?) do + label = to_label(level, legacy?) + cond do + label in [:error, :fatal] -> :ops + label == :unknown and legacy? -> :dev1 + label == :unknown -> :dev2 + true -> false + end + end +end diff --git a/elixir/log-level/mix.exs b/elixir/log-level/mix.exs new file mode 100644 index 0000000..46d643d --- /dev/null +++ b/elixir/log-level/mix.exs @@ -0,0 +1,28 @@ +defmodule LogLevel.MixProject do + use Mix.Project + + def project do + [ + app: :log_level, + 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/log-level/test/log_level_test.exs b/elixir/log-level/test/log_level_test.exs new file mode 100644 index 0000000..500833b --- /dev/null +++ b/elixir/log-level/test/log_level_test.exs @@ -0,0 +1,101 @@ +defmodule LogLevelTest do + use ExUnit.Case + + describe "LogLevel.to_label/2" do + @tag task_id: 1 + test "level 0 has label trace only in a non-legacy app" do + assert LogLevel.to_label(0, false) == :trace + assert LogLevel.to_label(0, true) == :unknown + end + + @tag task_id: 1 + test "level 1 has label debug" do + assert LogLevel.to_label(1, false) == :debug + assert LogLevel.to_label(1, true) == :debug + end + + @tag task_id: 1 + test "level 2 has label info" do + assert LogLevel.to_label(2, false) == :info + assert LogLevel.to_label(2, true) == :info + end + + @tag task_id: 1 + test "level 3 has label warning" do + assert LogLevel.to_label(3, false) == :warning + assert LogLevel.to_label(3, true) == :warning + end + + @tag task_id: 1 + test "level 4 has label error" do + assert LogLevel.to_label(4, false) == :error + assert LogLevel.to_label(4, true) == :error + end + + @tag task_id: 1 + test "level 5 has label fatal only in a non-legacy app" do + assert LogLevel.to_label(5, false) == :fatal + assert LogLevel.to_label(5, true) == :unknown + end + + @tag task_id: 1 + test "level 6 has label unknown" do + assert LogLevel.to_label(6, false) == :unknown + assert LogLevel.to_label(6, true) == :unknown + end + + @tag task_id: 1 + test "level -1 has label unknown" do + assert LogLevel.to_label(-1, false) == :unknown + assert LogLevel.to_label(-1, true) == :unknown + end + end + + describe "LogLevel.alert_recipient/2" do + @tag task_id: 2 + test "fatal code sends alert to ops" do + assert LogLevel.alert_recipient(5, false) == :ops + end + + @tag task_id: 2 + test "error code sends alert to ops" do + assert LogLevel.alert_recipient(4, false) == :ops + assert LogLevel.alert_recipient(4, true) == :ops + end + + @tag task_id: 2 + test "unknown code sends alert to dev team 1 for a legacy app" do + assert LogLevel.alert_recipient(6, true) == :dev1 + assert LogLevel.alert_recipient(0, true) == :dev1 + assert LogLevel.alert_recipient(5, true) == :dev1 + end + + @tag task_id: 2 + test "unknown code sends alert to dev team 2" do + assert LogLevel.alert_recipient(6, false) == :dev2 + end + + @tag task_id: 2 + test "trace code does not send alert" do + assert LogLevel.alert_recipient(0, false) == false + end + + @tag task_id: 2 + test "debug code does not send alert" do + assert LogLevel.alert_recipient(1, false) == false + assert LogLevel.alert_recipient(1, true) == false + end + + @tag task_id: 2 + test "info code does not send alert" do + assert LogLevel.alert_recipient(2, false) == false + assert LogLevel.alert_recipient(2, true) == false + end + + @tag task_id: 2 + test "warning code does not send alert" do + assert LogLevel.alert_recipient(3, false) == false + assert LogLevel.alert_recipient(3, true) == false + end + end +end diff --git a/elixir/log-level/test/test_helper.exs b/elixir/log-level/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/log-level/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/elixir/pacman-rules/.exercism/config.json b/elixir/pacman-rules/.exercism/config.json new file mode 100644 index 0000000..d54d621 --- /dev/null +++ b/elixir/pacman-rules/.exercism/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "neenjaw" + ], + "contributors": [ + "Cohen-Carlisle" + ], + "files": { + "solution": [ + "lib/rules.ex" + ], + "test": [ + "test/rules_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "blurb": "Learn about booleans by implementing the rules of the Pac-Man game." +} diff --git a/elixir/pacman-rules/.exercism/metadata.json b/elixir/pacman-rules/.exercism/metadata.json new file mode 100644 index 0000000..dbad49a --- /dev/null +++ b/elixir/pacman-rules/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"pacman-rules","id":"cbbe0dc834ef4a5ebbb5c7cb5c59c76d","url":"https://exercism.org/tracks/elixir/exercises/pacman-rules","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/pacman-rules/.formatter.exs b/elixir/pacman-rules/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/pacman-rules/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/pacman-rules/.gitignore b/elixir/pacman-rules/.gitignore new file mode 100644 index 0000000..5e50eeb --- /dev/null +++ b/elixir/pacman-rules/.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"). +booleans-*.tar + diff --git a/elixir/pacman-rules/HELP.md b/elixir/pacman-rules/HELP.md new file mode 100644 index 0000000..f88d6d6 --- /dev/null +++ b/elixir/pacman-rules/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/rules.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/pacman-rules/HINTS.md b/elixir/pacman-rules/HINTS.md new file mode 100644 index 0000000..db40143 --- /dev/null +++ b/elixir/pacman-rules/HINTS.md @@ -0,0 +1,28 @@ +# Hints + +## General + +- Don't worry about how the arguments are derived, just focus on combining the arguments to return the intended result. + +## 1. Define if pac-man can eat a ghost + +- The function must return a [boolean][boolean] value. +- You can use the [boolean][boolean] operator [`and/2`][boolean-function] to combine the arguments for a result. + +## 2. Define if pac-man scores + +- The function must return a [boolean][boolean] value. +- You can use the [boolean][boolean] operator [`or/2`][boolean-function] to combine the arguments for a result. + +## 3. Define if pac-man loses + +- The function must return a [boolean][boolean] value. +- You can use the [boolean][boolean] operators [`and/2`][boolean-function] and [`not/1`][boolean-function] to combine the arguments for a result. + +## 4. Define if pac-man wins + +- The function must return a [boolean][boolean] value. +- You can use the [boolean][boolean] operators [`and/2`][boolean-function] and [`not/1`][boolean-function] to combine the arguments and results of one of the previously implemented functions. + +[boolean]: https://elixir-lang.org/getting-started/basic-types.html#booleans +[boolean-function]: https://elixir-lang.org/getting-started/basic-operators.html \ No newline at end of file diff --git a/elixir/pacman-rules/README.md b/elixir/pacman-rules/README.md new file mode 100644 index 0000000..1792b16 --- /dev/null +++ b/elixir/pacman-rules/README.md @@ -0,0 +1,91 @@ +# Pacman Rules + +Welcome to Pacman Rules 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 + +## Booleans + +Elixir represents true and false values with the boolean type. There are only two values: `true` and `false`. These values can be bound to a variable: + +```elixir +true_variable = true +false_variable = false +``` + +We can evaluate strict boolean expressions using the `and/2`, `or/2`, and `not/1` operators. + +```elixir +true_variable = true and true +false_variable = true and false + +true_variable = false or true +false_variable = false or false + +true_variable = not false +false_variable = not true +``` + +When writing a function that returns a boolean value, it is idiomatic to end the function name with a `?`. The same convention can be used for variables that store boolean values. + +```elixir +def either_true?(a?, b?) do + a? or b? +end +``` + +## Instructions + +In this exercise, you need to translate some rules from the classic game Pac-Man into Elixir functions. + +You have four rules to translate, all related to the game states. + +> Don't worry about how the arguments are derived, just focus on combining the arguments to return the intended result. + +## 1. Define if Pac-Man eats a ghost + +Define the `Rules.eat_ghost?/2` function that takes two arguments (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a boolean value if Pac-Man is able to eat the ghost. The function should return true only if Pac-Man has a power pellet active and is touching a ghost. + +```elixir +Rules.eat_ghost?(false, true) +# => false +``` + +## 2. Define if Pac-Man scores + +Define the `Rules.score?/2` function that takes two arguments (_if Pac-Man is touching a power pellet_ and _if Pac-Man is touching a dot_) and returns a boolean value if Pac-Man scored. The function should return true if Pac-Man is touching a power pellet or a dot. + +```elixir +Rules.score?(true, true) +# => true +``` + +## 3. Define if Pac-Man loses + +Define the `Rules.lose?/2` function that takes two arguments (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a boolean value if Pac-Man loses. The function should return true if Pac-Man is touching a ghost and does not have a power pellet active. + +```elixir +Rules.lose?(false, true) +# => true +``` + +## 4. Define if Pac-Man wins + +Define the `Rules.win?/3` function that takes three arguments (_if Pac-Man has eaten all of the dots_, _if Pac-Man has a power pellet active_, and _if Pac-Man is touching a ghost_) and returns a boolean value if Pac-Man wins. The function should return true if Pac-Man has eaten all of the dots and has not lost based on the arguments defined in part 3. + +```elixir +Rules.win?(false, true, false) +# => false +``` + +## Source + +### Created by + +- @neenjaw + +### Contributed to by + +- @Cohen-Carlisle \ No newline at end of file diff --git a/elixir/pacman-rules/lib/rules.ex b/elixir/pacman-rules/lib/rules.ex new file mode 100644 index 0000000..658aed6 --- /dev/null +++ b/elixir/pacman-rules/lib/rules.ex @@ -0,0 +1,11 @@ +defmodule Rules do + def eat_ghost?(power_pellet_active?, touching_ghost?), do: power_pellet_active? and touching_ghost? + + def score?(touching_power_pellet?, touching_dot?), do: touching_power_pellet? or touching_dot? + + def lose?(power_pellet_active?, touching_ghost?), do: not power_pellet_active? and touching_ghost? + + def win?(has_eaten_all_dots?, power_pellet_active?, touching_ghost?) do + has_eaten_all_dots? and not lose?(power_pellet_active?, touching_ghost?) + end +end diff --git a/elixir/pacman-rules/mix.exs b/elixir/pacman-rules/mix.exs new file mode 100644 index 0000000..f592497 --- /dev/null +++ b/elixir/pacman-rules/mix.exs @@ -0,0 +1,28 @@ +defmodule Rules.MixProject do + use Mix.Project + + def project do + [ + app: :pacman_rules, + 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/pacman-rules/test/rules_test.exs b/elixir/pacman-rules/test/rules_test.exs new file mode 100644 index 0000000..91a871b --- /dev/null +++ b/elixir/pacman-rules/test/rules_test.exs @@ -0,0 +1,76 @@ +defmodule RulesTest do + use ExUnit.Case + + describe "eat_ghost?/2" do + @tag task_id: 1 + test "ghost gets eaten" do + assert Rules.eat_ghost?(true, true) + end + + @tag task_id: 1 + test "ghost does not get eaten because no power pellet active" do + refute Rules.eat_ghost?(false, true) + end + + @tag task_id: 1 + test "ghost does not get eaten because not touching ghost" do + refute Rules.eat_ghost?(true, false) + end + + @tag task_id: 1 + test "ghost does not get eaten because no power pellet is active, even if not touching ghost" do + refute Rules.eat_ghost?(false, false) + end + end + + describe "score?/2" do + @tag task_id: 2 + test "score when eating dot" do + assert Rules.score?(false, true) + end + + @tag task_id: 2 + test "score when eating power pellet" do + assert Rules.score?(true, false) + end + + @tag task_id: 2 + test "no score when nothing eaten" do + refute Rules.score?(false, false) + end + end + + describe "lose?/2" do + @tag task_id: 3 + test "lose if touching a ghost without a power pellet active" do + assert Rules.lose?(false, true) + end + + @tag task_id: 3 + test "don't lose if touching a ghost with a power pellet active" do + refute Rules.lose?(true, true) + end + + @tag task_id: 3 + test "don't lose if not touching a ghost" do + refute Rules.lose?(true, false) + end + end + + describe "win?/3" do + @tag task_id: 4 + test "win if all dots eaten" do + assert Rules.win?(true, false, false) + end + + @tag task_id: 4 + test "don't win if all dots eaten, but touching a ghost" do + refute Rules.win?(true, false, true) + end + + @tag task_id: 4 + test "win if all dots eaten and touching a ghost with a power pellet active" do + assert Rules.win?(true, true, true) + end + end +end diff --git a/elixir/pacman-rules/test/test_helper.exs b/elixir/pacman-rules/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/pacman-rules/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/elixir/secrets/.exercism/config.json b/elixir/secrets/.exercism/config.json new file mode 100644 index 0000000..fc9f708 --- /dev/null +++ b/elixir/secrets/.exercism/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/secrets.ex" + ], + "test": [ + "test/secrets_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "blurb": "Learn about bit manipulation and anonymous functions by writing the software for an encryption device." +} diff --git a/elixir/secrets/.exercism/metadata.json b/elixir/secrets/.exercism/metadata.json new file mode 100644 index 0000000..324ee14 --- /dev/null +++ b/elixir/secrets/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"secrets","id":"baa7ad210ba049e6be7ec6f9c548a06a","url":"https://exercism.org/tracks/elixir/exercises/secrets","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/secrets/.formatter.exs b/elixir/secrets/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/secrets/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/secrets/.gitignore b/elixir/secrets/.gitignore new file mode 100644 index 0000000..420e50b --- /dev/null +++ b/elixir/secrets/.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"). +anonymous_functions-*.tar + diff --git a/elixir/secrets/HELP.md b/elixir/secrets/HELP.md new file mode 100644 index 0000000..4389c29 --- /dev/null +++ b/elixir/secrets/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/secrets.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/secrets/HINTS.md b/elixir/secrets/HINTS.md new file mode 100644 index 0000000..107555b --- /dev/null +++ b/elixir/secrets/HINTS.md @@ -0,0 +1,47 @@ +# Hints + +## General + +- Make use of [anonymous functions][anon-fns]. +- Use a [closure][closure] to reference the variable from the outer scope. + +## 1. Create an adder + +- Return an anonymous function which adds the argument from the anonymous function to the argument passed in to `Secret.secret_add/1`. + +## 2. Create a subtractor + +- Return an anonymous function which subtracts the argument passed in to `Secret.secret_subtract/1` from the argument from the anonymous function. + +## 3. Create a multiplier + +- Return an anonymous function which multiplies the argument from the anonymous function to the argument passed in to `Secret.secret_multiply/1`. + +## 4. Create a divider + +- Return an anonymous function which divides the argument from the anonymous function by the argument passed in to `Secret.secret_divide/1`. +- Make use of [integer division][div]. + +## 5. Create an "and"-er + +- Return an anonymous function which performs a [bitwise _and_][bitwise-wiki] operation using the argument passed in to the anonymous function and the argument passed in to `Secret.secret_and/1` +- Functions in the [Bitwise module][bitwise-hexdocs] may be of use. +- If you are running Elixir version 1.9 or lower, you will need to call `require Bitwise` at the beginning of the module definition to be able to use the _Bitwise_ module. + +## 6. Create an "xor"-er + +- Return an anonymous function which performs a [bitwise _xor_][bitwise-wiki] operation using the argument passed in to the anonymous function and the argument passed in to `Secret.secret_xor/1` +- Functions in the [Bitwise module][bitwise-hexdocs] may be of use. +- If you are running Elixir version 1.9 or lower, you will need to call `require Bitwise` at the beginning of the module definition to be able to use the _Bitwise_ module. + +## 7. Create a function combiner + +- Return an anonymous function which [composes the functions][fn-composition] passed in to `Secret.secret_combine/2`. +- Use a `.` before `()` when calling an anonymous function. + +[anon-fns]: https://elixir-lang.org/getting-started/basic-types.html#anonymous-functions +[bitwise-hexdocs]: https://hexdocs.pm/elixir/Bitwise.html +[bitwise-wiki]: https://en.wikipedia.org/wiki/Bitwise_operation +[closure]: https://en.wikipedia.org/wiki/Closure_(computer_programming) +[div]: https://hexdocs.pm/elixir/Kernel.html#div/2 +[fn-composition]: https://en.wikipedia.org/wiki/Function_composition_(computer_science) \ No newline at end of file diff --git a/elixir/secrets/README.md b/elixir/secrets/README.md new file mode 100644 index 0000000..04efb9f --- /dev/null +++ b/elixir/secrets/README.md @@ -0,0 +1,155 @@ +# Secrets + +Welcome to Secrets 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 + +## Anonymous Functions + +Functions are treated as first class citizens in Elixir. This means that: + +- Named and anonymous functions can be assigned to variables. +- Named and anonymous functions can be passed around like data as arguments and return values. +- Anonymous functions can be created dynamically. + +Anonymous functions, in contrast to named functions, don't have a static reference available to them, they are only available if they are assigned to a variable or immediately invoked. + +We might use anonymous functions to: + +- Hide data using lexical scope (also known as a closure). +- Dynamically create functions at run-time. + +Anonymous functions start with the reserved word `fn`, the arguments are separated from the body of the function with the `->` token, and they are finished with an `end`. As with named functions, the last expression in the function is _implicitly returned_ to the calling function. + +To invoke a function reference, you must use a `.` between the reference variable and the list of arguments: + +```elixir +function_variable = fn param -> + param + 1 +end + +function_variable.(1) +# => 2 +``` + +You can even use short hand capture notation to make this more concise: + +```elixir +variable = &(&1 + 1) + +variable.(1) +# => 2 +``` + +## Bit Manipulation + +Elixir supports many functions for working with bits found in the _Bitwise module_. + +- `band/2`: bitwise AND +- `bsl/2`: bitwise SHIFT LEFT +- `bsr/2`: bitwise SHIFT RIGHT +- `bxor/2`: bitwise XOR +- `bor/2`: bitwise OR +- `bnot/1`: bitwise NOT + +Here is an example how to use a bitwise function: + +```elixir +Bitwise.bsl(1, 3) +# => 8 +``` + +All bitwise functions only work on integers. + +If you are running Elixir version 1.9 or lower, you will need to call `require Bitwise` at the beginning of the module definition to be able to use the _Bitwise_ module. + +## Instructions + +In this exercise, you've been tasked with writing the software for an encryption device that works by performing transformations on data. You need a way to flexibly create complicated functions by combining simpler functions together. + +For each task, return an anonymous function that can be invoked from the calling scope. + +All functions should expect integer arguments. Integers are also suitable for performing bitwise operations in Elixir. + +## 1. Create an adder + +Implement `Secrets.secret_add/1`. It should return a function which takes one argument and adds to it the argument passed in to `secret_add`. + +```elixir +adder = Secrets.secret_add(2) +adder.(2) +# => 4 +``` + +## 2. Create a subtractor + +Implement `Secrets.secret_subtract/1`. It should return a function which takes one argument and subtracts the secret passed in to `secret_subtract` from that argument. + +```elixir +subtractor = Secrets.secret_subtract(2) +subtractor.(3) +# => 1 +``` + +## 3. Create a multiplier + +Implement `Secrets.secret_multiply/1`. It should return a function which takes one argument and multiplies it by the secret passed in to `secret_multiply`. + +```elixir +multiplier = Secrets.secret_multiply(7) +multiplier.(3) +# => 21 +``` + +## 4. Create a divider + +Implement `Secrets.secret_divide/1`. It should return a function which takes one argument and divides it by the secret passed in to `secret_divide`. + +```elixir +divider = Secrets.secret_divide(3) +divider.(32) +# => 10 +``` + +Make use of integer division so the output is compatible with the other functions' expected input. + +## 5. Create an "and"-er + +Implement `Secrets.secret_and/1`. It should return a function which takes one argument and performs a bitwise _and_ operation on it and the secret passed in to `secret_and`. + +```elixir +ander = Secrets.secret_and(1) +ander.(2) +# => 0 +``` + +## 6. Create an "xor"-er + +Implement `Secrets.secret_xor/1`. It should return a function which takes one argument and performs a bitwise _xor_ operation on it and the secret passed in to `secret_xor`. + +```elixir +xorer = Secrets.secret_xor(1) +xorer.(3) +# => 2 +``` + +## 7. Create a function combiner + +Implement `Secrets.secret_combine/2`. It should return a function which takes one argument and applies to it the two functions passed in to `secret_combine` in order. + +```elixir +multiply = Secrets.secret_multiply(7) +divide = Secrets.secret_divide(3) +combined = Secrets.secret_combine(multiply, divide) + +combined.(6) +# => 14 +``` + +## Source + +### Created by + +- @neenjaw \ No newline at end of file diff --git a/elixir/secrets/lib/secrets.ex b/elixir/secrets/lib/secrets.ex new file mode 100644 index 0000000..89e95eb --- /dev/null +++ b/elixir/secrets/lib/secrets.ex @@ -0,0 +1,29 @@ +defmodule Secrets do + def secret_add(secret) do + &(&1 + secret) + end + + def secret_subtract(secret) do + &(&1 - secret) + end + + def secret_multiply(secret) do + &(&1 * secret) + end + + def secret_divide(secret) do + &(div(&1, secret)) + end + + def secret_and(secret) do + &(Bitwise.band(secret, &1)) + end + + def secret_xor(secret) do + &(Bitwise.bxor(secret, &1)) + end + + def secret_combine(secret_function1, secret_function2) do + &(secret_function2.(secret_function1.(&1))) + end +end diff --git a/elixir/secrets/mix.exs b/elixir/secrets/mix.exs new file mode 100644 index 0000000..ebcba98 --- /dev/null +++ b/elixir/secrets/mix.exs @@ -0,0 +1,28 @@ +defmodule Secrets.MixProject do + use Mix.Project + + def project do + [ + app: :secrets, + 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/secrets/test/secrets_test.exs b/elixir/secrets/test/secrets_test.exs new file mode 100644 index 0000000..04b51fc --- /dev/null +++ b/elixir/secrets/test/secrets_test.exs @@ -0,0 +1,179 @@ +defmodule SecretsTest do + use ExUnit.Case + + describe "secret_add" do + @tag task_id: 1 + test "add 3" do + add = Secrets.secret_add(3) + assert add.(3) === 6 + end + + @tag task_id: 1 + test "add 6" do + add = Secrets.secret_add(6) + assert add.(9) === 15 + end + end + + describe "secret_subtract" do + @tag task_id: 2 + test "subtract 3" do + subtract = Secrets.secret_subtract(3) + assert subtract.(6) === 3 + end + + @tag task_id: 2 + test "subtract 6" do + subtract = Secrets.secret_subtract(6) + assert subtract.(3) === -3 + end + end + + describe "secret_multiply" do + @tag task_id: 3 + test "multiply by 3" do + multiply = Secrets.secret_multiply(3) + assert multiply.(6) === 18 + end + + @tag task_id: 3 + test "multiply by 6" do + multiply = Secrets.secret_multiply(6) + assert multiply.(7) === 42 + end + end + + describe "secret_divide" do + @tag task_id: 4 + test "divide by 3" do + divide = Secrets.secret_divide(3) + assert divide.(6) === 2 + end + + @tag task_id: 4 + test "divide by 6" do + divide = Secrets.secret_divide(6) + assert divide.(7) === 1 + end + end + + describe "secret_and" do + @tag task_id: 5 + test "2 and 1" do + ander = Secrets.secret_and(1) + assert ander.(2) === 0 + end + + @tag task_id: 5 + test "7 and 7" do + ander = Secrets.secret_and(7) + assert ander.(7) === 7 + end + end + + describe "secret_xor" do + @tag task_id: 6 + test "2 xor 1" do + xorer = Secrets.secret_xor(1) + assert xorer.(2) === 3 + end + + @tag task_id: 6 + test "7 xor 7" do + xorer = Secrets.secret_xor(7) + assert xorer.(7) === 0 + end + end + + describe "secret_combine" do + @tag task_id: 7 + test "5 add 10 then subtract 5" do + f = Secrets.secret_add(10) + g = Secrets.secret_subtract(5) + h = Secrets.secret_combine(f, g) + + assert h.(5) === 10 + end + + @tag task_id: 7 + test "100 multiply by 2 then subtract 20" do + f = Secrets.secret_multiply(2) + g = Secrets.secret_subtract(20) + h = Secrets.secret_combine(f, g) + + assert h.(100) === 180 + end + + @tag task_id: 7 + test "100 divide by 10 then add 10" do + f = Secrets.secret_divide(10) + g = Secrets.secret_add(10) + h = Secrets.secret_combine(f, g) + + assert h.(100) === 20 + end + + @tag task_id: 7 + test "32 divide by 3 then add 5" do + f = Secrets.secret_divide(3) + g = Secrets.secret_add(5) + h = Secrets.secret_combine(f, g) + + assert h.(32) === 15 + end + + @tag task_id: 7 + test "7 and 3 then and 5" do + f = Secrets.secret_and(3) + g = Secrets.secret_and(5) + h = Secrets.secret_combine(f, g) + + assert h.(7) === 1 + end + + @tag task_id: 7 + test "7 and 7 then and 7" do + f = Secrets.secret_and(7) + g = Secrets.secret_and(7) + h = Secrets.secret_combine(f, g) + + assert h.(7) === 7 + end + + @tag task_id: 7 + test "4 xor 1 then xor 2" do + f = Secrets.secret_xor(1) + g = Secrets.secret_xor(2) + h = Secrets.secret_combine(f, g) + + assert h.(4) === 7 + end + + @tag task_id: 7 + test "7 xor 7 then xor 7" do + f = Secrets.secret_xor(7) + g = Secrets.secret_xor(7) + h = Secrets.secret_combine(f, g) + + assert h.(7) === 7 + end + + @tag task_id: 7 + test "4 add 3 then xor 7" do + f = Secrets.secret_add(3) + g = Secrets.secret_xor(7) + h = Secrets.secret_combine(f, g) + + assert h.(4) === 0 + end + + @tag task_id: 7 + test "81 divide by 9 then and 7" do + f = Secrets.secret_divide(9) + g = Secrets.secret_and(7) + h = Secrets.secret_combine(f, g) + + assert h.(81) === 1 + end + end +end diff --git a/elixir/secrets/test/test_helper.exs b/elixir/secrets/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/secrets/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0)