Skip to content

Commit 48c576e

Browse files
authored
Merge pull request #4 from fhunleth/step1
Manage circular buffer using lists
2 parents aeb28b8 + ee4e91f commit 48c576e

2 files changed

Lines changed: 34 additions & 37 deletions

File tree

lib/circular_buffer.ex

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,70 @@
11
defmodule CircularBuffer do
22
@moduledoc """
3-
Circular Buffer built around erlang's queue module.
3+
Circular Buffer
44
55
When creating a circular buffer you must specify the max size:
66
77
```
88
cb = CircularBuffer.new(10)
99
```
10+
11+
CircularBuffers are implemented as Okasaki queues like Erlang's `:queue`
12+
module, but with additional optimizations thanks to the reduced set
13+
of operations.
1014
"""
1115

12-
defstruct [:q, :max_size, :count]
16+
defstruct [:a, :b, :max_size, :count]
1317

1418
alias __MODULE__, as: CB
1519

1620
@doc """
1721
Creates a new circular buffer with a given size.
1822
"""
1923
def new(size) when is_integer(size) and size > 0 do
20-
%CB{q: :queue.new(), max_size: size, count: 0}
24+
%CB{a: [], b: [], max_size: size, count: 0}
2125
end
2226

2327
@doc """
2428
Inserts a new item into the next location of the circular buffer
2529
"""
26-
def insert(%CB{}=cb, item) do
27-
if cb.count < cb.max_size do
28-
%{cb | q: :queue.cons(item, cb.q), count: cb.count + 1}
29-
else
30-
new_q =
31-
cb.q
32-
|> :queue.drop_r
33-
|> (fn q -> :queue.cons(item, q) end).()
34-
35-
%CB{cb | q: new_q}
30+
def insert(%CB{b: b} = cb, item) when b != [] do
31+
%CB{cb | a: [item | cb.a], b: tl(b)}
32+
end
33+
34+
def insert(%CB{count: count, max_size: max_size} = cb, item) when count < max_size do
35+
%CB{cb | a: [item | cb.a], count: cb.count + 1}
3636
end
37+
38+
def insert(%CB{b: []} = cb, item) do
39+
new_b = cb.a |> Enum.reverse() |> tl()
40+
%CB{cb | a: [item], b: new_b}
3741
end
3842

3943
@doc """
4044
Converts a circular buffer to a list. The list is ordered from oldest to newest
4145
elements based on their insertion order.
4246
"""
43-
def to_list(%CB{}=cb) do
44-
cb.q
45-
|> :queue.reverse
46-
|> :queue.to_list
47+
def to_list(%CB{} = cb) do
48+
cb.b ++ Enum.reverse(cb.a)
4749
end
4850

4951
@doc """
5052
Returns the newest element in the buffer
5153
"""
52-
def newest(%CB{}=cb) do
53-
case :queue.peek(cb.q) do
54-
{_, val} -> val
55-
:empty -> nil
56-
end
57-
end
54+
def newest(%CB{a: [newest | _rest]}), do: newest
55+
def newest(%CB{b: []}), do: nil
5856

5957
@doc """
6058
Returns the oldest element in the buffer
6159
"""
62-
def oldest(%CB{}=cb) do
63-
case :queue.peek_r(cb.q) do
64-
{_, val} -> val
65-
:empty -> nil
66-
end
67-
end
60+
def oldest(%CB{b: [oldest | _rest]}), do: oldest
61+
def oldest(%CB{a: a}), do: List.last(a)
6862

6963
@doc """
7064
Checks the buffer to see if its empty
7165
"""
72-
def empty?(%CB{}=cb) do
73-
:queue.is_empty(cb.q)
66+
def empty?(%CB{} = cb) do
67+
cb.count == 0
7468
end
7569

7670
defimpl Enumerable do
@@ -79,7 +73,7 @@ defmodule CircularBuffer do
7973
end
8074

8175
def member?(cb, element) do
82-
{:ok, :queue.member(element, cb.q)}
76+
{:ok, Enum.member?(cb.a, element) or Enum.member?(cb.b, element)}
8377
end
8478

8579
def reduce(cb, acc, fun) do

test/circular_buffer_test.exs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ defmodule CircularBufferTest do
1919
end
2020
end
2121

22+
def slow_cb_count(cb) do
23+
Enum.count(cb.a) + Enum.count(cb.b)
24+
end
25+
2226
property "the count matches the number of elements in the buffer" do
2327
forall {size, is} <- {pos_integer(), list(integer())} do
2428
buffer = Enum.reduce(is, CB.new(size), fn i, cb -> CB.insert(cb, i) end)
25-
:queue.len(buffer.q) == buffer.count
29+
slow_cb_count(buffer) == buffer.count
2630
end
2731
end
2832

@@ -60,7 +64,7 @@ defmodule CircularBufferTest do
6064
property "the number of elements never exceeds the size of the buffer" do
6165
forall {size, is} <- size_and_list() do
6266
buffer = Enum.reduce(is, CB.new(size), fn i, cb -> CB.insert(cb, i) end)
63-
:queue.len(buffer.q) <= size
67+
slow_cb_count(buffer) <= size
6468
end
6569
end
6670

@@ -74,10 +78,9 @@ defmodule CircularBufferTest do
7478

7579
slice =
7680
iis
77-
|> Enum.reverse
78-
|> Enum.take(size)
81+
|> Enum.take(-size)
7982

80-
:queue.to_list(buffer.q) == slice
83+
CB.to_list(buffer) == slice
8184
end
8285
end
8386

0 commit comments

Comments
 (0)