lunchtime practice

This commit is contained in:
Ben Harris 2018-03-12 13:58:40 -04:00
parent 9df0f3b5f1
commit c6720da38d
19 changed files with 1075 additions and 0 deletions

View File

@ -0,0 +1,52 @@
# Flatten Array
Take a nested list and return a single flattened list with all values except nil/null.
The challenge is to write a function that accepts an arbitrarily-deep nested list-like structure and returns a flattened structure without any nil/null values.
For Example
input: [1,[2,3,null,4],[null],5]
output: [1,2,3,4,5]
## Running tests
Execute the tests with:
```bash
$ elixir flatten_array_test.exs
```
### Pending tests
In the test suites, all but the first test have been 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
```
Or, you can enable all the tests by commenting out the
`ExUnit.configure` line in the test suite.
```elixir
# ExUnit.configure exclude: :pending, trace: true
```
For more detailed information about the Elixir track, please
see the [help page](http://exercism.io/languages/elixir).
## Source
Interview Question [https://reference.wolfram.com/language/ref/Flatten.html](https://reference.wolfram.com/language/ref/Flatten.html)
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

View File

@ -0,0 +1,23 @@
defmodule FlattenArray do
@doc """
Accept a list and return the list flattened without nil values.
## Examples
iex> FlattenArray.flatten([1, [2], 3, nil])
[1,2,3]
iex> FlattenArray.flatten([nil, nil])
[]
"""
@spec flatten(list) :: list
def flatten(list) do
flat(list, [])
end
defp flat([], rest), do: rest
defp flat([head | tail], rest) when is_list(head), do: flat(tail, rest ++ flat(head, []))
defp flat([head | tail], rest) when is_nil(head), do: flat(tail, rest)
defp flat([head | tail], rest), do: flat(tail, rest ++ [head])
end

View File

@ -0,0 +1,39 @@
if !System.get_env("EXERCISM_TEST_EXAMPLES") do
Code.load_file("flatten_array.exs", __DIR__)
end
ExUnit.start()
ExUnit.configure(trace: true)
defmodule FlattenArrayTest do
use ExUnit.Case
test "returns original list if there is nothing to flatten" do
assert FlattenArray.flatten([1, 2, 3]) == [1, 2, 3]
end
@tag :pending
test "flattens an empty nested list" do
assert FlattenArray.flatten([[]]) == []
end
@tag :pending
test "flattens a nested list" do
assert FlattenArray.flatten([1, [2, [3], 4], 5, [6, [7, 8]]]) == [1, 2, 3, 4, 5, 6, 7, 8]
end
@tag :pending
test "removes nil from list" do
assert FlattenArray.flatten([1, nil, 2]) == [1, 2]
end
@tag :pending
test "removes nil from a nested list" do
assert FlattenArray.flatten([1, [2, nil, 4], 5]) == [1, 2, 4, 5]
end
@tag :pending
test "returns an empty list if all values in nested list are nil" do
assert FlattenArray.flatten([nil, [nil], [nil, [nil]]]) == []
end
end

View File

@ -10,6 +10,13 @@ defmodule School do
"""
@spec add(map, String.t(), integer) :: map
def add(db, name, grade) do
update_in(db, [grade], fn i ->
case i do
nil -> [name]
_ -> [name | i]
end
|> Enum.sort()
end)
end
@doc """
@ -17,6 +24,7 @@ defmodule School do
"""
@spec grade(map, integer) :: [String.t()]
def grade(db, grade) do
db[grade] || []
end
@doc """
@ -24,5 +32,6 @@ defmodule School do
"""
@spec sort(map) :: [{integer, [String.t()]}]
def sort(db) do
Enum.sort(db)
end
end

77
elixir/hamming/README.md Normal file
View File

@ -0,0 +1,77 @@
# Hamming
Calculate the Hamming difference between two DNA strands.
A mutation is simply a mistake that occurs during the creation or
copying of a nucleic acid, in particular DNA. Because nucleic acids are
vital to cellular functions, mutations tend to cause a ripple effect
throughout the cell. Although mutations are technically mistakes, a very
rare mutation may equip the cell with a beneficial attribute. In fact,
the macro effects of evolution are attributable by the accumulated
result of beneficial microscopic mutations over many generations.
The simplest and most common type of nucleic acid mutation is a point
mutation, which replaces one base with another at a single nucleotide.
By counting the number of differences between two homologous DNA strands
taken from different genomes with a common ancestor, we get a measure of
the minimum number of point mutations that could have occurred on the
evolutionary path between the two strands.
This is called the 'Hamming distance'.
It is found by comparing two DNA strands and counting how many of the
nucleotides are different from their equivalent in the other string.
GAGCCTACTAACGGGAT
CATCGTAATGACGGCCT
^ ^ ^ ^ ^ ^^
The Hamming distance between these two DNA strands is 7.
# Implementation notes
The Hamming distance is only defined for sequences of equal length. This means
that based on the definition, each language could deal with getting sequences
of equal length differently.
## Running tests
Execute the tests with:
```bash
$ elixir hamming_test.exs
```
### Pending tests
In the test suites, all but the first test have been 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
```
Or, you can enable all the tests by commenting out the
`ExUnit.configure` line in the test suite.
```elixir
# ExUnit.configure exclude: :pending, trace: true
```
For more detailed information about the Elixir track, please
see the [help page](http://exercism.io/languages/elixir).
## Source
The Calculating Point Mutations problem at Rosalind [http://rosalind.info/problems/hamm/](http://rosalind.info/problems/hamm/)
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

View File

@ -0,0 +1,18 @@
defmodule Hamming do
@doc """
Returns number of differences between two strands of DNA, known as the Hamming Distance.
## Examples
iex> Hamming.hamming_distance('AAGTCATA', 'TAGCGATC')
{:ok, 4}
"""
@spec hamming_distance([char], [char]) :: non_neg_integer
def hamming_distance(s1, s2) when length(s1) != length(s2) do
{:error, "Lists must be the same length"}
end
def hamming_distance(s1, s2) do
{:ok, Enum.count(0..length(s1), &(Enum.at(s1, &1) != Enum.at(s2, &1)))}
end
end

View File

@ -0,0 +1,43 @@
if !System.get_env("EXERCISM_TEST_EXAMPLES") do
Code.load_file("hamming.exs", __DIR__)
end
ExUnit.start()
ExUnit.configure(trace: true)
defmodule HammingTest do
use ExUnit.Case
test "no difference between empty strands" do
assert Hamming.hamming_distance('', '') == {:ok, 0}
end
@tag :pending
test "no difference between identical strands" do
assert Hamming.hamming_distance('GGACTGA', 'GGACTGA') == {:ok, 0}
end
@tag :pending
test "small hamming distance in middle somewhere" do
assert Hamming.hamming_distance('GGACG', 'GGTCG') == {:ok, 1}
end
@tag :pending
test "distance with same nucleotides in different locations" do
assert Hamming.hamming_distance('TAG', 'GAT') == {:ok, 2}
end
@tag :pending
test "larger distance" do
assert Hamming.hamming_distance('ACCAGGG', 'ACTATGG') == {:ok, 2}
end
@tag :pending
test "hamming distance is undefined for strands of different lengths" do
assert {:error, "Lists must be the same length"} =
Hamming.hamming_distance('AAAC', 'TAGGGGAGGCTAGCGGTAGGAC')
assert {:error, "Lists must be the same length"} =
Hamming.hamming_distance('GACTACGGACAGGACACC', 'GACATCGC')
end
end

View File

@ -0,0 +1,101 @@
# Kindergarten Garden
Given a diagram, determine which plants each child in the kindergarten class is
responsible for.
The kindergarten class is learning about growing plants. The teacher
thought it would be a good idea to give them actual seeds, plant them in
actual dirt, and grow actual plants.
They've chosen to grow grass, clover, radishes, and violets.
To this end, the children have put little cups along the window sills, and
planted one type of plant in each cup, choosing randomly from the available
types of seeds.
```text
[window][window][window]
........................ # each dot represents a cup
........................
```
There are 12 children in the class:
- Alice, Bob, Charlie, David,
- Eve, Fred, Ginny, Harriet,
- Ileana, Joseph, Kincaid, and Larry.
Each child gets 4 cups, two on each row. Their teacher assigns cups to
the children alphabetically by their names.
The following diagram represents Alice's plants:
```text
[window][window][window]
VR......................
RG......................
```
In the first row, nearest the windows, she has a violet and a radish. In the
second row she has a radish and some grass.
Your program will be given the plants from left-to-right starting with
the row nearest the windows. From this, it should be able to determine
which plants belong to each student.
For example, if it's told that the garden looks like so:
```text
[window][window][window]
VRCGVVRVCGGCCGVRGCVCGCGV
VRCCCGCRRGVCGCRVVCVGCGCV
```
Then if asked for Alice's plants, it should provide:
- Violets, radishes, violets, radishes
While asking for Bob's plants would yield:
- Clover, grass, clover, clover
## Running tests
Execute the tests with:
```bash
$ elixir kindergarten_garden_test.exs
```
### Pending tests
In the test suites, all but the first test have been 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
```
Or, you can enable all the tests by commenting out the
`ExUnit.configure` line in the test suite.
```elixir
# ExUnit.configure exclude: :pending, trace: true
```
For more detailed information about the Elixir track, please
see the [help page](http://exercism.io/languages/elixir).
## Source
Random musings during airplane trip. [http://jumpstartlab.com](http://jumpstartlab.com)
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

View File

@ -0,0 +1,14 @@
defmodule Garden do
@doc """
Accepts a string representing the arrangement of cups on a windowsill and a
list with names of students in the class. The student names list does not
have to be in alphabetical order.
It decodes that string into the various gardens for each student and returns
that information in a map.
"""
@spec info(String.t(), list) :: map
def info(info_string, student_names) do
end
end

View File

@ -0,0 +1,90 @@
if !System.get_env("EXERCISM_TEST_EXAMPLES") do
Code.load_file("garden.exs", __DIR__)
end
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)
defmodule GardenTest do
use ExUnit.Case
test "gets the garden for Alice with just her plants" do
garden_info = Garden.info("RC\nGG")
assert garden_info.alice == {:radishes, :clover, :grass, :grass}
end
@tag :pending
test "gets another garden for Alice with just her plants" do
garden_info = Garden.info("VC\nRC")
assert garden_info.alice == {:violets, :clover, :radishes, :clover}
end
@tag :pending
test "returns an empty tuple if the child has no plants" do
garden_info = Garden.info("VC\nRC")
assert garden_info.bob == {}
end
@tag :pending
test "gets the garden for Bob" do
garden_info = Garden.info("VVCG\nVVRC")
assert garden_info.bob == {:clover, :grass, :radishes, :clover}
end
@tag :pending
test "gets the garden for all students" do
garden_info = Garden.info("VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV")
assert garden_info.alice == {:violets, :radishes, :violets, :radishes}
assert garden_info.bob == {:clover, :grass, :clover, :clover}
assert garden_info.charlie == {:violets, :violets, :clover, :grass}
assert garden_info.david == {:radishes, :violets, :clover, :radishes}
assert garden_info.eve == {:clover, :grass, :radishes, :grass}
assert garden_info.fred == {:grass, :clover, :violets, :clover}
assert garden_info.ginny == {:clover, :grass, :grass, :clover}
assert garden_info.harriet == {:violets, :radishes, :radishes, :violets}
assert garden_info.ileana == {:grass, :clover, :violets, :clover}
assert garden_info.joseph == {:violets, :clover, :violets, :grass}
assert garden_info.kincaid == {:grass, :clover, :clover, :grass}
assert garden_info.larry == {:grass, :violets, :clover, :violets}
end
@tag :pending
test "accepts custom child names" do
garden_info = Garden.info("VC\nRC", [:nate, :maggie])
assert garden_info.maggie == {:violets, :clover, :radishes, :clover}
assert garden_info.nate == {}
end
@tag :pending
test "gets the garden for all students with custom child names" do
names = [
:maggie,
:nate,
:xander,
:ophelia,
:pete,
:reggie,
:sylvia,
:tanner,
:ursula,
:victor,
:winnie,
:ynold
]
garden_string = "VRCGVVRVCGGCCGVRGCVCGCGV\nVRCCCGCRRGVCGCRVVCVGCGCV"
garden_info = Garden.info(garden_string, names)
assert garden_info.maggie == {:violets, :radishes, :violets, :radishes}
assert garden_info.nate == {:clover, :grass, :clover, :clover}
assert garden_info.ophelia == {:violets, :violets, :clover, :grass}
assert garden_info.pete == {:radishes, :violets, :clover, :radishes}
assert garden_info.reggie == {:clover, :grass, :radishes, :grass}
assert garden_info.sylvia == {:grass, :clover, :violets, :clover}
assert garden_info.tanner == {:clover, :grass, :grass, :clover}
assert garden_info.ursula == {:violets, :radishes, :radishes, :violets}
assert garden_info.victor == {:grass, :clover, :violets, :clover}
assert garden_info.winnie == {:violets, :clover, :violets, :grass}
assert garden_info.xander == {:grass, :clover, :clover, :grass}
assert garden_info.ynold == {:grass, :violets, :clover, :violets}
end
end

68
elixir/leap/README.md Normal file
View File

@ -0,0 +1,68 @@
# Leap
Given a year, report if it is a leap year.
The tricky thing here is that a leap year in the Gregorian calendar occurs:
```text
on every year that is evenly divisible by 4
except every year that is evenly divisible by 100
unless the year is also evenly divisible by 400
```
For example, 1997 is not a leap year, but 1996 is. 1900 is not a leap
year, but 2000 is.
If your language provides a method in the standard library that does
this look-up, pretend it doesn't exist and implement it yourself.
## Notes
Though our exercise adopts some very simple rules, there is more to
learn!
For a delightful, four minute explanation of the whole leap year
phenomenon, go watch [this youtube video][video].
[video]: http://www.youtube.com/watch?v=xX96xng7sAE
## Running tests
Execute the tests with:
```bash
$ elixir leap_test.exs
```
### Pending tests
In the test suites, all but the first test have been 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
```
Or, you can enable all the tests by commenting out the
`ExUnit.configure` line in the test suite.
```elixir
# ExUnit.configure exclude: :pending, trace: true
```
For more detailed information about the Elixir track, please
see the [help page](http://exercism.io/languages/elixir).
## Source
JavaRanch Cattle Drive, exercise 3 [http://www.javaranch.com/leap.jsp](http://www.javaranch.com/leap.jsp)
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

15
elixir/leap/leap.exs Normal file
View File

@ -0,0 +1,15 @@
defmodule Year do
@doc """
Returns whether 'year' is a leap year.
A leap year occurs:
on every year that is evenly divisible by 4
except every year that is evenly divisible by 100
unless the year is also evenly divisible by 400
"""
@spec leap_year?(non_neg_integer) :: boolean
def leap_year?(year) do
rem(year, 400) == 0 || (rem(year, 4) == 0 && rem(year, 100) != 0)
end
end

30
elixir/leap/leap_test.exs Normal file
View File

@ -0,0 +1,30 @@
if !System.get_env("EXERCISM_TEST_EXAMPLES") do
Code.load_file("leap.exs", __DIR__)
end
ExUnit.start()
ExUnit.configure(trace: true)
defmodule LeapTest do
use ExUnit.Case
# @tag :pending
test "vanilla leap year" do
assert Year.leap_year?(1996)
end
@tag :pending
test "any old year" do
refute Year.leap_year?(1997), "1997 is not a leap year."
end
@tag :pending
test "century" do
refute Year.leap_year?(1900), "1900 is not a leap year."
end
@tag :pending
test "exceptional century" do
assert Year.leap_year?(2400)
end
end

44
elixir/list-ops/README.md Normal file
View File

@ -0,0 +1,44 @@
# List Ops
Implement basic list operations.
In functional languages list operations like `length`, `map`, and
`reduce` are very common. Implement a series of basic list operations,
without using existing functions.
## Running tests
Execute the tests with:
```bash
$ elixir list_ops_test.exs
```
### Pending tests
In the test suites, all but the first test have been 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
```
Or, you can enable all the tests by commenting out the
`ExUnit.configure` line in the test suite.
```elixir
# ExUnit.configure exclude: :pending, trace: true
```
For more detailed information about the Elixir track, please
see the [help page](http://exercism.io/languages/elixir).
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

View File

@ -0,0 +1,42 @@
defmodule ListOps do
@doc """
so apparently you can build the rest of these
functions with *just* `reduce`... neat!
"""
@type acc :: any
@spec reduce(list, acc, (any, acc -> acc)) :: acc
def reduce([], acc, _f), do: acc
def reduce([h | t], acc, f), do: reduce(t, f.(h, acc), f)
@spec count(list) :: non_neg_integer
def count(l) do
reduce(l, 0, fn _, acc -> acc + 1 end)
end
@spec reverse(list) :: list
def reverse(l) do
reduce(l, [], &[&1 | &2])
end
@spec map(list, (any -> any)) :: list
def map(l, f) do
reduce(reverse(l), [], &[f.(&1) | &2])
end
@spec filter(list, (any -> as_boolean(term))) :: list
def filter(l, f) do
reduce(reverse(l), [], fn x, acc ->
if f.(x), do: [x | acc], else: acc
end)
end
@spec append(list, list) :: list
def append(a, b) do
reduce(reverse(a), b, &[&1 | &2])
end
@spec concat([[any]]) :: [any]
def concat(ll) do
reduce(reverse(ll), [], &append(&1, &2))
end
end

View File

@ -0,0 +1,142 @@
if !System.get_env("EXERCISM_TEST_EXAMPLES") do
Code.load_file("list_ops.exs", __DIR__)
end
ExUnit.start()
ExUnit.configure(trace: true)
defmodule ListOpsTest do
alias ListOps, as: L
use ExUnit.Case
defp odd?(n), do: rem(n, 2) == 1
# @tag :pending
test "count of empty list" do
assert L.count([]) == 0
end
@tag :pending
test "count of normal list" do
assert L.count([1, 3, 5, 7]) == 4
end
@tag :pending
test "count of huge list" do
assert L.count(Enum.to_list(1..1_000_000)) == 1_000_000
end
@tag :pending
test "reverse of empty list" do
assert L.reverse([]) == []
end
@tag :pending
test "reverse of normal list" do
assert L.reverse([1, 3, 5, 7]) == [7, 5, 3, 1]
end
@tag :pending
test "reverse of huge list" do
assert L.reverse(Enum.to_list(1..1_000_000)) == Enum.to_list(1_000_000..1)
end
@tag :pending
test "map of empty list" do
assert L.map([], &(&1 + 1)) == []
end
@tag :pending
test "map of normal list" do
assert L.map([1, 3, 5, 7], &(&1 + 1)) == [2, 4, 6, 8]
end
@tag :pending
test "map of huge list" do
assert L.map(Enum.to_list(1..1_000_000), &(&1 + 1)) == Enum.to_list(2..1_000_001)
end
@tag :pending
test "filter of empty list" do
assert L.filter([], &odd?/1) == []
end
@tag :pending
test "filter of normal list" do
assert L.filter([1, 2, 3, 4], &odd?/1) == [1, 3]
end
@tag :pending
test "filter of huge list" do
assert L.filter(Enum.to_list(1..1_000_000), &odd?/1) == Enum.map(1..500_000, &(&1 * 2 - 1))
end
@tag :pending
test "reduce of empty list" do
assert L.reduce([], 0, &(&1 + &2)) == 0
end
@tag :pending
test "reduce of normal list" do
assert L.reduce([1, 2, 3, 4], -3, &(&1 + &2)) == 7
end
@tag :pending
test "reduce of huge list" do
assert L.reduce(Enum.to_list(1..1_000_000), 0, &(&1 + &2)) ==
Enum.reduce(1..1_000_000, 0, &(&1 + &2))
end
@tag :pending
test "reduce with non-commutative function" do
assert L.reduce([1, 2, 3, 4], 10, fn x, acc -> acc - x end) == 0
end
@tag :pending
test "append of empty lists" do
assert L.append([], []) == []
end
@tag :pending
test "append of empty and non-empty list" do
assert L.append([], [1, 2, 3, 4]) == [1, 2, 3, 4]
end
@tag :pending
test "append of non-empty and empty list" do
assert L.append([1, 2, 3, 4], []) == [1, 2, 3, 4]
end
@tag :pending
test "append of non-empty lists" do
assert L.append([1, 2, 3], [4, 5]) == [1, 2, 3, 4, 5]
end
@tag :pending
test "append of huge lists" do
assert L.append(Enum.to_list(1..1_000_000), Enum.to_list(1_000_001..2_000_000)) ==
Enum.to_list(1..2_000_000)
end
@tag :pending
test "concat of empty list of lists" do
assert L.concat([]) == []
end
@tag :pending
test "concat of normal list of lists" do
assert L.concat([[1, 2], [3], [], [4, 5, 6]]) == [1, 2, 3, 4, 5, 6]
end
@tag :pending
test "concat of huge list of small lists" do
assert L.concat(Enum.map(1..1_000_000, &[&1])) == Enum.to_list(1..1_000_000)
end
@tag :pending
test "concat of small list of huge lists" do
assert L.concat(Enum.map(0..9, &Enum.to_list((&1 * 100_000 + 1)..((&1 + 1) * 100_000)))) ==
Enum.to_list(1..1_000_000)
end
end

107
elixir/tournament/README.md Normal file
View File

@ -0,0 +1,107 @@
# Tournament
Tally the results of a small football competition.
Based on an input file containing which team played against which and what the
outcome was, create a file with a table like this:
```text
Team | MP | W | D | L | P
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 3 | 1 | 0 | 2 | 3
Courageous Californians | 3 | 0 | 1 | 2 | 1
```
What do those abbreviations mean?
- MP: Matches Played
- W: Matches Won
- D: Matches Drawn (Tied)
- L: Matches Lost
- P: Points
A win earns a team 3 points. A draw earns 1. A loss earns 0.
The outcome should be ordered by points, descending. In case of a tie, teams are ordered alphabetically.
###
Input
Your tallying program will receive input that looks like:
```text
Allegoric Alaskans;Blithering Badgers;win
Devastating Donkeys;Courageous Californians;draw
Devastating Donkeys;Allegoric Alaskans;win
Courageous Californians;Blithering Badgers;loss
Blithering Badgers;Devastating Donkeys;loss
Allegoric Alaskans;Courageous Californians;win
```
The result of the match refers to the first team listed. So this line
```text
Allegoric Alaskans;Blithering Badgers;win
```
Means that the Allegoric Alaskans beat the Blithering Badgers.
This line:
```text
Courageous Californians;Blithering Badgers;loss
```
Means that the Blithering Badgers beat the Courageous Californians.
And this line:
```text
Devastating Donkeys;Courageous Californians;draw
```
Means that the Devastating Donkeys and Courageous Californians tied.
Formatting the output is easy with `String`'s padding functions. All number
columns can be left-padded with spaces to a width of 2 characters, while the
team name column can be right-padded with spaces to a width of 30.
## Running tests
Execute the tests with:
```bash
$ elixir tournament_test.exs
```
### Pending tests
In the test suites, all but the first test have been 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
```
Or, you can enable all the tests by commenting out the
`ExUnit.configure` line in the test suite.
```elixir
# ExUnit.configure exclude: :pending, trace: true
```
For more detailed information about the Elixir track, please
see the [help page](http://exercism.io/languages/elixir).
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

View File

@ -0,0 +1,58 @@
defmodule Tournament do
defmodule Stat do
defstruct mp: 0, w: 0, d: 0, l: 0, p: 0
end
@doc """
Given `input` lines representing two teams and whether the first of them won,
lost, or reached a draw, separated by semicolons, calculate the statistics
for each team's number of games played, won, drawn, lost, and total points
for the season, and return a nicely-formatted string table.
A win earns a team 3 points, a draw earns 1 point, and a loss earns nothing.
Order the outcome by most total points for the season, and settle ties by
listing the teams in alphabetical order.
"""
@spec tally(input :: list(String.t())) :: String.t()
def tally(input) do
import String, only: [pad_trailing: 2, pad_leading: 2]
input
|> Enum.map(&String.split(&1, ";"))
|> Enum.filter(&(length(&1) == 3 && Enum.at(&1, 2) in ["win", "loss", "draw"]))
|> Enum.reduce(%{}, fn [team1, team2, outcome], acc ->
acc
|> Map.put(team1, stats(Map.get(acc, team1, %Stat{}), outcome))
|> Map.put(team2, stats(Map.get(acc, team2, %Stat{}), opposite_outcome(outcome)))
end)
|> Enum.sort_by(fn {name, hist} -> {-hist.p, name} end)
|> Enum.reduce("Team | MP | W | D | L | P", fn {name, hist}, acc ->
acc <>
"\n" <>
Enum.join(
[
pad_trailing(name, 30),
pad_leading(to_string(hist.mp), 2),
pad_leading(to_string(hist.w), 2),
pad_leading(to_string(hist.d), 2),
pad_leading(to_string(hist.l), 2),
pad_leading(to_string(hist.p), 2)
],
" | "
)
end)
end
defp stats(team, "win"), do: %{team | mp: team.mp + 1, w: team.w + 1, p: team.p + 3}
defp stats(team, "loss"), do: %{team | mp: team.mp + 1, l: team.l + 1}
defp stats(team, "draw"), do: %{team | mp: team.mp + 1, d: team.d + 1, p: team.p + 1}
defp opposite_outcome(outcome) do
case outcome do
"win" -> "loss"
"loss" -> "win"
"draw" -> "draw"
end
end
end

View File

@ -0,0 +1,103 @@
if !System.get_env("EXERCISM_TEST_EXAMPLES") do
Code.load_file("tournament.exs", __DIR__)
end
ExUnit.start()
ExUnit.configure(trace: true)
defmodule TournamentTest do
use ExUnit.Case
# @tag :pending
test "typical input" do
input = [
"Allegoric Alaskans;Blithering Badgers;win",
"Devastating Donkeys;Courageous Californians;draw",
"Devastating Donkeys;Allegoric Alaskans;win",
"Courageous Californians;Blithering Badgers;loss",
"Blithering Badgers;Devastating Donkeys;loss",
"Allegoric Alaskans;Courageous Californians;win"
]
expected =
"""
Team | MP | W | D | L | P
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 3 | 1 | 0 | 2 | 3
Courageous Californians | 3 | 0 | 1 | 2 | 1
"""
|> String.trim()
assert Tournament.tally(input) == expected
end
@tag :pending
test "incomplete competition (not all pairs have played)" do
input = [
"Allegoric Alaskans;Blithering Badgers;loss",
"Devastating Donkeys;Allegoric Alaskans;loss",
"Courageous Californians;Blithering Badgers;draw",
"Allegoric Alaskans;Courageous Californians;win"
]
expected =
"""
Team | MP | W | D | L | P
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 2 | 1 | 1 | 0 | 4
Courageous Californians | 2 | 0 | 1 | 1 | 1
Devastating Donkeys | 1 | 0 | 0 | 1 | 0
"""
|> String.trim()
assert Tournament.tally(input) == expected
end
@tag :pending
test "ties broken alphabetically" do
input = [
"Courageous Californians;Devastating Donkeys;win",
"Allegoric Alaskans;Blithering Badgers;win",
"Devastating Donkeys;Allegoric Alaskans;loss",
"Courageous Californians;Blithering Badgers;win",
"Blithering Badgers;Devastating Donkeys;draw",
"Allegoric Alaskans;Courageous Californians;draw"
]
expected =
"""
Team | MP | W | D | L | P
Allegoric Alaskans | 3 | 2 | 1 | 0 | 7
Courageous Californians | 3 | 2 | 1 | 0 | 7
Blithering Badgers | 3 | 0 | 1 | 2 | 1
Devastating Donkeys | 3 | 0 | 1 | 2 | 1
"""
|> String.trim()
assert Tournament.tally(input) == expected
end
@tag :pending
test "mostly invalid lines" do
# Invalid input lines in an otherwise-valid game still results in valid
# output.
input = [
"",
"Allegoric Alaskans@Blithering Badgers;draw",
"Blithering Badgers;Devastating Donkeys;loss",
"Devastating Donkeys;Courageous Californians;win;5",
"Courageous Californians;Allegoric Alaskans;los"
]
expected =
"""
Team | MP | W | D | L | P
Devastating Donkeys | 1 | 1 | 0 | 0 | 3
Blithering Badgers | 1 | 0 | 0 | 1 | 0
"""
|> String.trim()
assert Tournament.tally(input) == expected
end
end