Skip to content

Instantly share code, notes, and snippets.

@nielsbom
Created March 9, 2019 19:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nielsbom/8aaec8838d927cac9f72b0e7c28770b2 to your computer and use it in GitHub Desktop.
Save nielsbom/8aaec8838d927cac9f72b0e7c28770b2 to your computer and use it in GitHub Desktop.
Practicing Elixir
ExUnit.start()
defmodule Enum2 do
# all
def all?(list), do: all?(list, &(!!&1 === true))
def all?([], _), do: true
# Short circuit with "and"
# Tail-call optimized πŸ‘
def all?([head | tail], func), do:
func.(head) and all?(tail, func)
# each (no relevant return value)
def each([], _), do: nil
def each([head | tail], fun) do
fun.(head)
# Tail-call optimized πŸ‘
each(tail, fun)
end
# filter
def filter([], _), do: []
def filter([head | tail], func), do:
# Tail-call optimized πŸ‘
# But: list concatenation with linked lists is relatively expensive.
if(func.(head), do: [head], else: []) ++ filter(tail, func)
# faster filter
def ffilter(lst, func), do: _ffilter(lst, func, [])
defp _ffilter([], _, result), do: Enum.reverse(result)
defp _ffilter([head | tail], func, result), do:
# every item that is πŸ‘ is added to it (prepending to LL is cheap)
# then when we reach the end, reverse and pass it back (reverse is cheap?)
# Tail-call optimized πŸ‘
_ffilter(
tail,
func,
(if(func.(head), do: [head | result], else: result))
)
# Splits the enumerable into two enumerables, leaving count elements in the
# first one.
# If count is a negative number, it starts counting from the back to the
# beginning of the enumerable.
# Be aware that a negative count implies the enumerable will be enumerated
# twice: once to calculate the position, and a second time to do the actual
# splitting.
def split([], _), do: {[], []}
def split(list, 0), do: {[], list}
def split(list, n) when n < 0 do
{a, b} = split(Enum.reverse(list), abs(n))
{Enum.reverse(b), Enum.reverse(a)}
end
def split([head | tail], n) do
# Not tail-call optimized πŸ‘Ž
{head_rest, tail_rest} = split(tail, n-1)
{[head | head_rest], tail_rest}
end
# More performant split
# Tail-call optimized πŸ‘
# Reverse is cheap
def fsplit(list, n) when n < 0 do
# Can I remove temporary variables?
{a, b} = fsplit(Enum.reverse(list), abs(n))
{Enum.reverse(b), Enum.reverse(a)}
end
def fsplit(list, n), do:
_fsplit(list, n, [])
defp _fsplit(list, n, result) when list == [] or n == 0, do:
{Enum.reverse(result), list}
defp _fsplit([head | tail], n, result), do:
_fsplit(tail, n-1, [head | result])
# Takes the first amount items from the enumerable.
# If a negative amount is given, the amount of last values will be taken.
# The enumerable will be enumerated once to retrieve the proper index and
# the remaining calculation is performed from the end.
def take([], _), do: []
def take(_, 0), do: []
def take([head | tail], n) when n > 0 do
[head | Enum2.take(tail, n - 1)]
end
def take(list = [_ | tail], n) do
if Enum.count(list) + n > 0 do
Enum2.take(tail, n)
else
list
end
end
# Tail call optimized πŸ‘
def ftake([], _), do: []
def ftake(_, 0), do: []
def ftake(list, n) when n < 0, do:
# Triple reversing when passing in negative... πŸ€”
list
|> Enum.reverse
|> ftake(abs(n))
|> Enum.reverse
def ftake(list, n), do:
_ftake(list, n, [])
# Termination
defp _ftake(list, n, result) when list == [] or n == 0, do:
Enum.reverse(result)
defp _ftake([head | tail], n, result), do:
_ftake(tail, n - 1, [head | result])
end
defmodule AssertionTest do
use ExUnit.Case, async: true
import Enum2
test "all?" do
assert all?([1,2,3,4])
assert all?([],fn _ -> false end)
assert all?([1,2,3,4], fn x -> x > 0 end)
refute all?([1,2,3,4], fn x -> x > 2 end)
end
test "each" do
# Return value for `each` is not relevant
import ExUnit.CaptureIO
sqr = &(&1 * &1 |> IO.puts)
assert capture_io(fn -> each([1,2,3,4], sqr) end) == "1\n4\n9\n16\n"
assert capture_io(fn -> each([], sqr) end) == ""
end
test "filter" do
assert [] |> filter(fn _ -> true end) == []
assert [1,2,3,4] |> filter(fn x -> x > 0 end) == [1,2,3,4]
assert [1,2,3,4] |> filter(fn x -> x > 2 end) == [3,4]
assert [1,2,3,4] |> filter(fn _ -> false end) == []
end
test "ffilter" do
assert [] |> ffilter(fn _ -> true end) == []
assert [1,2,3,4] |> ffilter(fn x -> x > 0 end) == [1,2,3,4]
assert [1,2,3,4] |> ffilter(fn x -> x > 2 end) == [3,4]
assert [1,2,3,4] |> ffilter(fn _ -> false end) == []
end
test "split" do
assert [] |> split(0) == {[], []}
assert [] |> split(4) == {[], []}
assert [] |> split(-4) == {[], []}
assert [1,2,3,4] |> split(0) == {[], [1,2,3,4]}
assert [1,2,3,4] |> split(2) == {[1,2], [3,4]}
assert [1,2,3,4] |> split(10) == {[1,2,3,4], []}
assert [1,2,3,4] |> split(-1) == {[1,2,3], [4]}
assert [1,2,3,4] |> split(-5) == {[], [1,2,3,4]}
end
test "fsplit" do
assert [] |> fsplit(0) == {[], []}
assert [] |> fsplit(4) == {[], []}
assert [] |> fsplit(-4) == {[], []}
assert [1,2,3,4] |> fsplit(0) == {[], [1,2,3,4]}
assert [1,2,3,4] |> fsplit(2) == {[1,2], [3,4]}
assert [1,2,3,4] |> fsplit(10) == {[1,2,3,4], []}
assert [1,2,3,4] |> fsplit(-1) == {[1,2,3], [4]}
assert [1,2,3,4] |> fsplit(-5) == {[], [1,2,3,4]}
end
test "take" do
assert [] |> take(0) == []
assert [] |> take(4) == []
assert [] |> take(-4) == []
assert [1,2,3,4] |> take(0) == []
assert [1,2,3,4] |> take(2) == [1,2]
assert [1,2,3,4] |> take(10) == [1,2,3,4]
assert [1,2,3,4] |> take(-1) == [4]
assert [1,2,3,4] |> take(-5) == [1,2,3,4]
end
test "ftake" do
assert [] |> ftake(0) == []
assert [] |> ftake(4) == []
assert [] |> ftake(-4) == []
assert [1,2,3,4] |> ftake(0) == []
assert [1,2,3,4] |> ftake(2) == [1,2]
assert [1,2,3,4] |> ftake(10) == [1,2,3,4]
assert [1,2,3,4] |> ftake(-1) == [4]
assert [1,2,3,4] |> ftake(-5) == [1,2,3,4]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment