Avatar of davearonson

davearonson's solution

to Strain in the Elixir Track

Published at Jul 13 2018 · 0 comments
Instructions
Test suite
Solution

Note:

This solution was written on an old version of Exercism. The tests below might not correspond to the solution code, and the exercise may have changed since this code was written.

Implement the keep and discard operation on collections. Given a collection and a predicate on the collection's elements, keep returns a new collection containing those elements where the predicate is true, while discard returns a new collection containing those elements where the predicate is false.

For example, given the collection of numbers:

  • 1, 2, 3, 4, 5

And the predicate:

  • is the number even?

Then your keep operation should produce:

  • 2, 4

While your discard operation should produce:

  • 1, 3, 5

Note that the union of keep and discard is all the elements.

The functions may be called keep and discard, or they may need different names in order to not clash with existing functions or concepts in your language.

Restrictions

Keep your hands off that filter/reject/whatchamacallit functionality provided by your standard library! Solve this one yourself using other basic tools instead.

apply will let you pass arguments to a function, as will fun.(args)

Running tests

Execute the tests with:

$ elixir strain_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:

# @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.

# ExUnit.configure exclude: :pending, trace: true

For more detailed information about the Elixir track, please see the help page.

Source

Conversation with James Edward Gray II https://twitter.com/jeg2

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

strain_test.exs

if !System.get_env("EXERCISM_TEST_EXAMPLES") do
  Code.load_file("strain.exs", __DIR__)
end

ExUnit.start()
ExUnit.configure(trace: true, exclude: :pending)

defmodule StrainTest do
  use ExUnit.Case

  defp is_odd?(n), do: rem(n, 2) == 1
  defp is_even?(n), do: rem(n, 2) == 0
  defp noop(_), do: true

  # @tag :pending
  test "empty keep" do
    assert Strain.keep([], &noop/1) == []
  end

  @tag :pending
  test "keep everything" do
    assert Strain.keep([1, 2, 3], fn e -> e < 10 end) == [1, 2, 3]
  end

  @tag :pending
  test "keep first and last" do
    assert Strain.keep([1, 2, 3], &is_odd?/1) == [1, 3]
  end

  @tag :pending
  test "keep neither first nor last" do
    assert Strain.keep([1, 2, 3, 4, 5], &is_even?/1) == [2, 4]
  end

  @tag :pending
  test "keep strings" do
    words = ~w(apple zebra banana zombies cherimoya zelot)
    assert Strain.keep(words, &String.starts_with?(&1, "z")) == ~w(zebra zombies zelot)
  end

  @tag :pending
  test "keep arrays" do
    rows = [
      [1, 2, 3],
      [5, 5, 5],
      [5, 1, 2],
      [2, 1, 2],
      [1, 5, 2],
      [2, 2, 1],
      [1, 2, 5]
    ]

    assert Strain.keep(rows, fn row -> 5 in row end) == [
             [5, 5, 5],
             [5, 1, 2],
             [1, 5, 2],
             [1, 2, 5]
           ]
  end

  @tag :pending
  test "empty discard" do
    assert Strain.discard([], &noop/1) == []
  end

  @tag :pending
  test "discard nothing" do
    assert Strain.discard([1, 2, 3], fn e -> e > 10 end) == [1, 2, 3]
  end

  @tag :pending
  test "discard first and last" do
    assert Strain.discard([1, 2, 3], &is_odd?/1) == [2]
  end

  @tag :pending
  test "discard neither first nor last" do
    assert Strain.discard([1, 2, 3, 4, 5], &is_even?/1) == [1, 3, 5]
  end

  @tag :pending
  test "discard strings" do
    words = ~w(apple zebra banana zombies cherimoya zelot)
    assert Strain.discard(words, &String.starts_with?(&1, "z")) == ~w(apple banana cherimoya)
  end

  @tag :pending
  test "discard arrays" do
    rows = [
      [1, 2, 3],
      [5, 5, 5],
      [5, 1, 2],
      [2, 1, 2],
      [1, 5, 2],
      [2, 2, 1],
      [1, 2, 5]
    ]

    assert Strain.discard(rows, fn row -> 5 in row end) == [[1, 2, 3], [2, 1, 2], [2, 2, 1]]
  end
end
defmodule Strain do
  @doc """
  Given a `list` of items and a function `fun`, return the list of items where
  `fun` returns true.

  Do not use `Enum.filter`.
  """
  @spec keep(list :: list(any), fun :: ((any) -> boolean)) :: list(any)
  def keep(list, fun) do
    do_filter(list, fun, true, [])
  end

  @doc """
  Given a `list` of items and a function `fun`, return the list of items where
  `fun` returns true.

  Do not use `Enum.reject`.
  """
  @spec discard(list :: list(any), fun :: ((any) -> boolean)) :: list(any)
  def discard(list, fun) do
    do_filter(list, fun, false, [])
  end

  defp do_filter([head|tail], fun, wanted, acc)  do
    next_acc = cond do
      fun.(head) == wanted -> [head|acc]
      true                 -> acc
    end
    do_filter(tail, fun, wanted, next_acc)
  end
  defp do_filter(_, _fun, _want, acc), do: acc |> Enum.reverse

end

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?