defmodule HRW do
@moduledoc """
HRW (Highest Random Weight), also known as rendezvous hashing, maps a key
to a node out of a set in a way that stays stable when nodes are added or
removed.
This module is stateless. For O(log n) lookups over large node sets, see
`HRW.Skeleton`.
"""
@doc """
Returns the node responsible for `key`.
Each node is hashed together with the key; the highest-scoring node wins.
## Options
* `:hash_fn` - a function `term -> integer`. Defaults to `&:erlang.phash2/1`.
## Examples
iex> HRW.owner("192.168.0.1", ["server1", "server2", "server3"])
"server2"
"""
@spec owner(term(), [term()], keyword()) :: term()
def owner(key, nodes, opts \\ []) do
hash_fn = Keyword.get(opts, :hash_fn, &:erlang.phash2/1)
Enum.max_by(nodes, fn node ->
hash_fn.({key, node})
end)
end
@doc """
Returns the top `count` nodes responsible for `key`, in descending weight order.
## Options
* `:hash_fn` - a function `term -> integer`. Defaults to `&:erlang.phash2/1`.
## Examples
iex> HRW.owners("192.168.0.1", ["server1", "server2", "server3"], 2)
["server2", "server3"]
"""
@spec owners(term(), [term()], non_neg_integer(), keyword()) :: [term()]
def owners(key, nodes, count, opts \\ []) do
hash_fn = Keyword.get(opts, :hash_fn, &:erlang.phash2/1)
nodes
|> Enum.sort_by(fn node -> hash_fn.({key, node}) end, :desc)
|> Enum.take(count)
end
end