Compare commits
2 Commits
5c704aa88f
...
b8a7845967
Author | SHA1 | Date |
---|---|---|
Danil Negrienko | b8a7845967 | |
Danil Negrienko | 5cc59f9234 |
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"angelikatyborska"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"neenjaw"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"lib/bird_count.ex"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/bird_count_test.exs"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.ex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"language_versions": ">=1.10",
|
||||||
|
"forked_from": [
|
||||||
|
"csharp/bird-watcher"
|
||||||
|
],
|
||||||
|
"icon": "bird-watcher",
|
||||||
|
"blurb": "Learn about recursion by keeping track of how many birds visit your garden each day."
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"elixir","exercise":"bird-count","id":"0c00dd30ec8043f1ab5a1e23a37b5f35","url":"https://exercism.org/tracks/elixir/exercises/bird-count","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").
|
||||||
|
recursion-*.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/bird_count.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,41 @@
|
||||||
|
# Hints
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- Read about recursion in the official [Getting Started guide][getting-started-recursion].
|
||||||
|
- Read about recursion on [elixirschool.com][elixir-school-recursion].
|
||||||
|
- Use multiple clause functions and pattern matching.
|
||||||
|
- Try to split a problem into a base case and a recursive case. For example, let's say you want to count how many cookies are there in the cookie jar with a recursive approach. A base case is an empty jar - it has zero cookies. If the jar is not empty, then the number of cookies in the jar is equal to one cookie plus the number of cookies in the jar after removing one cookie.
|
||||||
|
|
||||||
|
## 1. Check how many birds visited today
|
||||||
|
|
||||||
|
- This task doesn't need recursion.
|
||||||
|
- Accessing the first element in a list can be done by [pattern matching][getting-started-pattern-matching].
|
||||||
|
|
||||||
|
## 2. Increment today's count
|
||||||
|
|
||||||
|
- This task doesn't need recursion.
|
||||||
|
- Accessing the first element in a list can be done by [pattern matching][getting-started-pattern-matching].
|
||||||
|
|
||||||
|
## 3. Check if there was a day with no visiting birds
|
||||||
|
|
||||||
|
- Use recursion to iterate over elements in the list until a day with no visiting birds is found.
|
||||||
|
- The base case is an empty list.
|
||||||
|
|
||||||
|
## 4. Calculate the total number of visiting birds
|
||||||
|
|
||||||
|
- Use recursion to iterate over every element in the list.
|
||||||
|
- The base case is an empty list.
|
||||||
|
|
||||||
|
## 5. Calculate the number of busy days
|
||||||
|
|
||||||
|
- Use recursion to iterate over every element in the list.
|
||||||
|
- The base case is an empty list.
|
||||||
|
- A function can have more than one recursive case.
|
||||||
|
- Use a [guard][kernel-equal-or-greater-than] for one of the function clauses.
|
||||||
|
|
||||||
|
[getting-started-recursion]: https://elixir-lang.org/getting-started/recursion.html
|
||||||
|
[getting-started-pattern-matching]: https://elixir-lang.org/getting-started/pattern-matching.html#pattern-matching-1
|
||||||
|
[getting-started-strings]: https://elixir-lang.org/getting-started/recursion.html
|
||||||
|
[elixir-school-recursion]: https://elixirschool.com/blog/recursion/
|
||||||
|
[kernel-equal-or-greater-than]: https://hexdocs.pm/elixir/Kernel.html#%3E=/2
|
|
@ -0,0 +1,93 @@
|
||||||
|
# Bird Count
|
||||||
|
|
||||||
|
Welcome to Bird Count 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
|
||||||
|
|
||||||
|
## Recursion
|
||||||
|
|
||||||
|
Recursive functions are functions that call themselves.
|
||||||
|
|
||||||
|
A recursive function needs to have at least one _base case_ and at least one _recursive case_.
|
||||||
|
|
||||||
|
A _base case_ returns a value without calling the function again. A _recursive case_ calls the function again, modifying the input so that it will at some point match the base case.
|
||||||
|
|
||||||
|
Very often, each case is written in its own function clause.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# base case
|
||||||
|
def count([]), do: 0
|
||||||
|
|
||||||
|
# recursive case
|
||||||
|
def count([_head | tail]), do: 1 + count(tail)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You're an avid bird watcher that keeps track of how many birds have visited your garden on any given day.
|
||||||
|
|
||||||
|
You decided to bring your bird watching to a new level and implement a few tools that will help you track and process the data.
|
||||||
|
|
||||||
|
You have chosen to store the data as a list of integers. The first number in the list is the number of birds that visited your garden today, the second yesterday, and so on.
|
||||||
|
|
||||||
|
## 1. Check how many birds visited today
|
||||||
|
|
||||||
|
Implement the `BirdCount.today/1` function. It should take a list of daily bird counts and return today's count. If the list is empty, it should return `nil`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
BirdCount.today([2, 5, 1])
|
||||||
|
# => 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Increment today's count
|
||||||
|
|
||||||
|
Implement the `BirdCount.increment_day_count/1` function. It should take a list of daily bird counts and increment the today's count by 1. If the list is empty, return `[1]`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
BirdCount.increment_day_count([4, 0, 2])
|
||||||
|
# => [5, 0, 2]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Check if there was a day with no visiting birds
|
||||||
|
|
||||||
|
Implement the `BirdCount.has_day_without_birds?/1` function. It should take a list of daily bird counts. It should return `true` if there was at least one day when no birds visited the garden, and `false` otherwise.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
BirdCount.has_day_without_birds?([2, 0, 4])
|
||||||
|
# => true
|
||||||
|
|
||||||
|
BirdCount.has_day_without_birds?([3, 8, 1, 5])
|
||||||
|
# => false
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Calculate the total number of visiting birds
|
||||||
|
|
||||||
|
Implement the `BirdCount.total/1` function. It should take a list of daily bird counts and return the total number that visited your garden since you started collecting the data.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
BirdCount.total([4, 0, 9, 0, 5])
|
||||||
|
# => 18
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Calculate the number of busy days
|
||||||
|
|
||||||
|
Some days are busier than others. A busy day is one where five or more birds have visited your garden.
|
||||||
|
|
||||||
|
Implement the `BirdCount.busy_days/1` function. It should take a list of daily bird counts and return the number of busy days.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
BirdCount.busy_days([4, 5, 0, 0, 6])
|
||||||
|
# => 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @angelikatyborska
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @neenjaw
|
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule BirdCount do
|
||||||
|
def today([]), do: nil
|
||||||
|
def today([head | _tail]), do: head
|
||||||
|
|
||||||
|
def increment_day_count([]), do: [1]
|
||||||
|
def increment_day_count([head | tail]), do: [head + 1 | tail]
|
||||||
|
|
||||||
|
def has_day_without_birds?([]), do: false
|
||||||
|
def has_day_without_birds?([0 | _tail]), do: true
|
||||||
|
def has_day_without_birds?([_head | tail]), do: has_day_without_birds?(tail)
|
||||||
|
|
||||||
|
def total([]), do: 0
|
||||||
|
def total([head | tail]), do: head + total(tail)
|
||||||
|
|
||||||
|
def busy_days([]), do: 0
|
||||||
|
def busy_days([head | tail]) when head >= 5, do: 1 + busy_days(tail)
|
||||||
|
def busy_days([_head | tail]), do: busy_days(tail)
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule BirdCount.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :bird_count,
|
||||||
|
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,76 @@
|
||||||
|
defmodule BirdCountTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
describe "today/1" do
|
||||||
|
@tag task_id: 1
|
||||||
|
test "returns nil if no bird watching data recorded" do
|
||||||
|
assert BirdCount.today([]) == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "returns today's bird count" do
|
||||||
|
assert BirdCount.today([7]) == 7
|
||||||
|
assert BirdCount.today([2, 4, 11, 10, 6, 8]) == 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "increment_day_count/1" do
|
||||||
|
@tag task_id: 2
|
||||||
|
test "creates entry for today if no bird watching data recorded" do
|
||||||
|
assert BirdCount.increment_day_count([]) == [1]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "adds 1 to today's bird count" do
|
||||||
|
assert BirdCount.increment_day_count([7]) == [8]
|
||||||
|
assert BirdCount.increment_day_count([4, 2, 1, 0, 10]) == [5, 2, 1, 0, 10]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "has_day_without_birds?/1" do
|
||||||
|
@tag task_id: 3
|
||||||
|
test "false if no bird watching data recorded" do
|
||||||
|
assert BirdCount.has_day_without_birds?([]) == false
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "false if there are no zeros in bird watching data" do
|
||||||
|
assert BirdCount.has_day_without_birds?([1]) == false
|
||||||
|
assert BirdCount.has_day_without_birds?([6, 7, 10, 2, 5]) == false
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "true if there are is at least one zero in bird watching data" do
|
||||||
|
assert BirdCount.has_day_without_birds?([0]) == true
|
||||||
|
assert BirdCount.has_day_without_birds?([4, 4, 0, 1]) == true
|
||||||
|
assert BirdCount.has_day_without_birds?([0, 0, 3, 0, 5, 6, 0]) == true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "total/1" do
|
||||||
|
@tag task_id: 4
|
||||||
|
test "zero if no bird watching data recorded" do
|
||||||
|
assert BirdCount.total([]) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "sums up bird counts" do
|
||||||
|
assert BirdCount.total([4]) == 4
|
||||||
|
assert BirdCount.total([3, 0, 0, 4, 4, 0, 0, 10]) == 21
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "busy_days/1" do
|
||||||
|
@tag task_id: 5
|
||||||
|
test "zero if no bird watching data recorded" do
|
||||||
|
assert BirdCount.busy_days([]) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 5
|
||||||
|
test "counts days with bird count of 5 or more" do
|
||||||
|
assert BirdCount.busy_days([1]) == 0
|
||||||
|
assert BirdCount.busy_days([0, 5]) == 1
|
||||||
|
assert BirdCount.busy_days([0, 6, 10, 4, 4, 5, 0]) == 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"angelikatyborska"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"neenjaw"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"lib/high_school_sweetheart.ex"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/high_school_sweetheart_test.exs"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.ex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"language_versions": ">=1.10",
|
||||||
|
"icon": "high-school-sweethearts",
|
||||||
|
"blurb": "Learn about strings and the pipe operator by helping high school sweethearts profess their love on social media via ASCII art."
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"elixir","exercise":"high-school-sweetheart","id":"338aaf1c5adb4b5289ec37cdbaef88c0","url":"https://exercism.org/tracks/elixir/exercises/high-school-sweetheart","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").
|
||||||
|
basic_strings-*.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/high_school_sweetheart.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,36 @@
|
||||||
|
# Hints
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- Read about strings in the official [Getting Started guide][getting-started-strings].
|
||||||
|
- Browse the [functions available in the _String module_][string-module-functions] to discover which operations on strings Elixir's standard library offers.
|
||||||
|
|
||||||
|
## 1. Get the name's first letter
|
||||||
|
|
||||||
|
- There is a [built-in function][string-first] to get the first character from a string.
|
||||||
|
- There are multiple [built-in functions][string-trim] to remove leading, trailing, or leading and trailing whitespaces from a string.
|
||||||
|
|
||||||
|
## 2. Format the first letter as an initial
|
||||||
|
|
||||||
|
- There is a [built-in function][string-upcase] to convert all characters in a string to their uppercase variant.
|
||||||
|
- There is an [operator][kernel-concat] that concatenates two strings.
|
||||||
|
|
||||||
|
## 3. Split the full name into the first name and the last name
|
||||||
|
|
||||||
|
- There is a [built-in function][string-split] that splits a string on whitespace characters.
|
||||||
|
- A few first elements of a list can be assigned to variables by pattern matching on the list.
|
||||||
|
|
||||||
|
## 4. Put the initials inside of the heart
|
||||||
|
|
||||||
|
- There is a special syntax for [interpolating][string-interpolation] an expression inside of a string.
|
||||||
|
- There is a special syntax for writing [multiline strings][heredoc-syntax] without needing to escape newlines.
|
||||||
|
|
||||||
|
[getting-started-strings]: https://elixir-lang.org/getting-started/basic-types.html#strings
|
||||||
|
[string-module-functions]: https://hexdocs.pm/elixir/String.html#functions
|
||||||
|
[string-first]: https://hexdocs.pm/elixir/String.html#first/1
|
||||||
|
[string-trim]: https://hexdocs.pm/elixir/String.html#trim/1
|
||||||
|
[string-upcase]: https://hexdocs.pm/elixir/String.html#upcase/2
|
||||||
|
[string-split]: https://hexdocs.pm/elixir/String.html#split/1
|
||||||
|
[string-interpolation]: https://hexdocs.pm/elixir/String.html#module-interpolation
|
||||||
|
[kernel-concat]: https://hexdocs.pm/elixir/Kernel.html#%3C%3E/2
|
||||||
|
[heredoc-syntax]: https://elixir-examples.github.io/examples/multiline-strings-heredocs
|
|
@ -0,0 +1,140 @@
|
||||||
|
# High School Sweetheart
|
||||||
|
|
||||||
|
Welcome to High School Sweetheart 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
|
||||||
|
|
||||||
|
## Strings
|
||||||
|
|
||||||
|
Strings in Elixir are delimited by double quotes, and they are encoded in UTF-8:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
"Hi!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Strings can be concatenated using the `<>/2` operator:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
"Welcome to" <> " " <> "New York"
|
||||||
|
# => "Welcome to New York"
|
||||||
|
```
|
||||||
|
|
||||||
|
Strings in Elixir support interpolation using the `#{}` syntax:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
"6 * 7 = #{6 * 7}"
|
||||||
|
# => "6 * 7 = 42"
|
||||||
|
```
|
||||||
|
|
||||||
|
To put a newline character in a string, use the `\n` escape code:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
"1\n2\n3\n"
|
||||||
|
```
|
||||||
|
|
||||||
|
To comfortably work with texts with a lot of newlines, use the triple-double-quote heredoc syntax instead:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
"""
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
Elixir provides many functions for working with strings in the `String` module.
|
||||||
|
|
||||||
|
## Pipe Operator
|
||||||
|
|
||||||
|
The `|>` operator is called the pipe operator. It can be used to chain function calls together in such a way that the value returned by the previous function call is passed as the first argument to the next function call.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
"hello"
|
||||||
|
|> String.upcase()
|
||||||
|
|> Kernel.<>("?!")
|
||||||
|
# => "HELLO?!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
In this exercise, you are going to help high school sweethearts profess their love on social media by generating an ASCII heart with their initials:
|
||||||
|
|
||||||
|
```
|
||||||
|
****** ******
|
||||||
|
** ** ** **
|
||||||
|
** ** ** **
|
||||||
|
** * **
|
||||||
|
** **
|
||||||
|
** J. K. + M. B. **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
***
|
||||||
|
*
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. Get the name's first letter
|
||||||
|
|
||||||
|
Implement the `HighSchoolSweetheart.first_letter/1` function. It should take a name and return its first letter. It should clean up any unnecessary whitespace from the name.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
HighSchoolSweetheart.first_letter("Jane")
|
||||||
|
# => "J"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Format the first letter as an initial
|
||||||
|
|
||||||
|
Implement the `HighSchoolSweetheart.initial/1` function. It should take a name and return its first letter, uppercase, followed by a dot. Make sure to reuse `HighSchoolSweetheart.first_letter/1` that you defined in the previous step.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
HighSchoolSweetheart.initial("Robert")
|
||||||
|
# => "R."
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Split the full name into the first name and the last name
|
||||||
|
|
||||||
|
Implement the `HighSchoolSweetheart.initials/1` function. It should take a full name, consisting of a first name and a last name separated by a space, and return the initials. Make sure to reuse `HighSchoolSweetheart.initial/1` that you defined in the previous step.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
HighSchoolSweetheart.initials("Lance Green")
|
||||||
|
# => "L. G."
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Put the initials inside of the heart
|
||||||
|
|
||||||
|
Implement the `HighSchoolSweetheart.pair/2` function. It should take two full names and return the initials inside an ASCII heart. Make sure to reuse `HighSchoolSweetheart.initials/1` that you defined in the previous step.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
HighSchoolSweetheart.pair("Blake Miller", "Riley Lewis")
|
||||||
|
# => """
|
||||||
|
# ****** ******
|
||||||
|
# ** ** ** **
|
||||||
|
# ** ** ** **
|
||||||
|
# ** * **
|
||||||
|
# ** **
|
||||||
|
# ** B. M. + R. L. **
|
||||||
|
# ** **
|
||||||
|
# ** **
|
||||||
|
# ** **
|
||||||
|
# ** **
|
||||||
|
# ** **
|
||||||
|
# ** **
|
||||||
|
# ***
|
||||||
|
# *
|
||||||
|
# """
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @angelikatyborska
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @neenjaw
|
|
@ -0,0 +1,42 @@
|
||||||
|
defmodule HighSchoolSweetheart do
|
||||||
|
def first_letter(name) do
|
||||||
|
name
|
||||||
|
|> String.trim()
|
||||||
|
|> String.first()
|
||||||
|
end
|
||||||
|
|
||||||
|
def initial(name) do
|
||||||
|
name
|
||||||
|
|> first_letter()
|
||||||
|
|> String.upcase()
|
||||||
|
|> Kernel.<>(".")
|
||||||
|
end
|
||||||
|
|
||||||
|
def initials(full_name) do
|
||||||
|
full_name
|
||||||
|
|> String.split(" ")
|
||||||
|
|> Enum.map(&initial/1)
|
||||||
|
|> Enum.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
def pair(full_name1, full_name2) do
|
||||||
|
i1 = initials(full_name1)
|
||||||
|
i2 = initials(full_name2)
|
||||||
|
"""
|
||||||
|
****** ******
|
||||||
|
** ** ** **
|
||||||
|
** ** ** **
|
||||||
|
** * **
|
||||||
|
** **
|
||||||
|
** #{i1} + #{i2} **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
***
|
||||||
|
*
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule HighSchoolSweetheart.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :high_school_sweetheart,
|
||||||
|
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,62 @@
|
||||||
|
defmodule HighSchoolSweetheartTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
describe "first_letter/1" do
|
||||||
|
@tag task_id: 1
|
||||||
|
test "it gets the first letter" do
|
||||||
|
assert HighSchoolSweetheart.first_letter("Mary") == "M"
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "it doesn't change the letter's case" do
|
||||||
|
assert HighSchoolSweetheart.first_letter("john") == "j"
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "it strips extra whitespace" do
|
||||||
|
assert HighSchoolSweetheart.first_letter("\n\t Sarah ") == "S"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "initial/1" do
|
||||||
|
@tag task_id: 2
|
||||||
|
test "it gets the first letter and appends a dot" do
|
||||||
|
assert HighSchoolSweetheart.initial("Betty") == "B."
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "it uppercases the first letter" do
|
||||||
|
assert HighSchoolSweetheart.initial("james") == "J."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "initials/1" do
|
||||||
|
@tag task_id: 3
|
||||||
|
test "returns the initials for the first name and the last name" do
|
||||||
|
assert HighSchoolSweetheart.initials("Linda Miller") == "L. M."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "pair/2" do
|
||||||
|
@tag task_id: 4
|
||||||
|
test "prints the pair's initials inside a heart" do
|
||||||
|
assert HighSchoolSweetheart.pair("Avery Bryant", "Charlie Dixon") ==
|
||||||
|
"""
|
||||||
|
****** ******
|
||||||
|
** ** ** **
|
||||||
|
** ** ** **
|
||||||
|
** * **
|
||||||
|
** **
|
||||||
|
** A. B. + C. D. **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
** **
|
||||||
|
***
|
||||||
|
*
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
Loading…
Reference in New Issue