Help shepherd to count sheep in Elixir

Shepard (Mass Effect)

I have been thinking about learning a functional language for a long time. So recently I’ve started to play around with Elixir. I am reading official guide and docs for theory. And also I do simple katas on Codewars for practice.

The last kata is **Counting sheep… **And I’ve come up with 13 solutions for it. Let me show it to you and explain why I need so many.

Task

Consider an array of sheep where some sheep may be missing from their place. We need a function that counts the number of sheep present in the array (true means present).

For example,

[true,  true,  true,  false,
  true,  true,  true,  true ,
  true,  false, true,  false,
  true,  false, false, true ,
  true,  true,  true,  true ,
  false, false, true,  true]

The correct answer would be 17.

Solution boilerplate


defmodule Shepherd do
  def count_sheeps(sheeps) do
    # TODO: for Elixir only true/false values can be presented the in sheeps list
  end
end

Sample Tests

defmodule TestShepherd do
  use ExUnit.Case
  import Shepherd, only: [count_sheeps: 1]

  test "work for some examples" do
    assert count_sheeps([]) == 0
    assert count_sheeps([true]) == 1
    assert count_sheeps([true, false]) == 1
    assert count_sheeps([false, false]) == 0
    assert count_sheeps(
    [true,  true,  true,  false,
     true,  true,  true,  true ,
     true,  false, true,  false,
     true,  false, false, true]) == 11
  end
end
view raw

My solutions

defmodule ShepherdHeadTail do
  def count_sheeps([]), do: 0

  def count_sheeps(sheeps) do
    [head | tail] = sheeps
    count_sheeps(tail) + (if head, do: 1, else: 0)
  end
end

defmodule ShepherdAccumulator do
  def count_sheeps(sheeps, accumulator \\ 0)

  def count_sheeps([], accumulator) do
    accumulator
  end

  def count_sheeps([head | tail], accumulator) do
    count_sheeps tail, accumulator + (if head, do: 1, else: 0)
  end
end

defmodule ShepherdIsSheep do
  def is_sheep?(true), do: 1
  def is_sheep?(false), do: 0

  def count_sheeps([]), do: 0

  def count_sheeps([head | tail]) do
    is_sheep?(head) + count_sheeps(tail)
  end
end

defmodule ShepherdIsSheepAccumulator do
  def is_sheep?(true), do: 1
  def is_sheep?(false), do: 0

  def count_sheeps(sheeps, accumulator \\ 0)

  def count_sheeps([], accumulator), do: accumulator

  def count_sheeps([head | tail], accumulator) do
    count_sheeps(tail, is_sheep?(head) + accumulator)
  end
end

defmodule ShepherdFoldl do
  def is_sheep?(true, acc), do: 1 + acc
  def is_sheep?(false, acc), do: acc

  def count_sheeps(sheeps) do
    List.foldl sheeps, 0, &is_sheep?/2
  end
end

defmodule ShepherdCount do
  def count_sheeps(sheeps) do
    Enum.count sheeps, fn x -> x end
  end
end

defmodule ShepherdFilter do
  def count_sheeps(sheeps) do
    Enum.filter(sheeps, fn x -> x end) |> Enum.count
  end
end

defmodule ShepherdGroupBy do
  defp counter(:error), do: []
  defp counter({:ok, list}), do: list

  def count_sheeps(sheeps) do
    # alternative: split_with
    Enum.group_by(sheeps, &(&1)) |> Map.fetch(true) |> counter |> Enum.count
  end
end

defmodule ShepherdMapJoin do
  def count_sheeps(sheeps) do
    Enum.map_join(sheeps, &(if &1, do: "1", else: "")) |> String.length
  end
end

defmodule ShepherdReduce do
  def accumulate(true, acc), do: acc + 1
  def accumulate(false, acc), do: acc

  def count_sheeps(sheeps) do
    Enum.reduce(sheeps, 0, &accumulate/2)
  end
end

defmodule ShepherdSum do
  def count_sheeps(sheeps) do
    Enum.map(sheeps, fn(x) -> if x, do: 1, else: 0 end) |> Enum.sum
  end
end

defmodule ShepherdDuplicate do
  def count_sheeps(sheeps) do
    (sheeps -- List.duplicate(false, length(sheeps))) |> length
  end
end

defmodule ShepherdDelete do
  def count_sheeps(sheeps, acc \\ 0) do
    new_sheeps = List.delete sheeps, true
    if new_sheeps == sheeps do
      acc
    else
      count_sheeps new_sheeps, acc + 1
    end
  end
end

When I first saw this kata I had very small grasp on how to solve it or cycle through a list in Elixir in general. So I had to look for some clues in guides.

Also during my little investigation I browsed docs on Enum and *List *and I was curious to try few functions from there too. Lastly, while I am still on that task I decided to try out syntax peculiarities of Elixir that I already read about.

So this ends my little story on learning a new language. I think this is an interesting approach: take a relatively simple task in a new language and try to solve it in different ways. I see quite a few benefits here.

It would be cool if you propose your solution in comments or share your thoughts on how to learn a new language.