Current section
Files
Jump to
Current section
Files
lib/zset.ex
defmodule ZSet do
@moduledoc """
A Red Black Tree implementation that follows the Redis API, complete with scores,
and with the same big O running time.
"""
# @behaviour Set
@type score_tuple :: {integer, String.t}
@opaque t :: %__MODULE__{
members: Tree.S,
size: non_neg_integer
}
defstruct members: Tree.new, size: 0
@spec new(score_tuple) :: t
def new(tuples \\ []) do
members = Tree.new
new_set = %ZSet{
members: members
}
Enum.reduce(tuples, new_set, fn(tuple, zset) ->
zadd(zset, tuple)
end)
end
@spec zcard(t) :: integer
def zcard(%ZSet{size: size}) do
size
end
@spec to_list(t, :atom) :: list(score_tuple)
def to_list(%ZSet{members: members}, opt \\ nil) do
case opt do
:withscores ->
Tree.to_list(members) |> Enum.map(fn {a,b} -> {b,a} end) |> Enum.reverse
_ ->
Tree.to_list(members) |> Enum.map(fn {a,_b} -> a end) |> Enum.reverse
end
end
@spec zadd(zset :: t, score :: integer, score_tuple :: String.t) :: t
def zadd(zset, score, str) when is_integer(score) and is_binary(str) do
zadd(zset, {score, str})
end
@spec zadd(zset :: t, score_tuple) :: t
defp zadd(%ZSet{members: members}, {score, str}) when is_integer(score) and is_binary(str) do
tree = Tree.insert(members, str, score)
{_, new_size} = tree
%ZSet{members: tree, size: new_size}
end
@spec zrem(t, String.t) :: t
def zrem(%ZSet{}=zset, []), do: zset
def zrem(%ZSet{size: 0}, k), do: {:error, {:non_existent_key, k}}
def zrem(%ZSet{members: members}, string) when is_binary(string) do
tree = Tree.delete members, string
{new_members, new_size} = tree
new_size = case new_members do
nil -> 0
_ -> new_size
end
%ZSet{members: tree, size: new_size}
end
@spec zrem(t, list(String.t)) :: t
def zrem(%ZSet{}=zset, [h|t]) when is_binary(h) do
zrem (zrem zset, h), t
end
@spec zscore(t, String.t) :: integer
def zscore(%ZSet{members: members}, key) do
Tree.fetch members, key
end
@spec zincrby(t, non_neg_integer(), String.t) :: t
def zincrby(%ZSet{members: members}=zset, score, key) do
orig_score = zscore(zset, key)
orig_score = case orig_score do
nil -> 0
_ -> orig_score
end
tree = Tree.insert(members, key, orig_score + score)
{_, new_size} = tree
%ZSet{members: tree, size: new_size}
end
@spec at(t, integer) :: tuple
def at(%ZSet{members: members}, index) do
Tree.nth(members, index)
end
@spec zcount(t, integer, integer) :: integer
def zcount(%ZSet{members: tree}, min, max) do
count = Tree.filter_range_by_value(tree, min, max)
case count do
nil -> 0
a -> a |> Enum.count
end
end
@spec zlexcount(t, String.t, String.t) :: integer
def zlexcount(%ZSet{members: tree}, min, max) do
count = Tree.filter_range(tree, min, max)
case count do
nil -> 0
a -> a |> Enum.count
end
end
@spec zrangebylex(t, String.t, String.t) :: integer
def zrangebylex(%ZSet{members: tree}, min, max, opt \\ nil) do
min_graphemes = min |> String.graphemes
max_graphemes = max |> String.graphemes
{l_inc, l} = case min_graphemes do
["[" | rest] -> {true, rest |> Enum.join("")}
["(" | rest] -> {false, rest |> Enum.join("")}
_ -> {true, min_graphemes |> Enum.join("")}
end
{r_inc, r} = case max_graphemes do
["[" | rest] -> {true, rest |> Enum.join("")}
["(" | rest] -> {false, rest |> Enum.join("")}
_ -> {true, max_graphemes |> Enum.join("")}
end
count = Tree.filter_range(tree, l, r, l_inc, r_inc)
case count do
nil -> 0
t ->
if opt == :withscores do
t
else
t |> Enum.map(fn {a,_b} -> a end)
end
end
end
@spec zrevrangebylex(t, String.t, String.t) :: integer
def zrevrangebylex(zset, max, min) do
zrangebylex(zset, min, max) |> Enum.reverse
end
@spec zrange(t, integer, integer, :atom) :: integer
def zrange(%ZSet{members: tree}, min, max, opt \\ nil) do
count = Tree.range(tree, min, max)
case count do
nil -> 0
t ->
if opt == :withscores do
t
else
t |> Enum.map(fn {a,_b} -> a end)
end
end
end
@spec zrangebyscore(t, integer, integer, :atom) :: term
def zrangebyscore(%ZSet{members: tree}, min, max, opt \\ nil) do
min_graphemes = min |> String.graphemes
max_graphemes = max |> String.graphemes
{l_inc, l} = case min_graphemes do
["[" | rest] -> {true, rest |> Enum.join("")}
["(" | rest] -> {false, rest |> Enum.join("")}
_ -> {true, min_graphemes |> Enum.join("")}
end
{r_inc, r} = case max_graphemes do
["[" | rest] -> {true, rest |> Enum.join("")}
["(" | rest] -> {false, rest |> Enum.join("")}
_ -> {true, max_graphemes |> Enum.join("")}
end
ret = Tree.filter_range_by_value(tree,
String.to_integer(l),
String.to_integer(r),
l_inc,
r_inc)
if opt == :withscores do
ret
else
ret |> Enum.map(fn {a,_b} -> a end)
end
end
@spec zrank(t, string, :atom) :: term
def zrank(%ZSet{members: tree}, key, opt \\ nil) do
Tree.index(tree, key)
end
end
defimpl Inspect, for: ZSet do
import Inspect.Algebra
def inspect(set, opts) do
concat ["#ZSet<", Inspect.List.inspect(ZSet.to_list(set), opts), ">"]
end
end