Skip to content

Commit f3ed2e5

Browse files
committed
Remove treatment of heaps with nil elements as empty
1 parent 06a16df commit f3ed2e5

File tree

4 files changed

+86
-57
lines changed

4 files changed

+86
-57
lines changed

lib/chapter_2/unbalanced_set.ex

+8-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ defmodule UnbalancedSet do
1515
Creates an UnbalancedSet from a list
1616
"""
1717
def from_list([]), do: %__MODULE__{}
18+
1819
def from_list(list) do
1920
list
2021
|> Enum.reduce(%__MODULE__{}, fn x, acc ->
@@ -68,6 +69,7 @@ defmodule UnbalancedSet do
6869
def insert(el, %__MODULE__{set: set}), do: %__MODULE__{set: insert_(el, set)}
6970

7071
defp insert_(el, :empty), do: {:empty, el, :empty}
72+
7173
defp insert_(el, {left, root, right} = tree) do
7274
cond do
7375
Ordered.lt(el, root) -> {insert_(el, left), root, right}
@@ -123,7 +125,9 @@ defmodule UnbalancedSet do
123125
d + 1 comparisons
124126
"""
125127
@spec optimized_insert(Chapter2.el(), t()) :: t()
126-
def optimized_insert(el, %__MODULE__{set: tree}), do: %__MODULE__{set: optimized_insert_(el, nil, tree)}
128+
def optimized_insert(el, %__MODULE__{set: tree}),
129+
do: %__MODULE__{set: optimized_insert_(el, nil, tree)}
130+
127131
defp optimized_insert_(el, el, :empty), do: raise(ExistingElementException, el)
128132
defp optimized_insert_(el, _prev, :empty), do: {:empty, el, :empty}
129133

@@ -141,11 +145,13 @@ defimpl Enumerable, for: UnbalancedSet do
141145
def reduce(_set, {:halt, acc}, _fun), do: {:halted, acc}
142146
def reduce(set, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(set, &1, fun)}
143147
def reduce(%UnbalancedSet{set: :empty}, {:cont, acc}, _fun), do: {:done, acc}
148+
144149
def reduce(%UnbalancedSet{set: set}, {:cont, acc}, fun) do
145-
{:done, reduce_(set, acc, fun)}
150+
{:done, reduce_(set, acc, fun)}
146151
end
147152

148153
defp reduce_(:empty, acc, _fun), do: acc
154+
149155
defp reduce_({left, root, right}, acc, fun) do
150156
with {:cont, intermediate} <- fun.(acc, root) do
151157
{:cont, sum} = fun.(reduce_(left, acc, fun), reduce_(right, acc, fun))

lib/chapter_3/leftist_heap.ex

+21-13
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
defmodule LeftistHeap do
2+
@moduledoc """
3+
A Leftist Heap is a heap-ordered binary tree that satisfies the leftist property --
4+
the rank of any left child is at least as large as the rank of its right sibling.
5+
6+
The rank of node is defined to be the length of its right spine (i.e., the rightmost
7+
path from the node in question to an empty or leaf node).
8+
9+
This definition means that the right spine of any node is always the shortest path
10+
to an empty node.
11+
"""
212

313
defstruct right: :empty,
414
left: :empty,
515
element: nil,
616
rank: 1
717

8-
@type t(a) :: %LeftistHeap{right: t(a), left: t(a), element: any(), rank: non_neg_integer()} | :empty
18+
@type t(a) ::
19+
%LeftistHeap{right: t(a), left: t(a), element: any(), rank: non_neg_integer()} | :empty
920

1021
@spec empty() :: t(any())
11-
def empty(), do: %LeftistHeap{}
22+
def empty(), do: :empty
1223

1324
@spec is_empty?(t(any())) :: boolean()
14-
def is_empty?(%LeftistHeap{element: nil}), do: true
1525
def is_empty?(:empty), do: true
1626
def is_empty?(%LeftistHeap{}), do: false
1727

@@ -20,18 +30,20 @@ defmodule LeftistHeap do
2030

2131
@spec rank(t(any())) :: non_neg_integer()
2232
def rank(:empty), do: 0
23-
def rank(%LeftistHeap{element: nil}), do: 0
2433
def rank(%LeftistHeap{rank: rank}), do: rank
2534

