diff --git a/elixir/need-for-speed/.exercism/config.json b/elixir/need-for-speed/.exercism/config.json new file mode 100644 index 0000000..db08d82 --- /dev/null +++ b/elixir/need-for-speed/.exercism/config.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "angelikatyborska" + ], + "contributors": [ + "neenjaw" + ], + "files": { + "solution": [ + "lib/need_for_speed.ex" + ], + "test": [ + "test/need_for_speed_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ], + "editor": [ + "lib/need_for_speed/race.ex", + "lib/need_for_speed/remote_control_car.ex" + ] + }, + "language_versions": ">=1.10", + "forked_from": [ + "csharp/need-for-speed" + ], + "blurb": "Learn about reusing code with alias and import by racing remote controlled cars." +} diff --git a/elixir/need-for-speed/.exercism/metadata.json b/elixir/need-for-speed/.exercism/metadata.json new file mode 100644 index 0000000..609bf5c --- /dev/null +++ b/elixir/need-for-speed/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"need-for-speed","id":"082ffb90c57f4558b617e4fd2b89ca7a","url":"https://exercism.org/tracks/elixir/exercises/need-for-speed","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/need-for-speed/.formatter.exs b/elixir/need-for-speed/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/need-for-speed/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/need-for-speed/.gitignore b/elixir/need-for-speed/.gitignore new file mode 100644 index 0000000..b97fe0c --- /dev/null +++ b/elixir/need-for-speed/.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"). +structs-*.tar + diff --git a/elixir/need-for-speed/HELP.md b/elixir/need-for-speed/HELP.md new file mode 100644 index 0000000..5019fcd --- /dev/null +++ b/elixir/need-for-speed/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/need_for_speed.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/need-for-speed/HINTS.md b/elixir/need-for-speed/HINTS.md new file mode 100644 index 0000000..bae21c8 --- /dev/null +++ b/elixir/need-for-speed/HINTS.md @@ -0,0 +1,31 @@ +# Hints + +## General + +- Read about [`alias` and `import`][getting-started] in the Getting Started guide. +- Read about [module composition][elixir-school-module-composition] on elixirschool.com. + +## 1. Fix compilation error `Race.__struct__/0 is undefined` + +- An alias can be created with the [`alias`][alias] special form. + +## 2. Fix compilation error `Car.__struct__/0 is undefined` + +- An alias can be created with the [`alias`][alias] special form. +- The `alias` special form accepts an `:as` option. + +## 3. Fix compilation error `undefined function puts/1` + +- Functions can be imported with the [`import`][import] special form. +- The [`import`][import] special form accepts an `:only` option. Its value should be a keyword list with function name as keys and function arities as values. + +## 4. Fix compilation error `undefined function default_color/0` + +- Functions can be imported with the [`import`][import] special form. +- The [`import`][import] special form accepts an `:except` option. Its value should be a keyword list with function name as keys and function arities as values. + + +[alias]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#alias/2 +[import]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#import/2 +[elixir-school-module-composition]: https://elixirschool.com/en/lessons/basics/modules/#composition +[getting-started]: https://hexdocs.pm/elixir/alias-require-and-import.html \ No newline at end of file diff --git a/elixir/need-for-speed/README.md b/elixir/need-for-speed/README.md new file mode 100644 index 0000000..c077a4b --- /dev/null +++ b/elixir/need-for-speed/README.md @@ -0,0 +1,71 @@ +# Need For Speed + +Welcome to Need For Speed 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 + +## Alias + +To share code between different Elixir modules within the same project, you need to reference the outside module by its full name. But what if that name is too long or confusing? + +The special form `alias` allows you to shorten or change the name by which you reference an outside module. When used without any arguments, it trims down the module name to its last segment, e.g. `MyApp.Logger.Settings` becomes `Settings`. A custom name can be specified with the `:as` option. + +Usually aliases are added at the beginning of the module definition. + +```elixir +defmodule Square do + alias Integer, as: I + + def area(a), do: I.pow(a, 2) +end +``` + +## Import + +The special form `import` allows you to use functions from an outside module without using the module's name. + +Importing a whole outside module might create conflicts with existing local functions. To avoid this, two options are available: `:except` and `:only`. Both expect a keyword list, where the key is the function name, and the value is the function's arity. + +Usually imports are added at the beginning of the module definition. + +```elixir +defmodule Square do + import Integer, only: [pow: 2] + + def area(a), do: pow(a, 2) +end +``` + +## Instructions + +That remote controlled car that you bought recently has turned into a whole new hobby. You have been organizing remote control car races. + +You were almost finished writing a program that would allow to run race simulations when your cat jumped at your keyboard and deleted a few lines of code. Now your program doesn't compile anymore... + +## 1. Fix compilation error `Race.__struct__/0 is undefined` + +Add an alias so that the module `NeedForSpeed.Race` can be referenced by the shorter name `Race`. + +## 2. Fix compilation error `Car.__struct__/0 is undefined` + +Add an alias so that the module `NeedForSpeed.RemoteControlCar` can be referenced by the shorter name `Car`. + +## 3. Fix compilation error `undefined function puts/1` + +The function `puts/1` comes from the module `IO`. Import it to be able to use it without referencing the module. + +## 4. Fix compilation error `undefined function default_color/0` + +The functions `default_color/0`, `red/0`, `cyan/0`, and `green/0` all come from the module `IO.ANSI`. You're planning to add support for other car colors, so you want to import the whole module. Unfortunately, the function `color/1` from the module `IO.ANSI` conflicts with one of your local functions. Import the whole `IO.ANSI` module except that one function. + +## Source + +### Created by + +- @angelikatyborska + +### Contributed to by + +- @neenjaw \ No newline at end of file diff --git a/elixir/need-for-speed/lib/need_for_speed.ex b/elixir/need-for-speed/lib/need_for_speed.ex new file mode 100644 index 0000000..87837be --- /dev/null +++ b/elixir/need-for-speed/lib/need_for_speed.ex @@ -0,0 +1,43 @@ +defmodule NeedForSpeed do + alias NeedForSpeed.Race + alias NeedForSpeed.RemoteControlCar, as: Car + import IO, only: [puts: 1] + import IO.ANSI, except: [color: 1] + + # Add missing aliases and imports here. + + # Do not edit the code below. + + def print_race(%Race{} = race) do + puts(""" + 🏁 #{race.title} 🏁 + Status: #{Race.display_status(race)} + Distance: #{Race.display_distance(race)} + + Contestants: + """) + + race.cars + |> Enum.sort_by(&(-1 * &1.distance_driven_in_meters)) + |> Enum.with_index() + |> Enum.each(fn {car, index} -> print_car(car, index + 1) end) + end + + defp print_car(%Car{} = car, index) do + color = color(car) + + puts(""" + #{index}. #{color}#{car.nickname}#{default_color()} + Distance: #{Car.display_distance(car)} + Battery: #{Car.display_battery(car)} + """) + end + + defp color(%Car{} = car) do + case car.color do + :red -> red() + :blue -> cyan() + :green -> green() + end + end +end diff --git a/elixir/need-for-speed/lib/need_for_speed/race.ex b/elixir/need-for-speed/lib/need_for_speed/race.ex new file mode 100644 index 0000000..170fe96 --- /dev/null +++ b/elixir/need-for-speed/lib/need_for_speed/race.ex @@ -0,0 +1,24 @@ +defmodule NeedForSpeed.Race do + defstruct [ + :title, + :total_distance_in_meters, + cars: [] + ] + + def display_status(%NeedForSpeed.Race{} = race) do + cond do + Enum.any?(race.cars, &(&1.distance_driven_in_meters >= race.total_distance_in_meters)) -> + "Finished" + + Enum.any?(race.cars, &(&1.distance_driven_in_meters > 0)) -> + "In Progress" + + true -> + "Not Started" + end + end + + def display_distance(%NeedForSpeed.Race{total_distance_in_meters: d}) do + "#{d} meters" + end +end diff --git a/elixir/need-for-speed/lib/need_for_speed/remote_control_car.ex b/elixir/need-for-speed/lib/need_for_speed/remote_control_car.ex new file mode 100644 index 0000000..49fed6f --- /dev/null +++ b/elixir/need-for-speed/lib/need_for_speed/remote_control_car.ex @@ -0,0 +1,24 @@ +defmodule NeedForSpeed.RemoteControlCar do + defstruct [ + :nickname, + :color, + battery_percentage: 100, + distance_driven_in_meters: 0 + ] + + def new(color, nickname) when color in [:red, :blue, :green] do + %NeedForSpeed.RemoteControlCar{nickname: nickname, color: color} + end + + def display_distance(%NeedForSpeed.RemoteControlCar{distance_driven_in_meters: d}) do + "#{d} meters" + end + + def display_battery(%NeedForSpeed.RemoteControlCar{battery_percentage: 0}) do + "Battery empty" + end + + def display_battery(%NeedForSpeed.RemoteControlCar{battery_percentage: b}) do + "Battery at #{b}%" + end +end diff --git a/elixir/need-for-speed/mix.exs b/elixir/need-for-speed/mix.exs new file mode 100644 index 0000000..798f006 --- /dev/null +++ b/elixir/need-for-speed/mix.exs @@ -0,0 +1,28 @@ +defmodule RemoteControlCar.MixProject do + use Mix.Project + + def project do + [ + app: :need_for_speed, + 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/need-for-speed/test/need_for_speed_test.exs b/elixir/need-for-speed/test/need_for_speed_test.exs new file mode 100644 index 0000000..bac497f --- /dev/null +++ b/elixir/need-for-speed/test/need_for_speed_test.exs @@ -0,0 +1,144 @@ +defmodule NeedForSpeedTest do + use ExUnit.Case + + import ExUnit.CaptureIO + + describe "print_race" do + @tag task_id: nil + test "prints a race with no cars" do + race = %NeedForSpeed.Race{ + title: "Need For Speed 2021", + total_distance_in_meters: 350, + cars: [] + } + + io = capture_io(fn -> NeedForSpeed.print_race(race) end) + + assert io == """ + 🏁 Need For Speed 2021 🏁 + Status: Not Started + Distance: 350 meters + + Contestants: + + """ + end + + @tag task_id: nil + test "prints a race with a red car" do + car = NeedForSpeed.RemoteControlCar.new(:red, "The Fox") + + race = %NeedForSpeed.Race{ + title: "Need For Speed 2021", + total_distance_in_meters: 500, + cars: [car] + } + + io = capture_io(fn -> NeedForSpeed.print_race(race) end) + + assert io == """ + 🏁 Need For Speed 2021 🏁 + Status: Not Started + Distance: 500 meters + + Contestants: + + 1. #{IO.ANSI.red()}The Fox#{IO.ANSI.default_color()} + Distance: 0 meters + Battery: Battery at 100% + + """ + end + + @tag task_id: nil + test "prints a race with a blue car" do + car = NeedForSpeed.RemoteControlCar.new(:blue, "The Hurricane") + + race = %NeedForSpeed.Race{ + title: "Need For Speed 2021", + total_distance_in_meters: 500, + cars: [car] + } + + io = capture_io(fn -> NeedForSpeed.print_race(race) end) + + assert io == """ + 🏁 Need For Speed 2021 🏁 + Status: Not Started + Distance: 500 meters + + Contestants: + + 1. #{IO.ANSI.cyan()}The Hurricane#{IO.ANSI.default_color()} + Distance: 0 meters + Battery: Battery at 100% + + """ + end + + @tag task_id: nil + test "prints a race with a green car" do + car = NeedForSpeed.RemoteControlCar.new(:green, "The Grasshopper") + + race = %NeedForSpeed.Race{ + title: "Need For Speed 2021", + total_distance_in_meters: 777, + cars: [car] + } + + io = capture_io(fn -> NeedForSpeed.print_race(race) end) + + assert io == """ + 🏁 Need For Speed 2021 🏁 + Status: Not Started + Distance: 777 meters + + Contestants: + + 1. #{IO.ANSI.green()}The Grasshopper#{IO.ANSI.default_color()} + Distance: 0 meters + Battery: Battery at 100% + + """ + end + + @tag task_id: nil + test "prints a race in progress with many cars" do + red_car = NeedForSpeed.RemoteControlCar.new(:red, "The Fox") + blue_car = NeedForSpeed.RemoteControlCar.new(:blue, "The Hurricane") + green_car = NeedForSpeed.RemoteControlCar.new(:green, "The Grasshopper") + red_car = %{red_car | distance_driven_in_meters: 305, battery_percentage: 70} + blue_car = %{blue_car | distance_driven_in_meters: 500, battery_percentage: 62} + green_car = %{green_car | distance_driven_in_meters: 235, battery_percentage: 60} + + race = %NeedForSpeed.Race{ + title: "Need For Speed 2021", + total_distance_in_meters: 1000, + cars: [red_car, blue_car, green_car] + } + + io = capture_io(fn -> NeedForSpeed.print_race(race) end) + + assert io == """ + 🏁 Need For Speed 2021 🏁 + Status: In Progress + Distance: 1000 meters + + Contestants: + + 1. #{IO.ANSI.cyan()}The Hurricane#{IO.ANSI.default_color()} + Distance: 500 meters + Battery: Battery at 62% + + 2. #{IO.ANSI.red()}The Fox#{IO.ANSI.default_color()} + Distance: 305 meters + Battery: Battery at 70% + + 3. #{IO.ANSI.green()}The Grasshopper#{IO.ANSI.default_color()} + Distance: 235 meters + Battery: Battery at 60% + + """ + end + end +end diff --git a/elixir/need-for-speed/test/test_helper.exs b/elixir/need-for-speed/test/test_helper.exs new file mode 100644 index 0000000..35fc5bf --- /dev/null +++ b/elixir/need-for-speed/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true)