Current section
Files
Jump to
Current section
Files
lib/cbuf.ex
defmodule Cbuf do
defstruct impl: :array.new(), size: 0, start: 0, current: 0
@doc """
Create a new circular buffer of a given size.
iex> Cbuf.new(5)
#Cbuf<[]>
"""
def new(size) when size > 0 do
%__MODULE__{
impl: :array.new(size: size),
size: size,
start: 0,
current: 0
}
end
@doc """
Calculate the allocated size for the buffer. This is maximum addressable size of the buffer, not how many values it currently contains. For the number of values in the current buffer, see `count/1`
iex> Cbuf.new(5) |> Cbuf.size()
5
"""
def size(buf) do
buf.size
end
@doc """
Insert a value into a circular buffer.
Values are inserted such that when the buffer is full, the oldest items are overwritten first.
iex> buf = Cbuf.new(5)
iex> buf |> Cbuf.insert("a") |> Cbuf.insert("b")
#Cbuf<["a", "b"]>
iex> buf = Cbuf.new(3)
iex> Enum.reduce(1..20, buf, fn(val, acc) -> Cbuf.insert(acc, val) end)
#Cbuf<[18, 19, 20]>
"""
def insert(buf, val) do
next_start =
if buf.start == buf.size - 1 do
0
else
buf.start + 1
end
next_current =
if buf.current == buf.size - 1 do
0
else
buf.current + 1
end
%{
buf
| impl: :array.set(buf.current, val, buf.impl),
start: next_start,
current: next_current
}
end
@doc """
Convert a circular buffer to a list. The list is ordered by age, oldest to newest.
iex> buf = Cbuf.new(5)
iex> buf |> Cbuf.insert("a") |> Cbuf.insert("b") |> Cbuf.to_list()
["a", "b"]
"""
def to_list(buf) do
do_to_list(buf, [], count(buf))
end
defp do_to_list(_buf, list, 0), do: list
defp do_to_list(buf, list, remaining) do
if buf.start == 0 do
do_to_list(
%{buf | start: buf.size - 1},
[:array.get(buf.size - 1, buf.impl) | list],
remaining - 1
)
else
do_to_list(
%{buf | start: buf.start - 1},
[:array.get(buf.start - 1, buf.impl) | list],
remaining - 1
)
end
end
@doc """
Returns the count of the non-empty values in the buffer.
iex> Cbuf.new(5) |> Cbuf.insert("hi") |> Cbuf.count()
1
iex> [1,2,3,4,5,6] |> Enum.reduce(Cbuf.new(5), fn(el, acc) -> Cbuf.insert(acc, el) end)
#Cbuf<[2, 3, 4, 5, 6]>
"""
def count(buf) do
:array.sparse_foldl(fn _idx, _val, total -> total + 1 end, 0, buf.impl)
end
@doc """
Queries `buf` for the presence of `val`.
iex> Cbuf.new(5) |> Cbuf.insert("hello") |> Cbuf.member?("hello")
true
iex> Cbuf.new(5) |> Cbuf.insert("hello") |> Cbuf.member?("nope")
false
"""
def member?(buf, val) do
:array.sparse_foldl(fn _idx, v, acc -> acc || val == v end, false, buf.impl)
end
defimpl Collectable do
def into(original) do
collector_fun = fn
buf, {:cont, val} -> Cbuf.insert(buf, val)
buf, :done -> buf
_buf, :halt -> :ok
end
{original, collector_fun}
end
end
defimpl Enumerable do
def count(buf), do: {:ok, Cbuf.count(buf)}
def member?(buf, val), do: {:ok, Cbuf.member?(buf, val)}
def reduce(buf, acc, fun), do: Enumerable.List.reduce(Cbuf.to_list(buf), acc, fun)
def slice(_buf), do: {:error, __MODULE__}
end
defimpl Inspect do
import Inspect.Algebra
def inspect(buf, opts) do
concat(["#Cbuf<", to_doc(Cbuf.to_list(buf), opts), ">"])
end
end
end