332 lines
7.5 KiB
Elixir
332 lines
7.5 KiB
Elixir
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
|
# SPDX-FileCopyrightText: 2012 Plataformatec
|
|
|
|
defmodule HashSet do
|
|
@moduledoc """
|
|
Tuple-based HashSet implementation.
|
|
|
|
This module is deprecated. Use the `MapSet` module instead.
|
|
"""
|
|
|
|
@moduledoc deprecated: "Use MapSet instead"
|
|
|
|
@node_bitmap 0b111
|
|
@node_shift 3
|
|
@node_size 8
|
|
@node_template :erlang.make_tuple(@node_size, [])
|
|
|
|
message = "Use the MapSet module instead"
|
|
|
|
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
|
@doc false
|
|
defstruct size: 0, root: @node_template
|
|
|
|
# Inline common instructions
|
|
@compile :inline_list_funcs
|
|
@compile {:inline, key_hash: 1, key_mask: 1, key_shift: 1}
|
|
|
|
@deprecated message
|
|
@spec new :: Set.t()
|
|
def new do
|
|
%HashSet{}
|
|
end
|
|
|
|
@deprecated message
|
|
def union(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) when size1 <= size2 do
|
|
set_fold(set1, set2, fn v, acc -> put(acc, v) end)
|
|
end
|
|
|
|
@deprecated message
|
|
def union(%HashSet{} = set1, %HashSet{} = set2) do
|
|
set_fold(set2, set1, fn v, acc -> put(acc, v) end)
|
|
end
|
|
|
|
@deprecated message
|
|
def intersection(%HashSet{} = set1, %HashSet{} = set2) do
|
|
set_fold(set1, %HashSet{}, fn v, acc ->
|
|
if member?(set2, v), do: put(acc, v), else: acc
|
|
end)
|
|
end
|
|
|
|
@deprecated message
|
|
def difference(%HashSet{} = set1, %HashSet{} = set2) do
|
|
set_fold(set2, set1, fn v, acc -> delete(acc, v) end)
|
|
end
|
|
|
|
@deprecated message
|
|
def to_list(set) do
|
|
set_fold(set, [], &[&1 | &2]) |> :lists.reverse()
|
|
end
|
|
|
|
@deprecated message
|
|
def equal?(%HashSet{size: size1} = set1, %HashSet{size: size2} = set2) do
|
|
case size1 do
|
|
^size2 -> subset?(set1, set2)
|
|
_ -> false
|
|
end
|
|
end
|
|
|
|
@deprecated message
|
|
def subset?(%HashSet{} = set1, %HashSet{} = set2) do
|
|
reduce(set1, {:cont, true}, fn member, acc ->
|
|
case member?(set2, member) do
|
|
true -> {:cont, acc}
|
|
_ -> {:halt, false}
|
|
end
|
|
end)
|
|
|> elem(1)
|
|
end
|
|
|
|
@deprecated message
|
|
def disjoint?(%HashSet{} = set1, %HashSet{} = set2) do
|
|
reduce(set2, {:cont, true}, fn member, acc ->
|
|
case member?(set1, member) do
|
|
false -> {:cont, acc}
|
|
_ -> {:halt, false}
|
|
end
|
|
end)
|
|
|> elem(1)
|
|
end
|
|
|
|
@deprecated message
|
|
def member?(%HashSet{root: root}, term) do
|
|
do_member?(root, term, key_hash(term))
|
|
end
|
|
|
|
@deprecated message
|
|
def put(%HashSet{root: root, size: size}, term) do
|
|
{root, counter} = do_put(root, term, key_hash(term))
|
|
%HashSet{root: root, size: size + counter}
|
|
end
|
|
|
|
@deprecated message
|
|
def delete(%HashSet{root: root, size: size} = set, term) do
|
|
case do_delete(root, term, key_hash(term)) do
|
|
{:ok, root} -> %HashSet{root: root, size: size - 1}
|
|
:error -> set
|
|
end
|
|
end
|
|
|
|
@doc false
|
|
def reduce(%HashSet{root: root}, acc, fun) do
|
|
do_reduce(root, acc, fun, @node_size, fn
|
|
{:suspend, acc} -> {:suspended, acc, &{:done, elem(&1, 1)}}
|
|
{:halt, acc} -> {:halted, acc}
|
|
{:cont, acc} -> {:done, acc}
|
|
end)
|
|
end
|
|
|
|
@deprecated message
|
|
def size(%HashSet{size: size}) do
|
|
size
|
|
end
|
|
|
|
## Set helpers
|
|
|
|
defp set_fold(%HashSet{root: root}, acc, fun) do
|
|
do_fold(root, acc, fun, @node_size)
|
|
end
|
|
|
|
## Set manipulation
|
|
|
|
defp do_member?(node, term, hash) do
|
|
index = key_mask(hash)
|
|
|
|
case elem(node, index) do
|
|
[] -> false
|
|
[^term | _] -> true
|
|
[_] -> false
|
|
[_ | n] -> do_member?(n, term, key_shift(hash))
|
|
end
|
|
end
|
|
|
|
defp do_put(node, term, hash) do
|
|
index = key_mask(hash)
|
|
|
|
case elem(node, index) do
|
|
[] ->
|
|
{put_elem(node, index, [term]), 1}
|
|
|
|
[^term | _] ->
|
|
{node, 0}
|
|
|
|
[t] ->
|
|
n = put_elem(@node_template, key_mask(key_shift(hash)), [term])
|
|
{put_elem(node, index, [t | n]), 1}
|
|
|
|
[t | n] ->
|
|
{n, counter} = do_put(n, term, key_shift(hash))
|
|
{put_elem(node, index, [t | n]), counter}
|
|
end
|
|
end
|
|
|
|
defp do_delete(node, term, hash) do
|
|
index = key_mask(hash)
|
|
|
|
case elem(node, index) do
|
|
[] ->
|
|
:error
|
|
|
|
[^term] ->
|
|
{:ok, put_elem(node, index, [])}
|
|
|
|
[_] ->
|
|
:error
|
|
|
|
[^term | n] ->
|
|
{:ok, put_elem(node, index, do_compact_node(n))}
|
|
|
|
[t | n] ->
|
|
case do_delete(n, term, key_shift(hash)) do
|
|
{:ok, @node_template} ->
|
|
{:ok, put_elem(node, index, [t])}
|
|
|
|
{:ok, n} ->
|
|
{:ok, put_elem(node, index, [t | n])}
|
|
|
|
:error ->
|
|
:error
|
|
end
|
|
end
|
|
end
|
|
|
|
Enum.each(0..(@node_size - 1), fn index ->
|
|
defp do_compact_node(node) when elem(node, unquote(index)) != [] do
|
|
case elem(node, unquote(index)) do
|
|
[t] ->
|
|
case put_elem(node, unquote(index), []) do
|
|
@node_template -> [t]
|
|
n -> [t | n]
|
|
end
|
|
|
|
[t | n] ->
|
|
[t | put_elem(node, unquote(index), do_compact_node(n))]
|
|
end
|
|
end
|
|
end)
|
|
|
|
## Set fold
|
|
|
|
defp do_fold_each([], acc, _fun), do: acc
|
|
defp do_fold_each([t], acc, fun), do: fun.(t, acc)
|
|
defp do_fold_each([t | n], acc, fun), do: do_fold(n, fun.(t, acc), fun, @node_size)
|
|
|
|
defp do_fold(node, acc, fun, count) when count > 0 do
|
|
acc = do_fold_each(:erlang.element(count, node), acc, fun)
|
|
do_fold(node, acc, fun, count - 1)
|
|
end
|
|
|
|
defp do_fold(_node, acc, _fun, 0) do
|
|
acc
|
|
end
|
|
|
|
## Set reduce
|
|
|
|
defp do_reduce_each(_node, {:halt, acc}, _fun, _next) do
|
|
{:halted, acc}
|
|
end
|
|
|
|
defp do_reduce_each(node, {:suspend, acc}, fun, next) do
|
|
{:suspended, acc, &do_reduce_each(node, &1, fun, next)}
|
|
end
|
|
|
|
defp do_reduce_each([], acc, _fun, next) do
|
|
next.(acc)
|
|
end
|
|
|
|
defp do_reduce_each([t], {:cont, acc}, fun, next) do
|
|
next.(fun.(t, acc))
|
|
end
|
|
|
|
defp do_reduce_each([t | n], {:cont, acc}, fun, next) do
|
|
do_reduce(n, fun.(t, acc), fun, @node_size, next)
|
|
end
|
|
|
|
defp do_reduce(node, acc, fun, count, next) when count > 0 do
|
|
do_reduce_each(
|
|
:erlang.element(count, node),
|
|
acc,
|
|
fun,
|
|
&do_reduce(node, &1, fun, count - 1, next)
|
|
)
|
|
end
|
|
|
|
defp do_reduce(_node, acc, _fun, 0, next) do
|
|
next.(acc)
|
|
end
|
|
|
|
## Key operations
|
|
|
|
import Bitwise
|
|
|
|
defp key_hash(key) do
|
|
:erlang.phash2(key)
|
|
end
|
|
|
|
defp key_mask(hash) do
|
|
hash &&& @node_bitmap
|
|
end
|
|
|
|
defp key_shift(hash) do
|
|
hash >>> @node_shift
|
|
end
|
|
end
|
|
|
|
defimpl Enumerable, for: HashSet do
|
|
@moduledoc false
|
|
@moduledoc deprecated: "Use MapSet instead"
|
|
|
|
def reduce(set, acc, fun) do
|
|
# Avoid warnings about HashSet being deprecated.
|
|
module = String.to_atom("HashSet")
|
|
module.reduce(set, acc, fun)
|
|
end
|
|
|
|
def member?(set, term) do
|
|
# Avoid warnings about HashSet being deprecated.
|
|
module = String.to_atom("HashSet")
|
|
{:ok, module.member?(set, term)}
|
|
end
|
|
|
|
def count(set) do
|
|
# Avoid warnings about HashSet being deprecated.
|
|
module = String.to_atom("HashSet")
|
|
{:ok, module.size(set)}
|
|
end
|
|
|
|
def slice(_set) do
|
|
{:error, __MODULE__}
|
|
end
|
|
end
|
|
|
|
defimpl Collectable, for: HashSet do
|
|
@moduledoc false
|
|
@moduledoc deprecated: "Use MapSet instead"
|
|
|
|
def into(original) do
|
|
# Avoid warnings about HashSet being deprecated.
|
|
module = String.to_atom("HashSet")
|
|
|
|
collector_fun = fn
|
|
set, {:cont, term} -> module.put(set, term)
|
|
set, :done -> set
|
|
_, :halt -> :ok
|
|
end
|
|
|
|
{original, collector_fun}
|
|
end
|
|
end
|
|
|
|
defimpl Inspect, for: HashSet do
|
|
@moduledoc false
|
|
@moduledoc deprecated: "Use MapSet instead"
|
|
import Inspect.Algebra
|
|
|
|
def inspect(set, opts) do
|
|
# Avoid warnings about HashSet being deprecated.
|
|
module = String.to_atom("HashSet")
|
|
concat(["#HashSet<", Inspect.List.inspect(module.to_list(set), opts), ">"])
|
|
end
|
|
end
|