kitchen-calculator
This commit is contained in:
parent
f3067bce0f
commit
5c704aa88f
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"authors": [
|
||||
"neenjaw"
|
||||
],
|
||||
"contributors": [
|
||||
"angelikatyborska"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"lib/kitchen_calculator.ex"
|
||||
],
|
||||
"test": [
|
||||
"test/kitchen_calculator_test.exs"
|
||||
],
|
||||
"exemplar": [
|
||||
".meta/exemplar.ex"
|
||||
]
|
||||
},
|
||||
"language_versions": ">=1.10",
|
||||
"blurb": "Learn about tuples and pattern matching by converting common US baking measurements to the metric system."
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"track":"elixir","exercise":"kitchen-calculator","id":"b4d4b27093db4fb3973a38c78cf108f7","url":"https://exercism.org/tracks/elixir/exercises/kitchen-calculator","handle":"negrienko","is_requester":true,"auto_approve":false}
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
|
@ -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").
|
||||
tuples-*.tar
|
||||
|
|
@ -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/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.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/kitchen_calculator.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.
|
|
@ -0,0 +1,30 @@
|
|||
# Hints
|
||||
|
||||
## General
|
||||
|
||||
- [Tuples][tuple-module] are data structures which are arranged in contiguous memory and can hold any data-type.
|
||||
- Atoms may be used to denote finite states, as this exercise uses `:cup`, `:fluid_ounce`, `:teaspoon`, `:tablespoon`, `:milliliter` to denote the units used.
|
||||
- You may use [`Kernel`][elem] or [`Tuple`][tuple-module] functions or pattern matching to manipulate the tuples.
|
||||
|
||||
## 1. Get the numeric component from a volume-pair
|
||||
|
||||
- Consider using [a `Kernel` module function][elem] to return the volume-pair's numeric component.
|
||||
|
||||
## 2. Convert the volume-pair to milliliters
|
||||
|
||||
- Use [multiple clause functions][multi-clause] and [pattern matching][pattern-matching] to reduce conditional control flow logic.
|
||||
- Implement the function for all units to milliliters, including milliliters to milliliters.
|
||||
|
||||
## 3. Convert the milliliter volume-pair to another unit
|
||||
|
||||
- Use [multiple clause functions][multi-clause] and [pattern matching][pattern-matching] to reduce conditional control flow logic.
|
||||
- Implement the function for all units to milliliters, including milliliters to milliliters.
|
||||
|
||||
## 4. Convert from any unit to any unit
|
||||
|
||||
- Reuse the functions already created to perform the conversion in the `convert/2` function.
|
||||
|
||||
[elem]: https://hexdocs.pm/elixir/Kernel.html#elem/2
|
||||
[multi-clause]: https://elixir-lang.org/getting-started/modules-and-functions.html#named-functions
|
||||
[tuple-module]: https://hexdocs.pm/elixir/Tuple.html
|
||||
[pattern-matching]: https://medium.com/rebirth-delivery/how-to-use-elixir-pattern-matched-functions-arguments-a793733acc6d
|
|
@ -0,0 +1,144 @@
|
|||
# Kitchen Calculator
|
||||
|
||||
Welcome to Kitchen Calculator 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
|
||||
|
||||
## Tuples
|
||||
|
||||
In Elixir, a tuple is a data structure which organizes data, holding a fixed number of items of any type, but without explicit names for each element. Tuples are often used in Elixir for memory read-intensive operations, since read-access of an element is a constant-time operation. They are not usually used when elements may need to be added/removed dynamically because rather than modifying the existing tuple, a new tuple is created which requires memory to be allocated upfront.
|
||||
|
||||
In practice, tuples are created in Elixir using curly braces. Elements in a tuple can be individually accessed using the `elem/2` function using 0-based indexing:
|
||||
|
||||
```elixir
|
||||
empty_tuple = {}
|
||||
one_element_tuple = {1}
|
||||
multiple_element_tuple = {1, :a, "hello"}
|
||||
|
||||
elem(multiple_element_tuple, 2)
|
||||
# => "hello"
|
||||
```
|
||||
|
||||
### Tuples as grouped information
|
||||
|
||||
Tuples are often used in practice to represent grouped information.
|
||||
|
||||
```elixir
|
||||
Float.ratio(0.25)
|
||||
# => {1, 4} indicating the numerator and denominator of the fraction ¼
|
||||
```
|
||||
|
||||
## Pattern Matching
|
||||
|
||||
The use of pattern matching is dominant in assertive, idiomatic Elixir code. You might recall that `=/2` is described as a match operator rather than as an assignment operator. When using the match operator, if the pattern on the left matches the right, any variables on the left are bound, and the value of the right side is returned. A `MatchError` is raised if there is no match.
|
||||
|
||||
```elixir
|
||||
2 = 2
|
||||
# => 2
|
||||
# Literals can be matched if they are the same
|
||||
|
||||
2 = 3
|
||||
# => ** (MatchError) no match of right hand side value: 3
|
||||
|
||||
{_, denominator} = Float.ratio(0.25)
|
||||
# => {1, 4}
|
||||
# the variable `denominator` is bound to the value 4
|
||||
```
|
||||
|
||||
Remember, matches occur from the right side to the left side.
|
||||
|
||||
In the last example, if we don't need a variable in a pattern match, we can discard it by referencing `_`.
|
||||
Any variable starting with an `_` is not tracked by the runtime.
|
||||
|
||||
### Pattern matching in named functions
|
||||
|
||||
Pattern matching is also a useful tool when creating multiple function clauses. Pattern matching can be used on the functions' arguments which then determines which function clause to invoke -- starting from the top of the file down until the first match. Variables may be bound in a function head and used in the function body.
|
||||
|
||||
```elixir
|
||||
defmodule Example do
|
||||
def named_function(:a = variable_a) do
|
||||
{variable_a, 1}
|
||||
end
|
||||
|
||||
def named_function(:b = variable_b) do
|
||||
{variable_b, 2}
|
||||
end
|
||||
end
|
||||
|
||||
Example.named_function(:a)
|
||||
# => {:a, 1}
|
||||
|
||||
Example.named_function(:b)
|
||||
# => {:b, 2}
|
||||
|
||||
Example.named_function(:c)
|
||||
# => ** (FunctionClauseError) no function clause matching in Example.named_function/1
|
||||
```
|
||||
|
||||
## Instructions
|
||||
|
||||
While preparing to bake cookies for your friends, you have found that you have to convert some of the measurements used in the recipe. Being only familiar with the metric system, you need to come up with a way to convert common US baking measurements to milliliters (mL) for your own ease.
|
||||
|
||||
Use this conversion chart for your solution:
|
||||
|
||||
| Unit to convert | volume | in milliliters (mL) |
|
||||
| --------------- | ------ | ------------------- |
|
||||
| mL | 1 | 1 |
|
||||
| US cup | 1 | 240 |
|
||||
| US fluid ounce | 1 | 30 |
|
||||
| US teaspoon | 1 | 5 |
|
||||
| US tablespoon | 1 | 15 |
|
||||
|
||||
Being a talented programmer in training, you decide to use milliliters as a transition unit to facilitate the conversion from any unit listed to any other (even itself).
|
||||
|
||||
## 1. Get the numeric component from a volume-pair
|
||||
|
||||
Implement the `KitchenCalculator.get_volume/1` function. Given a volume-pair tuple, it should return just the numeric component.
|
||||
|
||||
```elixir
|
||||
KitchenCalculator.get_volume({:cup, 2.0})
|
||||
# => 2.0
|
||||
```
|
||||
|
||||
## 2. Convert the volume-pair to milliliters
|
||||
|
||||
Implement the `KitchenCalculator.to_milliliter/1` function. Given a volume-pair tuple, it should convert the volume to milliliters using the conversion chart.
|
||||
|
||||
Use multiple function clauses and pattern matching to create the functions for each unit. The atoms used to denote each unit are: `:cup`, `:fluid_ounce`, `:teaspoon`, `:tablespoon`, `:milliliter`. Return the result of the conversion wrapped in a tuple.
|
||||
|
||||
```elixir
|
||||
KitchenCalculator.to_milliliter({:cup, 2.5})
|
||||
# => {:milliliter, 600.0}
|
||||
```
|
||||
|
||||
## 3. Convert the milliliter volume-pair to another unit
|
||||
|
||||
Implement the `KitchenCalculator.from_milliliter/2` function. Given a volume-pair tuple and the desired unit, it should convert the volume to the desired unit using the conversion chart.
|
||||
|
||||
Use multiple function clauses and pattern matching to create the functions for each unit. The atoms used to denote each unit are: `:cup`, `:fluid_ounce`, `:teaspoon`, `:tablespoon`, `:milliliter`
|
||||
|
||||
```elixir
|
||||
KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
|
||||
# => {:cup, 5.5}
|
||||
```
|
||||
|
||||
## 4. Convert from any unit to any unit
|
||||
|
||||
Implement the `KitchenCalculator.convert/2` function. Given a volume-pair tuple and the desired unit, it should convert the given volume to the desired unit.
|
||||
|
||||
```elixir
|
||||
KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
|
||||
# => {:tablespoon, 3.0}
|
||||
```
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @neenjaw
|
||||
|
||||
### Contributed to by
|
||||
|
||||
- @angelikatyborska
|
|
@ -0,0 +1,23 @@
|
|||
defmodule KitchenCalculator do
|
||||
defp milliliter(:milliliter), do: 1
|
||||
defp milliliter(:cup), do: 240
|
||||
defp milliliter(:fluid_ounce), do: 30
|
||||
defp milliliter(:teaspoon), do: 5
|
||||
defp milliliter(:tablespoon), do: 15
|
||||
|
||||
def get_volume(_volume_pair = {_, volume}), do: volume
|
||||
|
||||
def to_milliliter(_volume_pair = {from_unit, volume}),
|
||||
do:
|
||||
{:milliliter, milliliter(from_unit) * volume}
|
||||
|
||||
def from_milliliter(_volume_pair = {:milliliter, volume}, to_unit),
|
||||
do:
|
||||
{to_unit, volume / milliliter(to_unit)}
|
||||
|
||||
def convert(volume_pair, unit) do
|
||||
volume_pair
|
||||
|> to_milliliter
|
||||
|> from_milliliter(unit)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
defmodule KitchenCalculator.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :kitchen_calculator,
|
||||
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
|
|
@ -0,0 +1,109 @@
|
|||
defmodule KitchenCalculatorTest do
|
||||
use ExUnit.Case
|
||||
|
||||
describe "get volume from tuple pair" do
|
||||
@tag task_id: 1
|
||||
test "get cups" do
|
||||
assert KitchenCalculator.get_volume({:cup, 1}) == 1
|
||||
end
|
||||
|
||||
@tag task_id: 1
|
||||
test "get fluid ounces" do
|
||||
assert KitchenCalculator.get_volume({:fluid_ounce, 2}) == 2
|
||||
end
|
||||
|
||||
@tag task_id: 1
|
||||
test "get teaspoons" do
|
||||
assert KitchenCalculator.get_volume({:teaspoon, 3}) == 3
|
||||
end
|
||||
|
||||
@tag task_id: 1
|
||||
test "get tablespoons" do
|
||||
assert KitchenCalculator.get_volume({:tablespoon, 4}) == 4
|
||||
end
|
||||
|
||||
@tag task_id: 1
|
||||
test "get milliliters" do
|
||||
assert KitchenCalculator.get_volume({:milliliter, 5}) == 5
|
||||
end
|
||||
end
|
||||
|
||||
describe "convert to milliliters from" do
|
||||
@tag task_id: 2
|
||||
test "milliliters" do
|
||||
assert KitchenCalculator.to_milliliter({:milliliter, 3}) == {:milliliter, 3}
|
||||
end
|
||||
|
||||
@tag task_id: 2
|
||||
test "cups" do
|
||||
assert KitchenCalculator.to_milliliter({:cup, 3}) == {:milliliter, 720}
|
||||
end
|
||||
|
||||
@tag task_id: 2
|
||||
test "fluid ounces" do
|
||||
assert KitchenCalculator.to_milliliter({:fluid_ounce, 100}) == {:milliliter, 3000}
|
||||
end
|
||||
|
||||
@tag task_id: 2
|
||||
test "teaspoon" do
|
||||
assert KitchenCalculator.to_milliliter({:teaspoon, 3}) == {:milliliter, 15}
|
||||
end
|
||||
|
||||
@tag task_id: 2
|
||||
test "tablespoon" do
|
||||
assert KitchenCalculator.to_milliliter({:tablespoon, 3}) == {:milliliter, 45}
|
||||
end
|
||||
end
|
||||
|
||||
describe "convert from milliliters to" do
|
||||
@tag task_id: 3
|
||||
test "milliliters" do
|
||||
assert KitchenCalculator.from_milliliter({:milliliter, 4}, :milliliter) == {:milliliter, 4}
|
||||
end
|
||||
|
||||
@tag task_id: 3
|
||||
test "cups" do
|
||||
assert KitchenCalculator.from_milliliter({:milliliter, 840}, :cup) == {:cup, 3.5}
|
||||
end
|
||||
|
||||
@tag task_id: 3
|
||||
test "fluid ounces" do
|
||||
assert KitchenCalculator.from_milliliter({:milliliter, 4522.5}, :fluid_ounce) ==
|
||||
{:fluid_ounce, 150.75}
|
||||
end
|
||||
|
||||
@tag task_id: 3
|
||||
test "teaspoon" do
|
||||
assert KitchenCalculator.from_milliliter({:milliliter, 61.25}, :teaspoon) ==
|
||||
{:teaspoon, 12.25}
|
||||
end
|
||||
|
||||
@tag task_id: 3
|
||||
test "tablespoon" do
|
||||
assert KitchenCalculator.from_milliliter({:milliliter, 71.25}, :tablespoon) ==
|
||||
{:tablespoon, 4.75}
|
||||
end
|
||||
end
|
||||
|
||||
describe "convert from x to y:" do
|
||||
@tag task_id: 4
|
||||
test "teaspoon to tablespoon" do
|
||||
assert KitchenCalculator.convert({:teaspoon, 15}, :tablespoon) == {:tablespoon, 5}
|
||||
end
|
||||
|
||||
@tag task_id: 4
|
||||
test "cups to fluid ounces" do
|
||||
assert KitchenCalculator.convert({:cup, 4}, :fluid_ounce) == {:fluid_ounce, 32}
|
||||
end
|
||||
|
||||
@tag task_id: 4
|
||||
test "fluid ounces to teaspoons" do
|
||||
assert KitchenCalculator.convert({:fluid_ounce, 4}, :teaspoon) == {:teaspoon, 24}
|
||||
end
|
||||
|
||||
@tag task_id: 4
|
||||
test "tablespoons to cups" do
|
||||
assert KitchenCalculator.convert({:tablespoon, 320}, :cup) == {:cup, 20}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
ExUnit.start()
|
||||
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
|
@ -11,6 +11,5 @@ defmodule Year do
|
|||
@spec leap_year?(non_neg_integer) :: boolean
|
||||
def leap_year?(year) when rem(year, 400) == 0, do: true
|
||||
def leap_year?(year) when rem(year, 100) == 0, do: false
|
||||
def leap_year?(year) when rem(year, 4) == 0, do: true
|
||||
def leap_year?(_year), do: false
|
||||
def leap_year?(year), do: rem(year, 4) == 0
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue