Skip to content

Instantly share code, notes, and snippets.

@jadeallenx
Last active August 2, 2017 00:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jadeallenx/06dd3fa9c3008db490df25e8d30fffc7 to your computer and use it in GitHub Desktop.
Save jadeallenx/06dd3fa9c3008db490df25e8d30fffc7 to your computer and use it in GitHub Desktop.
ringbuffer interface using a normal Erlang list
-module(ringbuffer).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-compile([export_all]).
-else.
-export([
new/0,
new/1,
get_head/1,
add/2,
get_all/1
]).
-endif.
-define(DEFAULT_SIZE, 50).
-record(ringbuffer, {
depth = 0 :: non_neg_integer(),
size = 1 :: pos_integer(),
data = [] :: list()
}).
-type ringbuffer() :: #ringbuffer{}.
-spec new() -> ringbuffer().
%% @doc Create a new ringbuffer with a capacity for 50 values.
new() ->
new(?DEFAULT_SIZE).
-spec new( Size :: pos_integer() ) -> ringbuffer() | badarg.
%% @doc Create a new ringbuffer with the specified size. A badarg error will be
%% returned for non-integers or integers less than 1.
new(Size) when Size < 1 -> error(badarg);
new(Size) when is_integer(Size) -> #ringbuffer{ size = Size };
new(_Other) -> error(badarg).
-spec get_head( Buffer :: ringbuffer() ) -> { Value :: term() | '$empty', NewBuffer :: ringbuffer() }.
%% @doc Return the first value in the buffer along with the updated buffer data
%% structure. The atom `$empty' will be returned if the buffer is empty.
get_head(R = #ringbuffer{ data = [] }) -> {'$empty', R};
get_head(R = #ringbuffer{ depth = 0 }) -> {'$empty', R};
get_head(#ringbuffer{ size = S, depth = 1, data = [ H | _T ] }) ->
{H, #ringbuffer{ size = S }};
get_head(R = #ringbuffer{ depth = D, data = [ H | T ] }) ->
{H, R#ringbuffer{ depth = D - 1, data = T }}.
-spec add( Element :: term(), Buffer :: ringbuffer() ) -> NewBuffer :: ringbuffer().
%% @doc Add a new element to the buffer. Items are inserted at the front of
%% the buffer, so it can be considered a "LIFO" (last in, first out) style
%% stack.
add(Element, R = #ringbuffer{ data = L, size = S, depth = D }) when D == S ->
R#ringbuffer{ data = [ Element | L ] };
add(Element, R = #ringbuffer{ data = L, size = S, depth = D }) when D < S ->
R#ringbuffer{ depth = D + 1, data = [ Element | L ] }.
-spec get_all( Buffer :: ringbuffer() ) -> { [term()], NewBuffer :: ringbuffer() }.
%% Return the entire contents of the buffer. This function
%% <strong>always</strong> returns a list, which may be empty.
get_all(#ringbuffer{ size = S, depth = D, data = L }) when length(L) > D ->
{L1, _Ntail} = lists:split(D, L),
{L1, #ringbuffer{ size = S }};
get_all(#ringbuffer{ size = S, data = L }) ->
{L, #ringbuffer{ size = S }}.
-ifdef(TEST).
new_test_() ->
[
?_assertEqual({ringbuffer, 0, 50, []}, new()),
?_assertError(badarg, new(0)),
?_assertError(badarg, new(-1)),
?_assertError(badarg, new(2.5)),
?_assertError(badarg, new("foo")),
?_assertError(badarg, new(abc)),
?_assertEqual({ringbuffer, 0, 42, []}, new(42))
].
add_test_() ->
Empty = new(1),
Pop = #ringbuffer{ size = 1, depth = 1, data = [a] },
Pop1 = #ringbuffer{ size = 1, depth = 1, data = [b, a] },
[
?_assertEqual(#ringbuffer{ size = 1, depth = 1, data = [a] }, add(a, Empty)),
?_assertEqual(#ringbuffer{ size = 1, depth = 1, data = [b, a] }, add(b, Pop)),
?_assertEqual(#ringbuffer{ size = 1, depth = 1, data = [c, b, a] }, add(c, Pop1))
].
get_head_test_() ->
P = #ringbuffer{ size = 1, depth = 1, data = [b, a] },
C = #ringbuffer{ size = 10, depth = 2, data = [b, a] },
[
?_assertEqual({b, new(1)}, get_head(P)),
?_assertEqual({b, #ringbuffer{ size = 10, depth = 1, data = [a]}}, get_head(C))
].
get_all_test_() ->
P = #ringbuffer{ size = 1, depth = 1, data = [b, a] },
C = #ringbuffer{ size = 10, depth = 2, data = [b, a] },
[
?_assertEqual({[b], new(1)}, get_all(P)),
?_assertEqual({[b, a], new(10)}, get_all(C))
].
-endif.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment