farmbot_os/farmbot_celery_script/lib/circular_list.ex

94 lines
2.1 KiB
Elixir

defmodule CircularList do
defstruct current_index: 0, items: %{}, autoinc: -1
@opaque index :: number
@type data :: any
@type t :: %CircularList{
current_index: index,
autoinc: index,
items: %{optional(index) => data}
}
@spec new :: %CircularList{
current_index: 0,
autoinc: -1,
items: %{}
}
def new do
%CircularList{}
end
@spec get_index(t) :: index
def get_index(this) do
this.current_index
end
@spec current(t) :: data()
def current(this) do
at(this, get_index(this))
end
@spec at(t(), index) :: data()
def at(this, index) do
this.items[index]
end
@spec is_empty?(t()) :: boolean()
def is_empty?(%{items: items}) when map_size(items) == 0, do: true
def is_empty?(_this), do: false
@spec reduce(t(), (index, data -> data)) :: t()
def reduce(this, fun) do
results = Enum.reduce(this.items, %{}, fun)
%{this | items: results}
|> rotate()
end
@spec update_current(t, (data -> data)) :: t
def update_current(this, fun) do
index = get_index(this)
current_value = at(this, index)
if current_value do
result = fun.(current_value)
%{this | items: Map.put(this.items, index, result)}
else
fun.(:noop)
this
end
end
@spec rotate(t) :: t
def rotate(this) do
current = this.current_index
keys = Enum.sort(Map.keys(this.items))
# Grab first where index > this.current_index, or keys.first
next_key = Enum.find(keys, List.first(keys), fn key -> key > current end)
%CircularList{this | current_index: next_key}
end
@spec push(t, data) :: t
def push(this, item) do
# Bump autoinc
next_autoinc = this.autoinc + 1
next_items = Map.put(this.items, next_autoinc, item)
# Add the item
%CircularList{this | autoinc: next_autoinc, items: next_items}
end
@spec remove(t, index) :: t
def remove(this, index) do
if index in Map.keys(this.items) do
this
|> rotate()
|> Map.update(:items, %{}, fn old_items ->
Map.delete(old_items, index)
end)
else
this
end
end
end