From c6720da38d6019ee572c340d0ea4af2c43ad6f50 Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Mon, 12 Mar 2018 13:58:40 -0400 Subject: [PATCH] lunchtime practice --- elixir/flatten-array/README.md | 52 +++++++ elixir/flatten-array/flatten_array.exs | 23 ++++ elixir/flatten-array/flatten_array_test.exs | 39 ++++++ elixir/grade-school/school.exs | 9 ++ elixir/hamming/README.md | 77 +++++++++++ elixir/hamming/hamming.exs | 18 +++ elixir/hamming/hamming_test.exs | 43 ++++++ elixir/kindergarten-garden/README.md | 101 ++++++++++++++ elixir/kindergarten-garden/garden.exs | 14 ++ elixir/kindergarten-garden/garden_test.exs | 90 +++++++++++++ elixir/leap/README.md | 68 ++++++++++ elixir/leap/leap.exs | 15 +++ elixir/leap/leap_test.exs | 30 +++++ elixir/list-ops/README.md | 44 ++++++ elixir/list-ops/list_ops.exs | 42 ++++++ elixir/list-ops/list_ops_test.exs | 142 ++++++++++++++++++++ elixir/tournament/README.md | 107 +++++++++++++++ elixir/tournament/tournament.exs | 58 ++++++++ elixir/tournament/tournament_test.exs | 103 ++++++++++++++ 19 files changed, 1075 insertions(+) create mode 100644 elixir/flatten-array/README.md create mode 100644 elixir/flatten-array/flatten_array.exs create mode 100644 elixir/flatten-array/flatten_array_test.exs create mode 100644 elixir/hamming/README.md create mode 100644 elixir/hamming/hamming.exs create mode 100644 elixir/hamming/hamming_test.exs create mode 100644 elixir/kindergarten-garden/README.md create mode 100644 elixir/kindergarten-garden/garden.exs create mode 100644 elixir/kindergarten-garden/garden_test.exs create mode 100644 elixir/leap/README.md create mode 100644 elixir/leap/leap.exs create mode 100644 elixir/leap/leap_test.exs create mode 100644 elixir/list-ops/README.md create mode 100644 elixir/list-ops/list_ops.exs create mode 100644 elixir/list-ops/list_ops_test.exs create mode 100644 elixir/tournament/README.md create mode 100644 elixir/tournament/tournament.exs create mode 100644 elixir/tournament/tournament_test.exs diff --git a/elixir/flatten-array/README.md b/elixir/flatten-array/README.md new file mode 100644 index 0000000..df3eaad --- /dev/null +++ b/elixir/flatten-array/README.md @@ -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. diff --git a/elixir/flatten-array/flatten_array.exs b/elixir/flatten-array/flatten_array.exs new file mode 100644 index 0000000..e01b643 --- /dev/null +++ b/elixir/flatten-array/flatten_array.exs @@ -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 diff --git a/elixir/flatten-array/flatten_array_test.exs b/elixir/flatten-array/flatten_array_test.exs new file mode 100644 index 0000000..29d6f67 --- /dev/null +++ b/elixir/flatten-array/flatten_array_test.exs @@ -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 diff --git a/elixir/grade-school/school.exs b/elixir/grade-school/school.exs index 4f20c59..0cdabf3 100644 --- a/elixir/grade-school/school.exs +++ b/elixir/grade-school/school.exs @@ -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 diff --git a/elixir/hamming/README.md b/elixir/hamming/README.md new file mode 100644 index 0000000..f716401 --- /dev/null +++ b/elixir/hamming/README.md @@ -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. diff --git a/elixir/hamming/hamming.exs b/elixir/hamming/hamming.exs new file mode 100644 index 0000000..8a45e47 --- /dev/null +++ b/elixir/hamming/hamming.exs @@ -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 diff --git a/elixir/hamming/hamming_test.exs b/elixir/hamming/hamming_test.exs new file mode 100644 index 0000000..eec2d4c --- /dev/null +++ b/elixir/hamming/hamming_test.exs @@ -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 diff --git a/elixir/kindergarten-garden/README.md b/elixir/kindergarten-garden/README.md new file mode 100644 index 0000000..dc1e545 --- /dev/null +++ b/elixir/kindergarten-garden/README.md @@ -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. diff --git a/elixir/kindergarten-garden/garden.exs b/elixir/kindergarten-garden/garden.exs new file mode 100644 index 0000000..d3f00ed --- /dev/null +++ b/elixir/kindergarten-garden/garden.exs @@ -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 diff --git a/elixir/kindergarten-garden/garden_test.exs b/elixir/kindergarten-garden/garden_test.exs new file mode 100644 index 0000000..d635314 --- /dev/null +++ b/elixir/kindergarten-garden/garden_test.exs @@ -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 diff --git a/elixir/leap/README.md b/elixir/leap/README.md new file mode 100644 index 0000000..8aa1307 --- /dev/null +++ b/elixir/leap/README.md @@ -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. diff --git a/elixir/leap/leap.exs b/elixir/leap/leap.exs new file mode 100644 index 0000000..4ecb7a7 --- /dev/null +++ b/elixir/leap/leap.exs @@ -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 diff --git a/elixir/leap/leap_test.exs b/elixir/leap/leap_test.exs new file mode 100644 index 0000000..a8d9a11 --- /dev/null +++ b/elixir/leap/leap_test.exs @@ -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 diff --git a/elixir/list-ops/README.md b/elixir/list-ops/README.md new file mode 100644 index 0000000..00854ba --- /dev/null +++ b/elixir/list-ops/README.md @@ -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. diff --git a/elixir/list-ops/list_ops.exs b/elixir/list-ops/list_ops.exs new file mode 100644 index 0000000..16d2a8c --- /dev/null +++ b/elixir/list-ops/list_ops.exs @@ -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 diff --git a/elixir/list-ops/list_ops_test.exs b/elixir/list-ops/list_ops_test.exs new file mode 100644 index 0000000..7a16260 --- /dev/null +++ b/elixir/list-ops/list_ops_test.exs @@ -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 diff --git a/elixir/tournament/README.md b/elixir/tournament/README.md new file mode 100644 index 0000000..f7a47eb --- /dev/null +++ b/elixir/tournament/README.md @@ -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. diff --git a/elixir/tournament/tournament.exs b/elixir/tournament/tournament.exs new file mode 100644 index 0000000..01e81ca --- /dev/null +++ b/elixir/tournament/tournament.exs @@ -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 diff --git a/elixir/tournament/tournament_test.exs b/elixir/tournament/tournament_test.exs new file mode 100644 index 0000000..4adbfd9 --- /dev/null +++ b/elixir/tournament/tournament_test.exs @@ -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