File 6561-Add-equality-check-functions-to-sets-ordsets-and-gb_.patch of Package erlang
From 41544381428927c2c6e892e75173f1f4add4ff6b Mon Sep 17 00:00:00 2001
From: Jan Uhlig <juhlig@hnc-agency.org>
Date: Wed, 7 Jun 2023 14:57:37 +0200
Subject: [PATCH] Add equality check functions to sets, ordsets and gb_sets
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Maria Scott <maria-12648430@hnc-agency.org>
Co-authored-by: Björn Gustavsson <bgustavsson@gmail.com>
---
lib/stdlib/doc/src/gb_sets.xml | 11 +++++
lib/stdlib/doc/src/ordsets.xml | 11 +++++
lib/stdlib/doc/src/sets.xml | 13 ++++++
lib/stdlib/src/gb_sets.erl | 37 ++++++++++++---
lib/stdlib/src/ordsets.erl | 12 ++++-
lib/stdlib/src/sets.erl | 21 ++++++++-
lib/stdlib/test/sets_SUITE.erl | 75 +++++++++++++++++++------------
lib/stdlib/test/sets_test_lib.erl | 8 +++-
8 files changed, 150 insertions(+), 38 deletions(-)
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
index 4107ba0c63..d8271f9608 100644
--- a/lib/stdlib/doc/src/gb_sets.xml
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -270,6 +270,17 @@
</desc>
</func>
+ <func>
+ <name name="is_equal" arity="2" since="OTP 26.1"/>
+ <fsummary>Test for equality.</fsummary>
+ <desc>
+ <p>Returns <c>true</c> if <c><anno>Set1</anno></c> and
+ <c><anno>Set2</anno></c> are equal, that is when every element of
+ one set is also a member of the respective other set, otherwise
+ <c>false</c>.</p>
+ </desc>
+ </func>
+
<func>
<name name="is_member" arity="2" since=""/>
<fsummary>Test for membership of a set.</fsummary>
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
index 0026377f73..b7575ea31b 100644
--- a/lib/stdlib/doc/src/ordsets.xml
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -164,6 +164,17 @@
</desc>
</func>
+ <func>
+ <name name="is_equal" arity="2" since="OTP 26.1"/>
+ <fsummary>Test for equality.</fsummary>
+ <desc>
+ <p>Returns <c>true</c> if <c><anno>Ordset1</anno></c> and
+ <c><anno>Ordset2</anno></c> are equal, that is when every element of
+ one set is also a member of the respective other set, otherwise
+ <c>false</c>.</p>
+ </desc>
+ </func>
+
<func>
<name name="is_set" arity="1" since=""/>
<fsummary>Test for an <c>Ordset</c>.</fsummary>
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
index 4465172848..45a8959556 100644
--- a/lib/stdlib/doc/src/sets.xml
+++ b/lib/stdlib/doc/src/sets.xml
@@ -98,6 +98,8 @@
</item>
<item><seemfa marker="#is_empty/1"><c>is_empty/1</c></seemfa>
</item>
+ <item><seemfa marker="#is_equal/2"><c>is_equal/2</c></seemfa>
+ </item>
<item><seemfa marker="#is_set/1"><c>is_set/1</c></seemfa>
</item>
<item><seemfa marker="#is_subset/2"><c>is_subset/2</c></seemfa>
@@ -257,6 +259,17 @@ true</pre>
</desc>
</func>
+ <func>
+ <name name="is_equal" arity="2" since="OTP 26.1"/>
+ <fsummary>Test for equality.</fsummary>
+ <desc>
+ <p>Returns <c>true</c> if <c><anno>Set1</anno></c> and
+ <c><anno>Set2</anno></c> are equal, that is when every element of
+ one set is also a member of the respective other set, otherwise
+ <c>false</c>.</p>
+ </desc>
+ </func>
+
<func>
<name name="is_set" arity="1" since=""/>
<fsummary>Test for a <c>Set</c>.</fsummary>
diff --git a/lib/stdlib/src/gb_sets.erl b/lib/stdlib/src/gb_sets.erl
index 217dff2b3c..f94996a669 100644
--- a/lib/stdlib/src/gb_sets.erl
+++ b/lib/stdlib/src/gb_sets.erl
@@ -153,11 +153,12 @@
-export([empty/0, is_empty/1, size/1, singleton/1, is_member/2,
insert/2, add/2, delete/2, delete_any/2, balance/1, union/2,
- union/1, intersection/2, intersection/1, is_disjoint/2, difference/2,
- is_subset/2, to_list/1, from_list/1, from_ordset/1, smallest/1,
- largest/1, take_smallest/1, take_largest/1, iterator/1,
- iterator_from/2, next/1, filter/2, fold/3, map/2, filtermap/2,
- is_set/1]).
+ union/1, intersection/2, intersection/1, is_equal/2,
+ is_disjoint/2, difference/2, is_subset/2, to_list/1,
+ from_list/1, from_ordset/1, smallest/1, largest/1,
+ take_smallest/1, take_largest/1, iterator/1,
+ iterator_from/2, next/1, filter/2, fold/3, map/2,
+ filtermap/2, is_set/1]).
%% `sets' compatibility aliases:
@@ -230,6 +231,32 @@ is_empty(_) ->
size({Size, _}) ->
Size.
+-spec is_equal(Set1, Set2) -> boolean() when
+ Set1 :: set(),
+ Set2 :: set().
+
+is_equal({Size, S1}, {Size, _} = S2) ->
+ try is_equal_1(S1, to_list(S2)) of
+ [] ->
+ true
+ catch
+ throw:not_equal ->
+ false
+ end;
+is_equal({_, _}, {_, _}) ->
+ false.
+
+is_equal_1(nil, Keys) ->
+ Keys;
+is_equal_1({Key1, Smaller, Bigger}, Keys0) ->
+ [Key2 | Keys] = is_equal_1(Smaller, Keys0),
+ if
+ Key1 == Key2 ->
+ is_equal_1(Bigger, Keys);
+ true ->
+ throw(not_equal)
+ end.
+
-spec singleton(Element) -> set(Element).
singleton(Key) ->
diff --git a/lib/stdlib/src/ordsets.erl b/lib/stdlib/src/ordsets.erl
index bad539c9f4..2001a1338b 100644
--- a/lib/stdlib/src/ordsets.erl
+++ b/lib/stdlib/src/ordsets.erl
@@ -22,7 +22,7 @@
-export([new/0,is_set/1,size/1,is_empty/1,to_list/1,from_list/1]).
-export([is_element/2,add_element/2,del_element/2]).
-export([union/2,union/1,intersection/2,intersection/1]).
--export([is_disjoint/2]).
+-export([is_equal/2, is_disjoint/2]).
-export([subtract/2,is_subset/2]).
-export([fold/3,filter/2,map/2,filtermap/2]).
@@ -67,6 +67,16 @@ size(S) -> length(S).
is_empty(S) -> S=:=[].
+%% is_equal(OrdSet1, OrdSet2) -> boolean().
+%% Return 'true' if OrdSet1 and OrdSet2 contain the same elements,
+%% otherwise 'false'.
+-spec is_equal(Ordset1, Ordset2) -> boolean() when
+ Ordset1 :: ordset(_),
+ Ordset2 :: ordset(_).
+
+is_equal(S1, S2) when is_list(S1), is_list(S2) ->
+ S1 == S2.
+
%% to_list(OrdSet) -> [Elem].
%% Return the elements in OrdSet as a list.
diff --git a/lib/stdlib/src/sets.erl b/lib/stdlib/src/sets.erl
index 27e6038f67..5cf42560fc 100644
--- a/lib/stdlib/src/sets.erl
+++ b/lib/stdlib/src/sets.erl
@@ -44,7 +44,7 @@
-export([new/0,is_set/1,size/1,is_empty/1,to_list/1,from_list/1]).
-export([is_element/2,add_element/2,del_element/2]).
-export([union/2,union/1,intersection/2,intersection/1]).
--export([is_disjoint/2]).
+-export([is_equal/2, is_disjoint/2]).
-export([subtract/2,is_subset/2]).
-export([fold/3,filter/2,map/2,filtermap/2]).
-export([new/1, from_list/2]).
@@ -146,6 +146,22 @@ size(#set{size=Size}) -> Size.
is_empty(#{}=S) -> map_size(S)=:=0;
is_empty(#set{size=Size}) -> Size=:=0.
+%% is_equal(Set1, Set2) -> boolean().
+%% Return 'true' if Set1 and Set2 contain the same elements,
+%% otherwise 'false'.
+-spec is_equal(Set1, Set2) -> boolean() when
+ Set1 :: set(),
+ Set2 :: set().
+is_equal(S1, S2) ->
+ case size(S1) =:= size(S2) of
+ true when S1 =:= S2 ->
+ true;
+ true ->
+ is_subset(S1, S2);
+ false ->
+ false
+ end.
+
%% to_list(Set) -> [Elem].
%% Return the elements in Set as a list.
-spec to_list(Set) -> List when
diff --git a/lib/stdlib/test/sets_SUITE.erl b/lib/stdlib/test/sets_SUITE.erl
index ea0b8c32b0..b7bbaa96c7 100644
--- a/lib/stdlib/test/sets_SUITE.erl
+++ b/lib/stdlib/test/sets_SUITE.erl
@@ -28,8 +28,8 @@
init_per_testcase/2,end_per_testcase/2,
create/1,add_element/1,del_element/1,
subtract/1,intersection/1,union/1,is_subset/1,
- is_disjoint/1,is_set/1,is_empty/1,fold/1,filter/1, map/1,
- filtermap/1, take_smallest/1,take_largest/1, iterate/1]).
+ is_equal/1, is_disjoint/1,is_set/1,is_empty/1,fold/1,filter/1,
+ map/1, filtermap/1, take_smallest/1,take_largest/1, iterate/1]).
-include_lib("common_test/include/ct.hrl").
@@ -49,7 +49,7 @@ all() ->
[create, add_element, del_element, subtract,
intersection, union, is_subset, is_set, fold, filter, map,
filtermap, take_smallest, take_largest, iterate, is_empty,
- is_disjoint].
+ is_disjoint, is_equal].
groups() ->
[].
@@ -94,13 +94,13 @@ add_element_1(List, M) ->
%% elements one at the time.
S2 = foldl(fun(El, Set) -> M(add_element, {El,Set}) end,
M(empty, []), List),
- true = M(equal, {S,S2}),
+ true = M(is_equal, {S,S2}),
%% Insert elements, randomly delete inserted elements,
%% and re-inserted all deleted elements at the end.
S3 = add_element_del(List, M, M(empty, []), [], []),
- true = M(equal, {S2,S3}),
- true = M(equal, {S,S3}),
+ true = M(is_equal, {S2,S3}),
+ true = M(is_equal, {S,S3}),
S.
add_element_del([H|T], M, S, Del, []) ->
@@ -124,12 +124,12 @@ del_element(Config) when is_list(Config) ->
del_element_1(List, M) ->
S0 = M(from_list, List),
Empty = foldl(fun(El, Set) -> M(del_element, {El,Set}) end, S0, List),
- true = M(equal, {Empty,M(empty, [])}),
+ true = M(is_equal, {Empty,M(empty, [])}),
true = M(is_empty, Empty),
S1 = foldl(fun(El, Set) ->
M(add_element, {El,Set})
end, S0, reverse(List)),
- true = M(equal, {S0,S1}),
+ true = M(is_equal, {S0,S1}),
S1.
subtract(Config) when is_list(Config) ->
@@ -149,7 +149,7 @@ subtract_1(List, M) ->
%% Trivial cases.
true = M(is_empty, M(subtract, {Empty,S0})),
- true = M(equal, {S0,M(subtract, {S0,Empty})}),
+ true = M(is_equal, {S0,M(subtract, {S0,Empty})}),
%% Not so trivial.
subtract_check(List, mutate_some(remove_some(List, 0.4)), M),
@@ -168,7 +168,7 @@ one_subtract_check(A, B, M) ->
BSet = M(from_list, B),
DiffSet = M(subtract, {ASet,BSet}),
Diff = ASorted -- BSorted,
- true = M(equal, {DiffSet,M(from_list, Diff)}),
+ true = M(is_equal, {DiffSet,M(from_list, Diff)}),
Diff = lists:sort(M(to_list, DiffSet)),
DiffSet.
@@ -180,15 +180,15 @@ intersection_1(List, M) ->
S0 = M(from_list, List),
%% Intersection with self.
- true = M(equal, {S0,M(intersection, {S0,S0})}),
- true = M(equal, {S0,M(intersection, [S0,S0])}),
- true = M(equal, {S0,M(intersection, [S0,S0,S0])}),
- true = M(equal, {S0,M(intersection, [S0])}),
+ true = M(is_equal, {S0,M(intersection, {S0,S0})}),
+ true = M(is_equal, {S0,M(intersection, [S0,S0])}),
+ true = M(is_equal, {S0,M(intersection, [S0,S0,S0])}),
+ true = M(is_equal, {S0,M(intersection, [S0])}),
%% Intersection with empty.
Empty = M(empty, []),
- true = M(equal, {Empty,M(intersection, {S0,Empty})}),
- true = M(equal, {Empty,M(intersection, [S0,Empty,S0,Empty])}),
+ true = M(is_equal, {Empty,M(intersection, {S0,Empty})}),
+ true = M(is_equal, {Empty,M(intersection, [S0,Empty,S0,Empty])}),
%% The intersection of no sets is undefined.
{'EXIT',_} = (catch M(intersection, [])),
@@ -229,7 +229,7 @@ check_intersection(Orig, Mutated, M) ->
Intersection = [El || El <- Mutated, not is_tuple(El)],
SortedIntersection = lists:usort(Intersection),
IntersectionSet = M(intersection, {OrigSet,MutatedSet}),
- true = M(equal, {IntersectionSet,M(from_list, SortedIntersection)}),
+ true = M(is_equal, {IntersectionSet,M(from_list, SortedIntersection)}),
SortedIntersection = lists:sort(M(to_list, IntersectionSet)),
IntersectionSet.
@@ -244,12 +244,12 @@ union_1(List, M) ->
%% Union with self and empty.
Empty = M(empty, []),
- true = M(equal, {S,M(union, {S,S})}),
- true = M(equal, {S,M(union, [S,S])}),
- true = M(equal, {S,M(union, [S,S,Empty])}),
- true = M(equal, {S,M(union, [S,Empty,S])}),
- true = M(equal, {S,M(union, {S,Empty})}),
- true = M(equal, {S,M(union, [S])}),
+ true = M(is_equal, {S,M(union, {S,S})}),
+ true = M(is_equal, {S,M(union, [S,S])}),
+ true = M(is_equal, {S,M(union, [S,S,Empty])}),
+ true = M(is_equal, {S,M(union, [S,Empty,S])}),
+ true = M(is_equal, {S,M(union, {S,Empty})}),
+ true = M(is_equal, {S,M(union, [S])}),
true = M(is_empty, M(union, [])),
%% Partial overlap.
@@ -272,9 +272,26 @@ check_union(Orig, Other, M) ->
SortedUnion = lists:usort(Union),
UnionSet = M(union, {OrigSet,OtherSet}),
SortedUnion = lists:sort(M(to_list, UnionSet)),
- M(equal, {UnionSet,M(from_list, Union)}),
+ M(is_equal, {UnionSet,M(from_list, Union)}),
UnionSet.
+is_equal(Config) when is_list(Config) ->
+ test_all([{1,132},{253,270},{299,311}], fun is_equal_1/2).
+
+is_equal_1(List, M) ->
+ S = M(from_list, List),
+ Empty = M(empty, []),
+
+ true = M(is_equal, {Empty, Empty}),
+ false = M(is_equal, {Empty, S}) andalso List =/= [],
+ false = M(is_equal, {S, Empty}) andalso List =/= [],
+ true = M(is_equal, {S, S}),
+
+ S1 = M(from_list, [make_ref()|List]),
+ false = M(is_equal, {S, S1}),
+
+ S.
+
is_subset(Config) when is_list(Config) ->
test_all([{1,132},{253,270},{299,311}], fun is_subset_1/2).
@@ -390,7 +407,7 @@ filter(Config) when is_list(Config) ->
filter_1(List, M) ->
S = M(from_list, List),
IsNumber = fun(X) -> is_number(X) end,
- M(equal, {M(from_list, lists:filter(IsNumber, List)),
+ M(is_equal, {M(from_list, lists:filter(IsNumber, List)),
M(filter, {IsNumber,S})}),
M(filter, {fun(X) -> is_atom(X) end,S}).
@@ -401,8 +418,8 @@ map(Config) when is_list(Config) ->
map_1(List, M) ->
S = M(from_list, List),
ToTuple = fun(X) -> {X} end,
- M(equal, {M(from_list, lists:map(ToTuple, List)),
- M(map, {ToTuple, S})}),
+ M(is_equal, {M(from_list, lists:map(ToTuple, List)),
+ M(map, {ToTuple, S})}),
M(map, {fun(_) -> x end, S}).
filtermap(Config) when is_list(Config) ->
@@ -416,8 +433,8 @@ filtermap_1(List, M) ->
(X) when is_integer(X) -> true;
(X) -> {true, {X}}
end,
- M(equal, {M(from_list, lists:filtermap(FMFun, List)),
- M(filtermap, {FMFun, S})}),
+ M(is_equal, {M(from_list, lists:filtermap(FMFun, List)),
+ M(filtermap, {FMFun, S})}),
M(empty, []).
%%%
diff --git a/lib/stdlib/test/sets_test_lib.erl b/lib/stdlib/test/sets_test_lib.erl
index 340dd9b1f2..5c1811aecb 100644
--- a/lib/stdlib/test/sets_test_lib.erl
+++ b/lib/stdlib/test/sets_test_lib.erl
@@ -25,17 +25,21 @@
new(Mod, Eq) ->
new(Mod, Eq, fun Mod:new/0, fun Mod:from_list/1).
-new(Mod, Eq, New, FromList) ->
+new(Mod, Eq0, New, FromList) ->
+ Eq = fun(S1, S2) ->
+ IsEqual = Eq0(S1, S2),
+ IsEqual = Mod:is_equal(S1, S2)
+ end,
fun (add_element, {El,S}) -> add_element(Mod, El, S);
(del_element, {El,S}) -> del_element(Mod, El, S);
(empty, []) -> New();
- (equal, {S1,S2}) -> Eq(S1, S2);
(filter, {F,S}) -> filter(Mod, F, S);
(filtermap, {F,S}) -> filtermap(Mod, F, S);
(fold, {F,A,S}) -> fold(Mod, F, A, S);
(from_list, L) -> FromList(L);
(intersection, {S1,S2}) -> intersection(Mod, Eq, S1, S2);
(intersection, Ss) -> intersection(Mod, Eq, Ss);
+ (is_equal, {S,Set}) -> Eq(S, Set);
(is_disjoint, {S,Set}) -> Mod:is_disjoint(S, Set);
(is_empty, S) -> Mod:is_empty(S);
(is_set, S) -> Mod:is_set(S);
--
2.35.3