2635
@spec merge(t(any()), t(any())) :: t(any())
27-
def merge(%LeftistHeap{element: nil}, %LeftistHeap{} = leftist_heap), do: leftist_heap
28-
def merge(%LeftistHeap{} = leftist_heap, %LeftistHeap{element: nil}), do: leftist_heap
2936
def merge(:empty, leftist_heap), do: leftist_heap
3037
def merge(leftist_heap, :empty), do: leftist_heap
31-
def merge(%LeftistHeap{element: val_1} = h1, %LeftistHeap{element: val_2} = h2) when val_1 > val_2, do: merge(h2, h1)
38+
39+
def merge(%LeftistHeap{element: val_1} = h1, %LeftistHeap{element: val_2} = h2)
40+
when val_1 > val_2,
41+
do: merge(h2, h1)
42+
3243
def merge(%LeftistHeap{right: right, left: left} = h1, %LeftistHeap{} = h2) do
3344
merged = merge(right, h2)
3445
{rank_left, rank_right} = {rank(left), rank(merged)}
46+
3547
case rank_left >= rank_right do
3648
true -> %LeftistHeap{h1 | right: merged, rank: rank_right + 1}
3749
false -> %LeftistHeap{h1 | left: merged, right: left, rank: rank_left + 1}
@@ -46,22 +58,18 @@ defmodule LeftistHeap do
4658
end
4759

4860
@spec get_min(t(any())) :: {:ok, any()} | {:error, :empty_heap}
49-
def get_min(%LeftistHeap{element: nil}), do: {:error, :empty_heap}
5061
def get_min(:empty), do: {:error, :empty_heap}
5162
def get_min(%LeftistHeap{element: el}), do: {:ok, el}
5263

5364
@spec get_min!(t(any)) :: any | none()
54-
def get_min!(%LeftistHeap{element: nil}), do: raise EmptyHeapError
55-
def get_min!(:empty), do: raise EmptyHeapError
65+
def get_min!(:empty), do: raise(EmptyHeapError)
5666
def get_min!(%LeftistHeap{element: min}), do: min
5767

5868
@spec delete_min(t(any())) :: {:ok, t(any())} | {:error, :empty_heap}
59-
def delete_min(%LeftistHeap{element: nil}), do: {:error, :empty_heap}
6069
def delete_min(:empty), do: {:error, :empty_heap}
6170
def delete_min(%LeftistHeap{left: left, right: right}), do: {:ok, merge(left, right)}
6271

6372
@spec delete_min!(t(any)) :: any | none()
64-
def delete_min!(%LeftistHeap{element: nil}), do: raise EmptyHeapError
65-
def delete_min!(:empty), do: raise EmptyHeapError
73+
def delete_min!(:empty), do: raise(EmptyHeapError)
6674
def delete_min!(%LeftistHeap{left: left, right: right}), do: merge(left, right)
6775
end

test/chapter_2/unbalanced_set_test.exs

+19-12
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ defmodule UnbalancedSetTest do
2424

2525
describe "insert/2" do
2626
test "places the element to be inserted in the left sub tree if smaller than the root node" do
27-
assert %UnbalancedSet{set: {{:empty, 1, :empty}, 4, :empty}} = UnbalancedSet.insert(1, %UnbalancedSet{set: @left_sub_tree})
27+
assert %UnbalancedSet{set: {{:empty, 1, :empty}, 4, :empty}} =
28+
UnbalancedSet.insert(1, %UnbalancedSet{set: @left_sub_tree})
2829
end
2930

3031
test "places the element to be inserted in the right sub tree if larger than the root node" do
31-
assert %UnbalancedSet{set: {:empty, 4, {:empty, 5, :empty}}} = UnbalancedSet.insert(5, %UnbalancedSet{set: @left_sub_tree})
32+
assert %UnbalancedSet{set: {:empty, 4, {:empty, 5, :empty}}} =
33+
UnbalancedSet.insert(5, %UnbalancedSet{set: @left_sub_tree})
3234
end
3335
end
3436

@@ -53,17 +55,20 @@ defmodule UnbalancedSetTest do
5355
end
5456

5557
test "return {:ok, {:empty, element, :empty} when inserting into an empty tree" do
56-
assert {:ok, %UnbalancedSet{set: {:empty, 2, :empty}}} = UnbalancedSet.efficient_insert(2, %UnbalancedSet{set: :empty})
58+
assert {:ok, %UnbalancedSet{set: {:empty, 2, :empty}}} =
59+
UnbalancedSet.efficient_insert(2, %UnbalancedSet{set: :empty})
5760
end
5861
end
5962

