Add several elixir solutions

This commit is contained in:
Fabian Becker 2022-08-25 13:28:30 +02:00
parent 57bc7b0289
commit 56734c60b9
32 changed files with 1191 additions and 0 deletions

View File

@ -0,0 +1,39 @@
{
"authors": [
"rubysolo"
],
"contributors": [
"andrewsardone",
"angelikatyborska",
"bunnymatic",
"Cohen-Carlisle",
"dalexj",
"devonestes",
"elasticdog",
"henrik",
"herminiotorres",
"jinyeow",
"lpil",
"neenjaw",
"parkerl",
"petehuang",
"pminten",
"sotojuan",
"Teapane",
"waiting-for-dev"
],
"files": {
"solution": [
"lib/grains.ex"
],
"test": [
"test/grains_test.exs"
],
"example": [
".meta/example.ex"
]
},
"blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.",
"source": "JavaRanch Cattle Drive, exercise 6",
"source_url": "http://www.javaranch.com/grains.jsp"
}

View File

@ -0,0 +1 @@
{"track":"elixir","exercise":"grains","id":"323e0439aa1d482b91d857e239fa16b7","url":"https://exercism.org/tracks/elixir/exercises/grains","handle":"halfdan","is_requester":true,"auto_approve":false}

View File

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
elixir/grains/.gitignore vendored Normal file
View File

@ -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").
grains-*.tar

75
elixir/grains/HELP.md Normal file
View File

@ -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/grains.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)
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
- 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.
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).

64
elixir/grains/README.md Normal file
View File

@ -0,0 +1,64 @@
# Grains
Welcome to Grains on Exercism's Elixir Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Calculate the number of grains of wheat on a chessboard given that the number
on each square doubles.
There once was a wise servant who saved the life of a prince. The king
promised to pay whatever the servant could dream up. Knowing that the
king loved chess, the servant told the king he would like to have grains
of wheat. One grain on the first square of a chess board, with the number
of grains doubling on each successive square.
There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on).
Write code that shows:
- how many grains were on a given square, and
- the total number of grains on the chessboard
## For bonus points
Did you get the tests passing and the code clean? If you want to, these
are some additional things you could try:
- Optimize for speed.
- Optimize for readability.
Then please share your thoughts in a comment on the submission. Did this
experiment make the code better? Worse? Did you learn anything from it?
## Source
### Created by
- @rubysolo
### Contributed to by
- @andrewsardone
- @angelikatyborska
- @bunnymatic
- @Cohen-Carlisle
- @dalexj
- @devonestes
- @elasticdog
- @henrik
- @herminiotorres
- @jinyeow
- @lpil
- @neenjaw
- @parkerl
- @petehuang
- @pminten
- @sotojuan
- @Teapane
- @waiting-for-dev
### Based on
JavaRanch Cattle Drive, exercise 6 - http://www.javaranch.com/grains.jsp

View File

@ -0,0 +1,23 @@
defmodule Grains do
@doc """
Calculate two to the power of the input minus one.
"""
@spec square(pos_integer()) :: {:ok, pos_integer()} | {:error, String.t()}
def square(number) when number < 1 or number > 64 do
{:error, "The requested square must be between 1 and 64 (inclusive)"}
end
def square(number), do: {:ok, 2**(number-1)}
@doc """
Adds square of each number from 1 to 64.
"""
@spec total :: {:ok, pos_integer()}
def total do
Enum.reduce_while(1..64, {:ok, 0}, fn n, {:ok, sum} ->
case square(n) do
{:ok, squared} -> {:cont, {:ok, squared + sum}}
{:error, error} -> {:halt, {:error, error}}
end
end)
end
end

28
elixir/grains/mix.exs Normal file
View File

