Skip to content

Commit ff9397b

Browse files
committed
Merge branch 'implement-protocols'
2 parents d10658a + 12dcb75 commit ff9397b

2 files changed

Lines changed: 91 additions & 6 deletions

File tree

lib/circular_buffer.ex

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,21 @@ defmodule CircularBuffer do
99
```
1010
"""
1111

12+
defstruct [:q, :max_size, :count]
13+
14+
alias __MODULE__, as: CB
15+
1216
@doc """
1317
Creates a new circular buffer with a given size.
1418
"""
1519
def new(size) when is_integer(size) and size > 0 do
16-
%{q: :queue.new(), max_size: size, count: 0}
20+
%CB{q: :queue.new(), max_size: size, count: 0}
1721
end
1822

1923
@doc """
2024
Inserts a new item into the next location of the circular buffer
2125
"""
22-
def insert(cb, item) do
26+
def insert(%CB{}=cb, item) do
2327
if cb.count < cb.max_size do
2428
%{cb | q: :queue.cons(item, cb.q), count: cb.count + 1}
2529
else
@@ -28,15 +32,15 @@ defmodule CircularBuffer do
2832
|> :queue.drop_r
2933
|> (fn q -> :queue.cons(item, q) end).()
3034

31-
%{cb | q: new_q}
35+
%CB{cb | q: new_q}
3236
end
3337
end
3438

3539
@doc """
3640
Converts a circular buffer to a list. The list is ordered from oldest to newest
3741
elements based on their insertion order.
3842
"""
39-
def to_list(cb) do
43+
def to_list(%CB{}=cb) do
4044
cb.q
4145
|> :queue.reverse
4246
|> :queue.to_list
@@ -45,7 +49,7 @@ defmodule CircularBuffer do
4549
@doc """
4650
Returns the newest element in the buffer
4751
"""
48-
def newest(cb) do
52+
def newest(%CB{}=cb) do
4953
case :queue.peek(cb.q) do
5054
{_, val} -> val
5155
:empty -> nil
@@ -55,10 +59,55 @@ defmodule CircularBuffer do
5559
@doc """
5660
Returns the oldest element in the buffer
5761
"""
58-
def oldest(cb) do
62+
def oldest(%CB{}=cb) do
5963
case :queue.peek_r(cb.q) do
6064
{_, val} -> val
6165
:empty -> nil
6266
end
6367
end
68+
69+
@doc """
70+
Checks the buffer to see if its empty
71+
"""
72+
def empty?(%CB{}=cb) do
73+
:queue.is_empty(cb.q)
74+
end
75+
76+
defimpl Enumerable do
77+
def count(cb) do
78+
{:ok, cb.count}
79+
end
80+
81+
def member?(cb, element) do
82+
{:ok, :queue.member(element, cb.q)}
83+
end
84+
85+
def reduce(cb, acc, fun) do
86+
Enumerable.List.reduce(CB.to_list(cb), acc, fun)
87+
end
88+
89+
def slice(cb) do
90+
{:ok, cb.count, &Enumerable.List.slice(CB.to_list(cb), &1, &2)}
91+
end
92+
end
93+
94+
defimpl Collectable do
95+
def into(original) do
96+
collector_fn = fn
97+
cb, {:cont, elem} -> CB.insert(cb, elem)
98+
cb, :done -> cb
99+
_cb, :halt -> :ok
100+
end
101+
102+
{original, collector_fn}
103+
end
104+
end
105+
106+
defimpl Inspect do
107+
import Inspect.Algebra
108+
109+
def inspect(cb, opts) do
110+
concat(["#CircularBuffer<", to_doc(CB.to_list(cb), opts), ">"])
111+
end
112+
end
64113
end

test/circular_buffer_test.exs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,37 @@ defmodule CircularBufferTest do
2626
end
2727
end
2828

29+
property "can tell the current number of elements" do
30+
forall {size, is} <- {pos_integer(), list(integer())} do
31+
buffer = Enum.reduce(is, CB.new(size), fn i, cb -> CB.insert(cb, i) end)
32+
Enum.count(buffer) == min(size, length(is))
33+
end
34+
end
35+
36+
property "member?/1" do
37+
items = such_that({a, b} <- {pos_integer(), pos_integer()}, when: a != b)
38+
forall {a, b} <- items do
39+
cb = CB.new(1) |> CB.insert(a)
40+
!Enum.member?(cb, b) && Enum.member?(cb, a)
41+
end
42+
end
43+
44+
property "implements Enumerable" do
45+
forall is <- list(integer()) do
46+
buffer = Enum.reduce(is, CB.new(length(is)+1), fn i, cb -> CB.insert(cb, i) end)
47+
48+
Enum.reduce(buffer, 0, fn acc, i -> acc + i end) == Enum.sum(is)
49+
end
50+
end
51+
52+
property "can be collected" do
53+
forall {size, is} <- size_and_list() do
54+
buffer = Enum.reduce(is, CB.new(size), fn i, cb -> CB.insert(cb, i) end)
55+
56+
Enum.into(is, CB.new(size)) == buffer
57+
end
58+
end
59+
2960
property "the number of elements never exceeds the size of the buffer" do
3061
forall {size, is} <- size_and_list() do
3162
buffer = Enum.reduce(is, CB.new(size), fn i, cb -> CB.insert(cb, i) end)
@@ -76,6 +107,11 @@ defmodule CircularBufferTest do
76107
end
77108
end
78109

110+
test "can be inspected" do
111+
str = inspect(Enum.into([1,2,3,4], CB.new(4)))
112+
assert str == "#CircularBuffer<[1, 2, 3, 4]>"
113+
end
114+
79115
def size_and_list do
80116
let size <- pos_integer() do
81117
let is <- ints(size*2, []) do

0 commit comments

Comments
 (0)