Skip to content

Commit

Permalink
Add simple linked list exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
lpil committed Dec 20, 2016
1 parent 8362235 commit 073da72
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 0 deletions.
7 changes: 7 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@
"Enumerations"
]
},
{
"slug": "simple-linked-list",
"difficulty": 1,
"topics": [
"Recursion"
]
},
{
"slug": "anagram",
"difficulty": 2,
Expand Down
86 changes: 86 additions & 0 deletions exercises/simple-linked-list/example.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
defmodule LinkedList do
@opaque t :: tuple()

@doc """
Construct a new LinkedList
"""
@spec new() :: t
def new() do
{}
end

@doc """
Push an item onto a LinkedList
"""
@spec push(t, any()) :: t
def push(list, elem) do
{elem, list}
end

@doc """
Calculate the length of a LinkedList
"""
@spec length(t) :: non_neg_integer()
def length(list) do
count_length(list, 0)
end

defp count_length({}, n), do: n
defp count_length({_, t}, n), do: count_length(t, n + 1)

@doc """
Determine if a LinkedList is empty
"""
@spec empty?(t) :: boolean()
def empty?({}), do: true
def empty?(_), do: false

@doc """
Get the value of a head of the LinkedList
"""
@spec peek(t) :: {:ok, any()} | {:error, :empty_list}
def peek({}), do: {:error, :empty_list}
def peek({x, _}), do: {:ok, x}

@doc """
Get tail of a LinkedList
"""
@spec tail(t) :: {:ok, t} | {:error, :empty_list}
def tail({}), do: {:error, :empty_list}
def tail({_, t}), do: {:ok, t}

@doc """
Remove the head from a LinkedList
"""
@spec pop(t) :: {:ok, any(), t} | {:error, :empty_list}
def pop({}), do: {:error, :empty_list}
def pop({h, t}), do: {:ok, h, t}

@doc """
Construct a LinkedList from a stdlib List
"""
@spec from_list(list()) :: t
def from_list(list) do
List.foldr(list, new(), &push(&2, &1))
end

@doc """
Construct a stdlib List LinkedList from a LinkedList
"""
@spec to_list(t) :: list()
def to_list(list) do
list |> do_to_list([]) |> Enum.reverse()
end
defp do_to_list({}, acc), do: acc
defp do_to_list({h, t}, acc), do: do_to_list(t, [h|acc])

@doc """
Reverse a LinkedList
"""
@spec reverse(t) :: t
def reverse(list) do
do_reverse(list, new())
end
def do_reverse({}, acc), do: acc
def do_reverse({h, t}, acc), do: do_reverse(t, push(acc, h))
end
83 changes: 83 additions & 0 deletions exercises/simple-linked-list/linked_list.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
defmodule LinkedList do
@opaque t :: tuple()

@doc """
Construct a new LinkedList
"""
@spec new() :: t
def new() do
# Your implementation here...
end

@doc """
Push an item onto a LinkedList
"""
@spec push(t, any()) :: t
def push(list, elem) do
# Your implementation here...
end

@doc """
Calculate the length of a LinkedList
"""
@spec length(t) :: non_neg_integer()
def length(list) do
# Your implementation here...
end

@doc """
Determine if a LinkedList is empty
"""
@spec empty?(t) :: boolean()
def empty?(list) do
# Your implementation here...
end

@doc """
Get the value of a head of the LinkedList
"""
@spec peek(t) :: {:ok, any()} | {:error, :empty_list}
def peek(list) do
# Your implementation here...
end

@doc """
Get tail of a LinkedList
"""
@spec tail(t) :: {:ok, t} | {:error, :empty_list}
def tail(list) do
# Your implementation here...
end

@doc """
Remove the head from a LinkedList
"""
@spec pop(t) :: {:ok, any(), t} | {:error, :empty_list}
def pop(list) do
# Your implementation here...
end

@doc """
Construct a LinkedList from a stdlib List
"""
@spec from_list(list()) :: t
def from_list(list) do
# Your implementation here...
end

@doc """
Construct a stdlib List LinkedList from a LinkedList
"""
@spec to_list(t) :: list()
def to_list(list) do
# Your implementation here...
end

@doc """
Reverse a LinkedList
"""
@spec reverse(t) :: t
def reverse(list) do
# Your implementation here...
end
end
154 changes: 154 additions & 0 deletions exercises/simple-linked-list/linked_list_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
if !System.get_env("EXERCISM_TEST_EXAMPLES") do
Code.load_file("linked_list.exs", __DIR__)
end

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

defmodule LinkedListTest do
use ExUnit.Case

test "length/1 of new list" do
list = LinkedList.new()
assert LinkedList.length(list) == 0
end

@tag :pending
test "empty?/1 of new list" do
list = LinkedList.new()
assert LinkedList.empty?(list)
end

@tag :pending
test "length/1 of list of 1 datum" do
list = LinkedList.new() |> LinkedList.push(10)
assert LinkedList.length(list) == 1
end

@tag :pending
test "empty?/1 of list of 1 datum" do
list = LinkedList.new() |> LinkedList.push(20)
refute LinkedList.empty?(list)
end

@tag :pending
test "peek/1 of list of 1 datum" do
list = LinkedList.new() |> LinkedList.push(20)
assert LinkedList.peek(list) == {:ok, 20}
end

@tag :pending
test "peek/1 of list of empty list" do
list = LinkedList.new()
assert LinkedList.peek(list) == {:error, :empty_list}
end

@tag :pending
test "tail/1 of empty list" do
list = LinkedList.new()
assert {:error, :empty_list} = LinkedList.tail(list)
end

@tag :pending
test "tail/1 of list of 1 datum" do
list = LinkedList.new() |> LinkedList.push(:hello)
assert {:ok, tail} = LinkedList.tail(list)
assert LinkedList.peek(tail) == {:error, :empty_list}
end

@tag :pending
test "pushed items are stacked" do
list =
LinkedList.new()
|> LinkedList.push(:a)
|> LinkedList.push(:b)
assert LinkedList.peek(list) == {:ok, :b}
assert {:ok, list} = LinkedList.tail(list)
assert LinkedList.peek(list) == {:ok, :a}
assert {:ok, list} = LinkedList.tail(list)
assert LinkedList.peek(list) == {:error, :empty_list}
end

@tag :pending
test "push 10 times" do
list = Enum.reduce(1..10, LinkedList.new(), &LinkedList.push(&2, &1))
assert LinkedList.peek(list) == {:ok, 10}
assert LinkedList.length(list) == 10
end

@tag :pending
test "pop/1 of list of 1 datum" do
list = LinkedList.new() |> LinkedList.push(:a)
assert {:ok, :a, tail} = LinkedList.pop(list)
assert LinkedList.length(tail) == 0
end

@tag :pending
test "popping frenzy" do
list = Enum.reduce(11..20, LinkedList.new(), &LinkedList.push(&2, &1))
assert LinkedList.length(list) == 10
assert {:ok, 20, list} = LinkedList.pop(list)
assert {:ok, 19, list} = LinkedList.pop(list)
assert {:ok, 18, list} = LinkedList.pop(list)
assert {:ok, 17, list} = LinkedList.pop(list)
assert {:ok, 16, list} = LinkedList.pop(list)
assert {:ok, 15} = LinkedList.peek(list)
assert LinkedList.length(list) == 5
end

@tag :pending
test "from_list/1 of empty list" do
list = LinkedList.from_list([])
assert LinkedList.length(list) == 0
end

@tag :pending
test "from_list/1 of 2 element list" do
list = LinkedList.from_list([:a, :b])
assert LinkedList.length(list) == 2
assert {:ok, :a, list} = LinkedList.pop(list)
assert {:ok, :b, list} = LinkedList.pop(list)
assert {:error, :empty_list} = LinkedList.pop(list)
end

@tag :pending
test "to_list/1 of empty list" do
list = LinkedList.new()
assert LinkedList.to_list(list) == []
end

@tag :pending
test "to_list/1 of list of 1 datum" do
list = LinkedList.from_list([:mon])
assert LinkedList.to_list(list) == [:mon]
end

@tag :pending
test "to_list/1 of list of 2 datum" do
list = LinkedList.from_list([:mon, :tues])
assert LinkedList.to_list(list) == [:mon, :tues]
end

@tag :pending
test "reverse/1 of list of 2 datum" do
list = LinkedList.from_list([1, 2, 3]) |> LinkedList.reverse()
assert LinkedList.to_list(list) == [3, 2, 1]
end

@tag :pending
test "reverse/1 of list of 200 datum" do
list = Enum.to_list(1..200)
linked_list = LinkedList.from_list(list) |> LinkedList.reverse()
assert LinkedList.to_list(linked_list) == Enum.reverse(list)
end

@tag :pending
test "reverse/1 round trip" do
list = Enum.to_list(1..200)
linked_list =
LinkedList.from_list(list)
|> LinkedList.reverse()
|> LinkedList.reverse()
assert LinkedList.to_list(linked_list) == list
end
end

0 comments on commit 073da72

Please sign in to comment.