@ -0,0 +1,28 @@
defmodule Grains.MixProject do
use Mix.Project
def project do
[
app: :grains,
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

View File

@ -0,0 +1,61 @@
defmodule GrainsTest do
use ExUnit.Case
# @tag :pending
test "square 1" do
assert Grains.square(1) === {:ok, 1}
end
@tag :pending
test "square 2" do
assert Grains.square(2) === {:ok, 2}
end
@tag :pending
test "square 3" do
assert Grains.square(3) === {:ok, 4}
end
@tag :pending
test "square 4" do
assert Grains.square(4) === {:ok, 8}
end
@tag :pending
test "square 16" do
assert Grains.square(16) === {:ok, 32768}
end
@tag :pending
test "square 32" do
assert Grains.square(32) === {:ok, 2_147_483_648}
end
@tag :pending
test "square 64" do
assert Grains.square(64) === {:ok, 9_223_372_036_854_775_808}
end
@tag :pending
test "total grains" do
assert Grains.total() === {:ok, 18_446_744_073_709_551_615}
end
@tag :pending
test "square greater than 64 returns an error" do
assert Grains.square(65) ===
{:error, "The requested square must be between 1 and 64 (inclusive)"}
end
@tag :pending
test "negative square returns an error" do
assert Grains.square(-1) ===
{:error, "The requested square must be between 1 and 64 (inclusive)"}
end
@tag :pending
test "square 0 returns an error" do
assert Grains.square(0) ===
{:error, "The requested square must be between 1 and 64 (inclusive)"}
end
end

View File

@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)

View File

@ -0,0 +1,21 @@
{
"authors": [
"neenjaw"
],
"contributors": [
"angelikatyborska"
],
"files": {
"solution": [
"lib/lucas_numbers.ex"
],
"test": [
"test/lucas_numbers_test.exs"
],
"exemplar": [
".meta/exemplar.ex"
]
},
"language_versions": ">=1.10",
"blurb": "Learn about streams by generating the Lucas number sequence."
}

View File

@ -0,0 +1 @@
{"track":"elixir","exercise":"lucas-numbers","id":"843df89f0b464117ac83a3f4120e8404","url":"https://exercism.org/tracks/elixir/exercises/lucas-numbers","handle":"halfdan","is_requester":true,"auto_approve":false}

View File

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

27
elixir/lucas-numbers/.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
lucas_numbers-*.tar
# Temporary files for e.g. tests
/tmp

View File