6063
describe "optimized_insert/2" do
6164
test "places the element to be inserted in the left sub tree if smaller than the root node" do
62-
assert %UnbalancedSet{set: {{:empty, 1, :empty}, 4, :empty}} = UnbalancedSet.optimized_insert(1, %UnbalancedSet{set: @left_sub_tree})
65+
assert %UnbalancedSet{set: {{:empty, 1, :empty}, 4, :empty}} =
66+
UnbalancedSet.optimized_insert(1, %UnbalancedSet{set: @left_sub_tree})
6367
end
6468

6569
test "places the element to be inserted in the right sub tree if larger than the root node" do
66-
assert %UnbalancedSet{set: {:empty, 4, {:empty, 5, :empty}}} = UnbalancedSet.optimized_insert(5, %UnbalancedSet{set: @left_sub_tree})
70+
assert %UnbalancedSet{set: {:empty, 4, {:empty, 5, :empty}}} =
71+
UnbalancedSet.optimized_insert(5, %UnbalancedSet{set: @left_sub_tree})
6772
end
6873

6974
test "raises ExistingElementException when inserting an existing element" do
@@ -83,25 +88,27 @@ defmodule UnbalancedSetTest do
8388
test "creates an unbalanced set from an Elixir list" do
8489
assert UnbalancedSet.from_list([]) == %UnbalancedSet{set: :empty}
8590
assert UnbalancedSet.from_list([1]) == %UnbalancedSet{set: {:empty, 1, :empty}}
91+
8692
assert UnbalancedSet.from_list([5, 4, 6, 7, 8]) ==
87-
%UnbalancedSet{set: @a_tree}
93+
%UnbalancedSet{set: @a_tree}
8894
end
8995
end
9096

9197
describe "Enumerable.UnbalancedSet" do
9298
test "allows you to call functions from the Enum module" do
9399
assert 30 ==
94-
[7, 5, 6, 4, 8]
95-
|> UnbalancedSet.from_list()
96-
|> Enum.reduce(0, &Kernel.+/2)
100+
[7, 5, 6, 4, 8]
101+
|> UnbalancedSet.from_list()
102+
|> Enum.reduce(0, &Kernel.+/2)
97103
end
98104

99105
test "reduces over larger sets" do
100106
range = 1..200
107+
101108
assert Enum.reduce(range, 0, &Kernel.+/2) ==
102-
range
103-
|> UnbalancedSet.from_list()
104-
|> Enum.reduce(0, &Kernel.+/2)
109+
range
110+
|> UnbalancedSet.from_list()
111+
|> Enum.reduce(0, &Kernel.+/2)
105112
end
106113
end
107114
end

test/chapter_3/leftist_heap_test.exs

+38-30
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@ defmodule LeftistHeapTest do
33

44
describe "empty/0" do
55
test "creates an empty leftist heap" do
6-
assert %LeftistHeap{} == LeftistHeap.empty()
6+
assert :empty == LeftistHeap.empty()
77
end
88
end
99

1010
describe "is_empty?/1" do
11-
test "returns true for a leftist heap with no value" do
12-
assert LeftistHeap.is_empty?(%LeftistHeap{})
13-
end
14-
1511
test "returns true for :empty" do
1612
assert LeftistHeap.is_empty?(:empty)
1713
end
@@ -31,12 +27,24 @@ defmodule LeftistHeapTest do
3127
test "returns non-empty leftist heap if one is empty" do
3228
assert LeftistHeap.merge(%LeftistHeap{element: 2}, :empty) == %LeftistHeap{element: 2}
3329
assert LeftistHeap.merge(:empty, %LeftistHeap{element: 1}) == %LeftistHeap{element: 1}
34-
assert LeftistHeap.merge(LeftistHeap.empty(), %LeftistHeap{element: 3}) == %LeftistHeap{element: 3}
35-
assert LeftistHeap.merge(%LeftistHeap{element: 4}, LeftistHeap.empty()) == %LeftistHeap{element: 4}
30+
31+
assert LeftistHeap.merge(LeftistHeap.empty(), %LeftistHeap{element: 3}) == %LeftistHeap{
32+
element: 3
33+
}
34+
35+
assert LeftistHeap.merge(%LeftistHeap{element: 4}, LeftistHeap.empty()) == %LeftistHeap{
36+
element: 4
37+
}
3638
end
3739

