94 lines
2.1 KiB
Elixir
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
|