%% -*- erlang-indent-level: 2 -*- %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%@doc %% IMMUTABLE DISJOINT SETS OF ARBITRARY TERMS %% %% The disjoint set forests data structure, for elements of arbitrary types. %% Note that the find operation mutates the set. %% %% We could do this more efficiently if we restricted the elements to integers, %% and used the (mutable) hipe arrays. For arbitrary terms ETS could be used, %% for a persistent interface (which isn't that nice when even accessors return %% modified copies), the array module could be used. -module(hipe_dsets). -export([new/1, find/2, union/3, to_map/1, to_rllist/1]). -export_type([dsets/1]). -opaque dsets(X) :: #{X => {node, X} | {root, non_neg_integer()}}. -spec new([E]) -> dsets(E). new(Elems) -> maps:from_list([{E,{root,0}} || E <- Elems]). -spec find(E, dsets(E)) -> {E, dsets(E)}. find(E, DS0) -> case DS0 of #{E := {root,_}} -> {E, DS0}; #{E := {node,N}} -> case find(N, DS0) of {N, _}=T -> T; {R, DS1} -> {R, DS1#{E := {node,R}}} end; _ -> error(badarg, [E, DS0]) end. -spec union(E, E, dsets(E)) -> dsets(E). union(X, Y, DS0) -> {XRoot, DS1} = find(X, DS0), case find(Y, DS1) of {XRoot, DS2} -> DS2; {YRoot, DS2} -> #{XRoot := {root,XRR}, YRoot := {root,YRR}} = DS2, if XRR < YRR -> DS2#{XRoot := {node,YRoot}}; XRR > YRR -> DS2#{YRoot := {node,XRoot}}; true -> DS2#{YRoot := {node,XRoot}, XRoot := {root,XRR+1}} end end. -spec to_map(dsets(E)) -> {#{Elem::E => Root::E}, dsets(E)}. to_map(DS) -> to_map(maps:keys(DS), DS, #{}). to_map([], DS, Acc) -> {Acc, DS}; to_map([K|Ks], DS0, Acc) -> {KR, DS} = find(K, DS0), to_map(Ks, DS, Acc#{K => KR}). -spec to_rllist(dsets(E)) -> {[{Root::E, Elems::[E]}], dsets(E)}. to_rllist(DS0) -> {Lists, DS} = to_rllist(maps:keys(DS0), #{}, DS0), {maps:to_list(Lists), DS}. to_rllist([], Acc, DS) -> {Acc, DS}; to_rllist([E|Es], Acc, DS0) -> {ERoot, DS} = find(E, DS0), to_rllist(Es, map_append(ERoot, E, Acc), DS). map_append(Key, Elem, Map) -> case Map of #{Key := List} -> Map#{Key := [Elem|List]}; #{} -> Map#{Key => [Elem]} end.