diff --git a/elixir/wine-cellar/.exercism/config.json b/elixir/wine-cellar/.exercism/config.json new file mode 100644 index 0000000..ec011da --- /dev/null +++ b/elixir/wine-cellar/.exercism/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "angelikatyborska" + ], + "contributors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/wine_cellar.ex" + ], + "test": [ + "test/wine_cellar_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10", + "blurb": "Learn about keyword lists by providing wine suggestions to the customers in your restaurant." +} diff --git a/elixir/wine-cellar/.exercism/metadata.json b/elixir/wine-cellar/.exercism/metadata.json new file mode 100644 index 0000000..86d3979 --- /dev/null +++ b/elixir/wine-cellar/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"wine-cellar","id":"de50f41fb5b4408e90f193a9d87670f6","url":"https://exercism.org/tracks/elixir/exercises/wine-cellar","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/wine-cellar/.formatter.exs b/elixir/wine-cellar/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/wine-cellar/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/wine-cellar/.gitignore b/elixir/wine-cellar/.gitignore new file mode 100644 index 0000000..cb54963 --- /dev/null +++ b/elixir/wine-cellar/.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"). +nil-*.tar + diff --git a/elixir/wine-cellar/HELP.md b/elixir/wine-cellar/HELP.md new file mode 100644 index 0000000..b3b3be6 --- /dev/null +++ b/elixir/wine-cellar/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/wine_cellar.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/wine-cellar/HINTS.md b/elixir/wine-cellar/HINTS.md new file mode 100644 index 0000000..ad5c8c6 --- /dev/null +++ b/elixir/wine-cellar/HINTS.md @@ -0,0 +1,33 @@ +# Hints + +## General + +- Read about keyword lists in the official [Getting Started guide][getting-started-keyword-lists]. +- Read about keyword lists on [elixirschool.com][elixir-school-keyword-lists]. +- Take a look at the [documentation of the `Keyword` module][keyword]. + +## 1. Explain wine colors + +- You need to write the information given in the table as a keyword list. +- Take a look at some [examples][keyword-examples] of how to define a keyword list. + +## 2. Get all wines of a given color + +- There is a [built-in function][keyword-get-values] for getting the list of all values for a given key. + +## 3. Get all wines of a given color bottled in a given year + +- There is a [built-in function][keyword-get] for getting a single value for a given key. +- You do not need to implement the filtering of bottles on your own. You just need to conditionally use the already-implemented `filter_by_year/2` function. + +## 4. Get all wines of a given color bottled in a given country + +- There is a [built-in function][keyword-get] for getting a single value for a given key. +- You do not need to implement the filtering of bottles on your own. You just need to conditionally use the already-implemented `filter_by_country/2` function. + +[getting-started-keyword-lists]: https://elixir-lang.org/getting-started/keywords-and-maps.html#keyword-lists +[elixir-school-keyword-lists]: https://elixirschool.com/en/lessons/basics/collections/#keyword-lists +[keyword]: https://hexdocs.pm/elixir/Keyword.html +[keyword-get-values]: https://hexdocs.pm/elixir/Keyword.html#get_values/2 +[keyword-get]: https://hexdocs.pm/elixir/Keyword.html#get/3 +[keyword-examples]: https://hexdocs.pm/elixir/Keyword.html#module-examples \ No newline at end of file diff --git a/elixir/wine-cellar/README.md b/elixir/wine-cellar/README.md new file mode 100644 index 0000000..424f35d --- /dev/null +++ b/elixir/wine-cellar/README.md @@ -0,0 +1,133 @@ +# Wine Cellar + +Welcome to Wine Cellar 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 + +## Keyword Lists + +Keyword lists are a key-value data structure. + +```elixir +[month: "April", year: 2018] +``` + +Keyword lists are lists of `{key, value}` [tuples][exercism-tuples], and can also be written as such, but the shorter syntax is more widely used. + +```elixir +[month: "April"] == [{:month, "April"}] +# => true +``` + +Keys in a keyword list must be [atoms][exercism-atoms], but the values can be anything. Each key can be used more than once. The key-value pairs in a keyword list are ordered. + +You can work with keyword lists using the same approaches as for lists, or you can use the `Keyword` module. + +[exercism-tuples]: https://exercism.org/tracks/elixir/concepts/tuples +[exercism-atoms]: https://exercism.org/tracks/elixir/concepts/atoms + +## Instructions + +You are the manager of a fancy restaurant that has a sizable wine cellar. A lot of your customers are demanding wine enthusiasts. Finding the right bottle of wine for a particular customer is not an easy task. + +As a tech-savvy restaurant owner, you decided to speed up the wine selection process by writing an app that will let guests filter your wines by their preferences. + +## 1. Explain wine colors + +On the welcome screen of your app, you want to display a short explanation of each wine color. + +Implement the `WineCellar.explain_colors/0` function. It takes no arguments and returns a keyword list with wine colors as keys and explanations as values. + +| Color | Explanation | +| -------- | -------------------------------------------------------------------------- | +| `:white` | Fermented without skin contact. | +| `:red` | Fermented with skin contact using dark-colored grapes. | +| `:rose` | Fermented with some skin contact, but not enough to qualify as a red wine. | + +## 2. Get all wines of a given color + +A bottle of wine is represented as a 3-tuple of grape variety, year, and country of origin. The wines are stored by wine color in a keyword list. + +```elixir +[ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Pinot grigio", 2017, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + rose: {"Dornfelder", 2018, "Germany"} +] +``` + +Implement the `WineCellar.filter/3` function. It should take a keyword list of wines, a color atom and a keyword list of options, with a default value of `[]`. The function should return a list of wines of a given color. + +```elixir +WineCellar.filter( + [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Pinot grigio", 2017, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + rose: {"Dornfelder", 2018, "Germany"} + ], + :white +) +# => [ +# {"Chardonnay", 2015, "Italy"}, +# {"Pinot grigio", 2017, "Germany"} +# ] +``` + +## 3. Get all wines of a given color bottled in a given year + +Extend the `WineCellar.filter/3` function. When given a `:year` option, the function should return a list of wines of a given color from a given year. + +Use the already-implemented `WineCellar.filter_by_year/2` function. It takes a list of wines and a year as arguments and returns a list of wines from a given year. + +```elixir +WineCellar.filter( + [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Pinot grigio", 2017, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + rose: {"Dornfelder", 2018, "Germany"} + ], + :white, + year: 2015 +) +# => [ +# {"Chardonnay", 2015, "Italy"} +# ] +``` + +## 4. Get all wines of a given color bottled in a given country + +Extend the `WineCellar.filter/3` function. When given a `:country` option, the function should return a list of wines of a given color from a given country. + +Use the already-implemented `WineCellar.filter_by_country/2` function. It takes a list of wines and a country as arguments and returns a list of wines from a given country. + +Make sure that the function works when given both the `:year` and the `:country` option, in any order. + +```elixir +WineCellar.filter( + [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Pinot grigio", 2017, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + rose: {"Dornfelder", 2018, "Germany"} + ], + :white, + year: 2015, + country: "Germany" +) +# => [] +``` + +## Source + +### Created by + +- @angelikatyborska + +### Contributed to by + +- @neenjaw \ No newline at end of file diff --git a/elixir/wine-cellar/lib/wine_cellar.ex b/elixir/wine-cellar/lib/wine_cellar.ex new file mode 100644 index 0000000..dc62008 --- /dev/null +++ b/elixir/wine-cellar/lib/wine_cellar.ex @@ -0,0 +1,49 @@ +defmodule WineCellar do + @colors [ + white: "Fermented without skin contact.", + red: "Fermented with skin contact using dark-colored grapes.", + rose: "Fermented with some skin contact, but not enough to qualify as a red wine." + ] + + # @cellar [ + # white: {"Chardonnay", 2015, "Italy"}, + # white: {"Pinot grigio", 2017, "Germany"}, + # red: {"Pinot noir", 2016, "France"}, + # rose: {"Dornfelder", 2018, "Germany"} + # ] + + def explain_colors(), do: @colors + + def filter(cellar, color, opts \\ []) do + cellar + |> Keyword.get_values(color) + |> filter_by_year(opts[:year]) + |> filter_by_country(opts[:country]) + end + + # The functions below do not need to be modified. + + defp filter_by_year(wines, year) + defp filter_by_year(wines, nil), do: wines + defp filter_by_year([], _year), do: [] + + defp filter_by_year([{_, year, _} = wine | tail], year) do + [wine | filter_by_year(tail, year)] + end + + defp filter_by_year([{_, _, _} | tail], year) do + filter_by_year(tail, year) + end + + defp filter_by_country(wines, country) + defp filter_by_country(wines, nil), do: wines + defp filter_by_country([], _country), do: [] + + defp filter_by_country([{_, _, country} = wine | tail], country) do + [wine | filter_by_country(tail, country)] + end + + defp filter_by_country([{_, _, _} | tail], country) do + filter_by_country(tail, country) + end +end diff --git a/elixir/wine-cellar/mix.exs b/elixir/wine-cellar/mix.exs new file mode 100644 index 0000000..043ecc9 --- /dev/null +++ b/elixir/wine-cellar/mix.exs @@ -0,0 +1,28 @@ +defmodule WineCellar.MixProject do + use Mix.Project + + def project do + [ + app: :wine_cellar, + 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/wine-cellar/test/test_helper.exs b/elixir/wine-cellar/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/elixir/wine-cellar/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/elixir/wine-cellar/test/wine_cellar_test.exs b/elixir/wine-cellar/test/wine_cellar_test.exs new file mode 100644 index 0000000..3cf5fba --- /dev/null +++ b/elixir/wine-cellar/test/wine_cellar_test.exs @@ -0,0 +1,118 @@ +defmodule WineCellarTest do + use ExUnit.Case + + describe "explain_colors/0" do + @tag task_id: 1 + test "returns a keyword list" do + assert WineCellar.explain_colors() == [ + white: "Fermented without skin contact.", + red: "Fermented with skin contact using dark-colored grapes.", + rose: "Fermented with some skin contact, but not enough to qualify as a red wine." + ] + end + end + + describe "filter/3" do + @tag task_id: 2 + test "works for empty cellar" do + assert WineCellar.filter([], :rose) == [] + end + + @tag task_id: 2 + test "returns all wines of a chosen color" do + cellar = [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Chardonnay", 2014, "France"}, + rose: {"Dornfelder", 2018, "Germany"}, + red: {"Merlot", 2015, "France"}, + white: {"Riesling ", 2017, "Germany"}, + white: {"Pinot grigio", 2015, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + red: {"Pinot noir", 2013, "Italy"} + ] + + assert WineCellar.filter(cellar, :white) == [ + {"Chardonnay", 2015, "Italy"}, + {"Chardonnay", 2014, "France"}, + {"Riesling ", 2017, "Germany"}, + {"Pinot grigio", 2015, "Germany"} + ] + + assert WineCellar.filter(cellar, :rose) == [{"Dornfelder", 2018, "Germany"}] + end + + @tag task_id: 3 + test "filters wines of chosen color by year" do + cellar = [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Chardonnay", 2014, "France"}, + rose: {"Dornfelder", 2018, "Germany"}, + red: {"Merlot", 2015, "France"}, + white: {"Riesling ", 2017, "Germany"}, + white: {"Pinot grigio", 2015, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + red: {"Pinot noir", 2013, "Italy"} + ] + + assert WineCellar.filter(cellar, :white, year: 2015) == [ + {"Chardonnay", 2015, "Italy"}, + {"Pinot grigio", 2015, "Germany"} + ] + end + + @tag task_id: 4 + test "filters wines of chosen color by country" do + cellar = [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Chardonnay", 2014, "France"}, + rose: {"Dornfelder", 2018, "Germany"}, + red: {"Merlot", 2015, "France"}, + white: {"Riesling ", 2017, "Germany"}, + white: {"Pinot grigio", 2015, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + red: {"Pinot noir", 2013, "Italy"} + ] + + assert WineCellar.filter(cellar, :red, country: "France") == [ + {"Merlot", 2015, "France"}, + {"Pinot noir", 2016, "France"} + ] + end + + @tag task_id: 4 + test "filters wines of chosen color by year and country" do + cellar = [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Chardonnay", 2014, "France"}, + rose: {"Dornfelder", 2018, "Germany"}, + red: {"Merlot", 2015, "France"}, + white: {"Riesling ", 2017, "Germany"}, + white: {"Pinot grigio", 2015, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + red: {"Pinot noir", 2013, "Italy"} + ] + + assert WineCellar.filter(cellar, :white, year: 2015, country: "Germany") == [ + {"Pinot grigio", 2015, "Germany"} + ] + end + + @tag task_id: 4 + test "filters wines of chosen color by country and year" do + cellar = [ + white: {"Chardonnay", 2015, "Italy"}, + white: {"Chardonnay", 2014, "France"}, + rose: {"Dornfelder", 2018, "Germany"}, + red: {"Merlot", 2015, "France"}, + white: {"Riesling ", 2017, "Germany"}, + white: {"Pinot grigio", 2015, "Germany"}, + red: {"Pinot noir", 2016, "France"}, + red: {"Pinot noir", 2013, "Italy"} + ] + + assert WineCellar.filter(cellar, :red, country: "France", year: 2015) == [ + {"Merlot", 2015, "France"} + ] + end + end +end