3840
test "handles two singletons" do
39-
expected = %LeftistHeap{element: 2, left: %LeftistHeap{element: 10, rank: 1}, rank: 1, right: :empty}
41+
expected = %LeftistHeap{
42+
element: 2,
43+
left: %LeftistHeap{element: 10, rank: 1},
44+
rank: 1,
45+
right: :empty
46+
}
47+
4048
heap_1 = LeftistHeap.singleton(2)
4149
heap_2 = LeftistHeap.singleton(10)
4250
result = LeftistHeap.merge(heap_1, heap_2)
@@ -45,15 +53,25 @@ defmodule LeftistHeapTest do
4553
end
4654

4755
test "handles leftist_heaps with more than one root" do
48-
expected =
49-
%LeftistHeap{
50-
element: 2,
51-
rank: 2,
52-
right: LeftistHeap.singleton(10),
53-
left: %LeftistHeap{element: 3, left: LeftistHeap.singleton(6), right: LeftistHeap.singleton(9), rank: 2}
56+
expected = %LeftistHeap{
57+
element: 2,
58+
rank: 2,
59+
right: LeftistHeap.singleton(10),
60+
left: %LeftistHeap{
61+
element: 3,
62+
left: LeftistHeap.singleton(6),
63+
right: LeftistHeap.singleton(9),
64+
rank: 2
5465
}
66+
}
67+
68+
left_heap = %LeftistHeap{
69+
element: 2,
70+
left: LeftistHeap.singleton(10),
71+
right: LeftistHeap.singleton(9),
72+
rank: 2
73+
}
5574

56-
left_heap = %LeftistHeap{element: 2, left: LeftistHeap.singleton(10), right: LeftistHeap.singleton(9), rank: 2}
5775
right_heap = %LeftistHeap{element: 3, left: LeftistHeap.singleton(6)}
5876

5977
assert LeftistHeap.merge(left_heap, right_heap) == expected
@@ -65,16 +83,15 @@ defmodule LeftistHeapTest do
6583
assert {:ok, 1} = LeftistHeap.get_min(LeftistHeap.singleton(1))
6684

6785
assert {:ok, 5} =
68-
LeftistHeap.singleton(5)
69-
|> LeftistHeap.merge(LeftistHeap.singleton(10))
70-
|> LeftistHeap.merge(LeftistHeap.singleton(20))
71-
|> LeftistHeap.merge(LeftistHeap.singleton(15))
72-
|> LeftistHeap.get_min()
86+
LeftistHeap.singleton(5)
87+
|> LeftistHeap.merge(LeftistHeap.singleton(10))
88+
|> LeftistHeap.merge(LeftistHeap.singleton(20))
89+
|> LeftistHeap.merge(LeftistHeap.singleton(15))
90+
|> LeftistHeap.get_min()
7391
end
7492

7593
test "returns an error tuple when the heap is empty" do
7694
assert {:error, :empty_heap} = LeftistHeap.get_min(:empty)
77-
assert {:error, :empty_heap} = LeftistHeap.get_min(%LeftistHeap{element: nil})
7895
end
7996
end
8097

@@ -83,10 +100,6 @@ defmodule LeftistHeapTest do
83100
assert_raise EmptyHeapError, fn ->
84101
LeftistHeap.get_min!(:empty)
85102
end
86-
87-
assert_raise EmptyHeapError, fn ->
88-
LeftistHeap.get_min!(%LeftistHeap{})
89-
end
90103
end
91104
end
92105

@@ -107,7 +120,6 @@ defmodule LeftistHeapTest do
107120
end
108121

109122
test "returns an error tuple when the heap is empty" do
110-
assert {:error, :empty_heap} = LeftistHeap.delete_min(%LeftistHeap{})
111123
assert {:error, :empty_heap} = LeftistHeap.delete_min(:empty)
112124
end
113125
end
@@ -129,10 +141,6 @@ defmodule LeftistHeapTest do
129141
end
130142

131143
test "raises EmptyHeapError when the heap is empty" do
132-
assert_raise EmptyHeapError, fn ->
133-
LeftistHeap.delete_min!(%LeftistHeap{})
134-
end
135-
136144
assert_raise EmptyHeapError, fn ->
137145
LeftistHeap.delete_min!(:empty)
138146
end

0 commit comments

Comments
 (0)