@ -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/lucas_numbers.ex` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
- 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.
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).

View File

@ -0,0 +1,29 @@
# Hints
## General
- Use the built-in [(linked) list type][list].
- Use the built-in [`Stream`][stream] module functions to create a stream
## 1. Generate the base cases
- You can use multiple [function clauses][multiple-fn-clauses] and [pattern-matching][pattern-matching] to create the base case functions.
## 2. Create the generalized case
- Use the [`Stream.iterate/2`][stream-iterate] function to generate a sequence of numbers, with the next being created from the previous.
- The starting numbers are `2` then `1`, which you can pass in together using a tuple to make a pair `{2, 1}`
- Make sure the next number is the sum of the two numbers previous to it.
- To evaluate the stream to a list, use an [`Enum`][enum] function.
## 3. Catch bad arguments
- Use a [guard][guards] to catch the cases when an integer isn't passed as an argument to `generate/1`.
[enum]: https://hexdocs.pm/elixir/Enum.html#content
[guards]: https://hexdocs.pm/elixir/master/patterns-and-guards.html#guards
[list]: https://elixir-lang.org/getting-started/basic-types.html#linked-lists
[multiple-fn-clauses]: https://elixir-lang.org/getting-started/modules-and-functions.html#named-functions
[pattern-matching]: https://elixir-lang.org/getting-started/pattern-matching.html#pattern-matching-1
[stream]: https://hexdocs.pm/elixir/Stream.html#content
[stream-iterate]: https://hexdocs.pm/elixir/Stream.html#iterate/2

View File

@ -0,0 +1,67 @@
# Lucas Numbers
Welcome to Lucas Numbers on Exercism's Elixir Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Streams
All functions in the [`Enum` module][exercism-enum] are _eager_. When performing multiple operations on enumerables with the `Enum` module, each operation is going to generate an intermediate result.
The `Stream` module is a _lazy_ alternative to the _eager_ `Enum` module. It offers many of the same functions as `Enum`, but instead of generating intermediate results, it builds a series of computations that are only executed once the stream is passed to a function from the `Enum` module.
Streams implement the _Enumerable [protocol][exercism-protocols]_ and are composable -- you can chain them together to create more complex functionality.
[exercism-enum]: https://exercism.org/tracks/elixir/concepts/enum
[exercism-protocols]: https://exercism.org/tracks/elixir/concepts/protocols
## Instructions
You are a huge fan of the [Numberphile Youtube channel](https://www.youtube.com/watch?v=PeUbRXnbmms) and you just saw a cool video about the _Lucas Number Sequence_. You want to create this sequence using Elixir.
While designing your function, you want to make use of _lazy evaluation_, so that you can generate as many numbers as you want, but only if you need to -- So you decide to use a stream:
## 1. Generate the base cases
You know that the sequence has two starting numbers which don't follow the same rule. Write two base case clauses to return these numbers:
```elixir
LucasNumbers.generate(1)
# => [2]
LucasNumbers.generate(2)
# => [2, 1]
```
## 2. Create the generalized case
For any sequence longer than 2, you know that you need to add the previous two numbers to get the next number and so on. Write the generalized case.
```elixir
LucasNumbers.generate(3)
# => [2, 1, 3]
LucasNumbers.generate(4)
# => [2, 1, 3, 4]
```
## 3. Catch bad arguments
Later, you find someone is using your function and having problems because they are using incorrect arguments. Add a guard clause to raise an error if a non-integer or an integer less than 1 is used to generate the sequence:
```elixir
LucasNumbers.generate("Hello World")
# => ** (ArgumentError) count must be specified as an integer >= 1
```
## Source
### Created by
- @neenjaw
### Contributed to by
- @angelikatyborska

View File

@ -0,0 +1,18 @@
defmodule LucasNumbers do
defguardp is_positive(k) when is_integer(k) and k > 0
@moduledoc """
Lucas numbers are an infinite sequence of numbers which build progressively
which hold a strong correlation to the golden ratio (φ or ϕ)
E.g.: 2, 1, 3, 4, 7, 11, 18, 29, ...
"""
def generate(1), do: [2]
def generate(2), do: [2, 1]
def generate(count) when is_positive(count) do
Stream.iterate({2,1}, fn {x,y} -> {y, x+y} end)
|> Stream.map(fn {x,_} -> x end)
|> Enum.take(count)
end
def generate(_), do: raise ArgumentError, "count must be specified as an integer >= 1"
end

View File

@ -0,0 +1,28 @@
defmodule LucasNumbers.MixProject do
use Mix.Project
def project do
[
app: :lucas_numbers,
version: "0.1.0",
# elixir: "~> 1.10",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end

View File

@ -0,0 +1,112 @@
defmodule LucasNumbersTest do
use ExUnit.Case
@tag task_id: 1
test "generates a sequence of length 1" do
assert LucasNumbers.generate(1) == [2]
end
@tag task_id: 1
test "generates a sequence of length 2" do
assert LucasNumbers.generate(2) == [2, 1]
end
@tag task_id: 2
test "generates a sequence of length 3" do
assert LucasNumbers.generate(3) == [2, 1, 3]
end
@tag task_id: 2
test "generates a sequence of length 4" do
assert LucasNumbers.generate(4) == [2, 1, 3, 4]
end
@tag task_id: 2
test "generates a sequence of length 5" do
sequence = [2, 1, 3, 4, 7]
assert LucasNumbers.generate(5) == sequence
end
@tag task_id: 2
test "generates a sequence of length 6" do
sequence = [2, 1, 3, 4, 7, 11]
assert LucasNumbers.generate(6) == sequence
end
@tag task_id: 2
test "generates a sequence of length 7" do
sequence = [2, 1, 3, 4, 7, 11, 18]
assert LucasNumbers.generate(7) == sequence
end
@tag task_id: 2
test "generates a sequence of length 8" do
sequence = [2, 1, 3, 4, 7, 11, 18, 29]
assert LucasNumbers.generate(8) == sequence
end
@tag task_id: 2
test "generates a sequence of length 9" do
sequence = [2, 1, 3, 4, 7, 11, 18, 29, 47]
assert LucasNumbers.generate(9) == sequence
end
@tag task_id: 2
test "generates a sequence of length 10" do
sequence = [2, 1, 3, 4, 7, 11, 18, 29, 47, 76]
assert LucasNumbers.generate(10) == sequence
end
@tag task_id: 2
test "generates a sequence of length 25" do
sequence = [
2,
1,
3,
4,
7,
11,
18,
29,
47,
76,
123,
199,
322,
521,
843,
1364,
2207,
3571,
5778,
9349,
15127,
24476,
39603,
64079,
103_682
]
assert LucasNumbers.generate(25) == sequence
end
@tag task_id: 3
test "catch incorrect non-integer arguments" do
assert_raise ArgumentError, "count must be specified as an integer >= 1", fn ->
LucasNumbers.generate("Hello world!")
end
end
@tag task_id: 3
test "catch incorrect integer arguments" do
assert_raise ArgumentError, "count must be specified as an integer >= 1", fn ->
LucasNumbers.generate(-1)
end
end
end

View File

@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true, seed: 0)

View File

@ -0,0 +1,21 @@
{
"authors": [
"jiegillet"
],
"contributors": [
"angelikatyborska"
],
"files": {
"solution": [
"lib/new_passport.ex"
],
"test": [
"test/new_passport_test.exs"
],
"exemplar": [
".meta/exemplar.ex"
]
},
"language_versions": ">=1.10",
"blurb": "Learn about `with` to concentrate on the happy path and manage a stressful day of facing bureaucracy."
}

View File

@ -0,0 +1 @@
{"track":"elixir","exercise":"new-passport","id":"674520f3b67548a8907ce0871db7f77c","url":"https://exercism.org/tracks/elixir/exercises/new-passport","handle":"halfdan","is_requester":true,"auto_approve":false}

View File

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
elixir/new-passport/.gitignore vendored Normal file
View File

@ -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").
match_binary-*.tar

View File

@ -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/new_passport.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)
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
- 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.
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).

View File

@ -0,0 +1,38 @@
# Hints
## General
- Read about using `with` in the [official Getting Started guide][getting-started-with].
- Review the functions available in the [`NaiveDateTime` module][naive-date-time], the [`Date` module][date], and the [`Time` module][time].
## 1. Get into the building
- Match the `:ok` tuple returned by `enter_building/1` in `with` with `<-`.
- In the `do` part of `with`, return an `:ok` tuple with the value you just matched.
- Since you don't need to modify the error, you don't need an `else` block.
## 2. Go to the information desk and find which counter you should go to
- Match the `:ok` tuple returned by `find_counter_information/1` in `with` with `<-`.
- Apply the anonymous function your just matched and match the result with `<-`.
- In the `do` part of `with`, return an `:ok` tuple with the counter you obtained.
- Add an `else` block that will expect a `:coffee_break` tuple and return a `:retry` tuple with a `NaiveDateTime`.
- A minute has `60` seconds.
- There is a [built-in function][naive-date-time-add] that adds a given number of seconds to a `NaiveDateTime` struct.
- Other errors should be returned as they are.
## 3. Go to the counter and get your form stamped
- Match the `:ok` tuple returned by `stamp_form/3` in `with` with `<-`.
- In the `do` part of `with`, return an `:ok` tuple with the checksum.
## 4. Receive your new passport
- In the `do` part of `with`, use `get_new_passport_number/3` and return the result in an `:ok` tuple.
[with]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1
[getting-started-with]: https://elixir-lang.org/getting-started/mix-otp/docs-tests-and-with.html#with
[naive-date-time]: https://hexdocs.pm/elixir/NaiveDateTime.html
[time]: https://hexdocs.pm/elixir/Time.html
[date]: https://hexdocs.pm/elixir/Date.html
[naive-date-time-add]: https://hexdocs.pm/elixir/NaiveDateTime.html#add/3

View File

@ -0,0 +1,78 @@
# New Passport
Welcome to New Passport 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
## With
The [special form with][with] provides a way to focus on the "happy path" of a series of potentially failing steps and deal with the failures later.
```elixir
with {:ok, id} <- get_id(username),
{:ok, avatar} <- fetch_avatar(id),
{:ok, image_type} <- check_valid_image_type(avatar) do
{:ok, image_type, avatar}
else
:not_found ->
{:error, "invalid username"}
{:error, "not an image"} ->
{:error, "avatar associated to #{username} is not an image"}
err ->
err
end
```
At each step, if a clause matches, the chain will continue until the `do` block is executed. If one match fails, the chain stops and the non-matching clause is returned. You have the option of using an `else` block to catch failed matches and modify the return value.
[with]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1
## Instructions
Your passport is about to expire, so you need to drop by the city office to renew it. You know from previous experience that your city office is not necessarily the easiest to deal with, so you decide to do your best to always "focus on the happy path".
You print out the form you need to get your new passport, fill it out, jump into your car, drive around the block, park and head to the office.
All the following tasks will require implementing and extending `get_new_passport/3`.
## 1. Get into the building
It turns out that the building is only open in the afternoon, and not at the same time everyday.
Call the function `enter_building/1` with the current time (given to you as first argument of `get_new_passport/3`). If the building is open, the function will return a tuple with `:ok` and a timestamp that you will need later, otherwise a tuple with `:error` and a message. For now, the happy path can return the `:ok` tuple.
If you get an `:error` tuple, use the `else` block to return it.
## 2. Go to the information desk and find which counter you should go to
The information desk is notorious for taking long coffee breaks. If you are lucky enough to find someone there, they will give you an instruction manual which will explain which counter you need to go to depending on your birth date.
Call the function `find_counter_information/1` with the current time. You will get either a tuple with `:ok` and a manual, represented by an anonymous function, or a tuple with `:coffee_break` and more instructions. In your happy path where you receive the manual, apply it to you birthday (second argument of `get_new_passport/3`). It will return the number of the counter where you need to go. Return an `:ok` tuple with that counter number.
If you get a `:coffee_break` message, return a tuple with `:retry` and a `NaiveDateTime` pointing to 15 minutes after the current time. As before, if you get an `:error` tuple, return it.
## 3. Go to the counter and get your form stamped
For some reason, different counters require forms of different colors. Of course, you printed the first one you found on the website, so you focus on your happy path and hope for the best.
Call the function `stamp_form/3` with the timestamp you received at the entrance, the counter and the form you brought (last argument of `get_new_passport/3`). You will get either a tuple with `:ok` and a checksum that will be used to verify your passport number or a tuple with `:error` and a message. Have your happy path return an `:ok` tuple with the checksum. If you get an `:error` tuple, return it.
## 4. Receive your new passport
Finally, you have all the documents you need.
Call `get_new_passport_number/3` with the timestamp, the counter and the checksum you received earlier. You will receive a string with your final passport number, all that is left to do is to return that string in a tuple with `:ok` and go home.
## Source
### Created by
- @jiegillet
### Contributed to by
- @angelikatyborska

View File

@ -0,0 +1,60 @@
defmodule NewPassport do
def get_new_passport(now, birthday, form) do
with {:ok, entrance} <- enter_building(now),
{:ok, manual} <- find_counter_information(now),
{:ok, counter} <- {:ok, manual.(birthday)},
{:ok, checksum} <- stamp_form(entrance, counter, form) do
{:ok, get_new_passport_number(entrance, counter, checksum)}
else
{:coffee_break, _} -> {:retry, NaiveDateTime.add(now, 60*15, :second)}
err -> err
end
end
# Do not modify the functions below
defp enter_building(%NaiveDateTime{} = datetime) do
day = Date.day_of_week(datetime)
time = NaiveDateTime.to_time(datetime)
cond do
day <= 4 and time_between(time, ~T[13:00:00], ~T[15:30:00]) ->
{:ok, datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_unix()}
day == 5 and time_between(time, ~T[13:00:00], ~T[14:30:00]) ->
{:ok, datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_unix()}
true ->
{:error, "city office is closed"}
end
end
@eighteen_years 18 * 365
defp find_counter_information(%NaiveDateTime{} = datetime) do
time = NaiveDateTime.to_time(datetime)
if time_between(time, ~T[14:00:00], ~T[14:20:00]) do
{:coffee_break, "information counter staff on coffee break, come back in 15 minutes"}
else
{:ok, fn %Date{} = birthday -> 1 + div(Date.diff(datetime, birthday), @eighteen_years) end}
end
end
defp stamp_form(timestamp, counter, :blue) when rem(counter, 2) == 1 do
{:ok, 3 * (timestamp + counter) + 1}
end
defp stamp_form(timestamp, counter, :red) when rem(counter, 2) == 0 do
{:ok, div(timestamp + counter, 2)}
end
defp stamp_form(_timestamp, _counter, _form), do: {:error, "wrong form color"}
defp get_new_passport_number(timestamp, counter, checksum) do
"#{timestamp}-#{counter}-#{checksum}"
end
defp time_between(time, from, to) do
Time.compare(from, time) != :gt and Time.compare(to, time) == :gt
end
end

View File

@ -0,0 +1,28 @@
defmodule NewPassport.MixProject do
use Mix.Project
def project do
[
app: :new_passport,
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

View File

@ -0,0 +1,155 @@
defmodule NewPassportTest do
use ExUnit.Case
describe "getting into the building" do
@tag task_id: 1
test "building is closed in the morning" do
assert NewPassport.get_new_passport(~N[2021-10-11 10:30:00], ~D[1984-09-14], :blue) ==
{:error, "city office is closed"}
end
@tag task_id: 1
test "building is closed early on Friday afternoon" do
assert NewPassport.get_new_passport(~N[2021-10-08 15:00:00], ~D[1984-09-14], :blue) ==
{:error, "city office is closed"}
end
@tag task_id: 1
test "entering during business hour" do
assert {:ok, _} =
NewPassport.get_new_passport(~N[2021-10-11 15:00:00], ~D[1984-09-14], :blue)
end
end
describe "find the right counter" do
@tag task_id: 2
test "information staff on coffee break" do
assert NewPassport.get_new_passport(~N[2021-10-11 14:10:00], ~D[1984-09-14], :blue) ==
{:retry, ~N[2021-10-11 14:25:00]}
end
@tag task_id: 2
test "information staff on coffee break, retry at given time" do
assert {:ok, _} =
NewPassport.get_new_passport(~N[2021-10-11 14:25:00], ~D[1984-09-14], :blue)
end
@tag task_id: 2
test "information staff on coffee break on Friday 15 minutes before closing time" do
assert NewPassport.get_new_passport(~N[2021-10-08 14:15:00], ~D[1984-09-14], :blue) ==
{:retry, ~N[2021-10-08 14:30:00]}
end
@tag task_id: 2
test "retry after previous attempt, hit closing time" do
assert NewPassport.get_new_passport(~N[2021-10-08 14:30:00], ~D[1984-09-14], :blue) ==
{:error, "city office is closed"}
end
end
describe "get the passport form stamped" do
@tag task_id: 3
test "illegal form color" do
assert NewPassport.get_new_passport(
~N[2021-10-11 14:25:00],
~D[1984-09-14],
:orange_and_purple
) == {:error, "wrong form color"}
end
@tag task_id: 3
test "wrong form color" do
assert NewPassport.get_new_passport(~N[2021-10-11 14:25:00], ~D[1984-09-14], :red) ==
{:error, "wrong form color"}
end
@tag task_id: 3
test "correct form color" do
assert {:ok, _} =
NewPassport.get_new_passport(~N[2021-10-11 14:25:00], ~D[1984-09-14], :blue)
end
end
describe "receive the new passport number" do
@tag task_id: 4
test "get the right timestamp" do
assert {:ok, passport_number} =
NewPassport.get_new_passport(~N[2021-10-11 13:00:00], ~D[1984-09-14], :blue)
[timestamp, _counter, _checksum] = String.split(passport_number, "-")
assert timestamp == "1633957200"
end
@tag task_id: 4
test "get the right timestamp after waiting for coffee break" do
assert NewPassport.get_new_passport(~N[2021-10-11 14:15:00], ~D[1984-09-14], :blue) ==
{:retry, ~N[2021-10-11 14:30:00]}
assert {:ok, passport_number} =
NewPassport.get_new_passport(~N[2021-10-11 14:30:00], ~D[1984-09-14], :blue)
[timestamp, _counter, _checksum] = String.split(passport_number, "-")
assert timestamp == "1633962600"
end
@tag task_id: 4
test "get the right timestamp after waiting twice for coffee break" do
assert NewPassport.get_new_passport(~N[2021-10-11 14:00:00], ~D[1984-09-14], :blue) ==
{:retry, ~N[2021-10-11 14:15:00]}
assert NewPassport.get_new_passport(~N[2021-10-11 14:15:00], ~D[1984-09-14], :blue) ==
{:retry, ~N[2021-10-11 14:30:00]}
assert {:ok, passport_number} =
NewPassport.get_new_passport(~N[2021-10-11 14:30:00], ~D[1984-09-14], :blue)
[timestamp, _counter, _checksum] = String.split(passport_number, "-")
assert timestamp == "1633962600"
end
@tag task_id: 4
test "16 year old finds the right counter" do
assert {:ok, passport_number} =
NewPassport.get_new_passport(~N[2021-10-11 14:30:00], ~D[2005-09-14], :blue)
[_timestamp, counter, _checksum] = String.split(passport_number, "-")
assert counter == "1"
end
@tag task_id: 4
test "34 year old finds the right counter" do
assert {:ok, passport_number} =
NewPassport.get_new_passport(~N[2021-10-11 14:30:00], ~D[1987-09-14], :red)
[_timestamp, counter, _checksum] = String.split(passport_number, "-")
assert counter == "2"
end
@tag task_id: 4
test "get the right passport number" do
assert NewPassport.get_new_passport(~N[2021-10-11 15:00:00], ~D[1984-09-14], :blue) ==
{:ok, "1633964400-3-4901893210"}
end
@tag task_id: 4
test "get a passport number after waiting for a coffee break" do
assert NewPassport.get_new_passport(~N[2021-10-11 14:15:00], ~D[1984-09-14], :blue) ==
{:retry, ~N[2021-10-11 14:30:00]}
assert NewPassport.get_new_passport(~N[2021-10-11 14:30:00], ~D[1984-09-14], :blue) ==
{:ok, "1633962600-3-4901887810"}
end
@tag task_id: 4
test "get a passport number after two coffee breaks" do
assert NewPassport.get_new_passport(~N[2021-10-11 14:00:00], ~D[1964-09-14], :red) ==
{:retry, ~N[2021-10-11 14:15:00]}
assert NewPassport.get_new_passport(~N[2021-10-11 14:15:00], ~D[1964-09-14], :red) ==
{:retry, ~N[2021-10-11 14:30:00]}
assert NewPassport.get_new_passport(~N[2021-10-12 14:30:00], ~D[1964-09-14], :red) ==
{:ok, "1634049000-4-817024502"}
end
end
end

View File

@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true, seed: 0)