Skip to content

Commit 6a45619

Browse files
rfourquetJeffBezanson
authored andcommitted
add issetequal and make hash/== generic for AbstractSet (#25368)
1 parent 36a492c commit 6a45619

File tree

5 files changed

+84
-41
lines changed

5 files changed

+84
-41
lines changed

NEWS.md

+4
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ This section lists changes that do not have deprecation warnings.
362362
trait; see its documentation for details. Types which support subtraction (operator
363363
`-`) must now implement `widen` for hashing to work inside heterogeneous arrays.
364364

365+
* `AbstractSet` objects are now considered equal by `==` and `isequal` if all of their
366+
elements are equal ([#25368]). This has required changing the hashing algorithm
367+
for `BitSet`.
368+
365369
* `findn(x::AbstractVector)` now return a 1-tuple with the vector of indices, to be
366370
consistent with higher order arrays ([#25365]).
367371

base/bitset.jl

+3-27
Original file line numberDiff line numberDiff line change
@@ -356,33 +356,9 @@ function ==(s1::BitSet, s2::BitSet)
356356
return true
357357
end
358358

359-
issubset(a::BitSet, b::BitSet) = isequal(a, intersect(a,b))
360-
<(a::BitSet, b::BitSet) = (a<=b) && !isequal(a,b)
361-
<=(a::BitSet, b::BitSet) = issubset(a, b)
362-
363-
const hashis_seed = UInt === UInt64 ? 0x88989f1fc7dea67d : 0xc7dea67d
364-
function hash(s::BitSet, h::UInt)
365-
h ⊻= hashis_seed
366-
bc = s.bits
367-
i = 1
368-
j = length(bc)
369-
370-
while j > 0 && bc[j] == CHK0
371-
# Skip trailing empty bytes to prevent extra space from changing the hash
372-
j -= 1
373-
end
374-
while i <= j && bc[i] == CHK0
375-
# Skip leading empty bytes to prevent extra space from changing the hash
376-
i += 1
377-
end
378-
i > j && return h # empty
379-
h = hash(i+s.offset, h) # normalized offset
380-
while j >= i
381-
h = hash(bc[j], h)
382-
j -= 1
383-
end
384-
h
385-
end
359+
issubset(a::BitSet, b::BitSet) = a == intersect(a,b)
360+
(a::BitSet, b::BitSet) = a <= b && a != b
361+
386362

387363
minimum(s::BitSet) = first(s)
388364
maximum(s::BitSet) = last(s)

base/exports.jl

+1
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@ export
642642
intersect,
643643
isempty,
644644
issubset,
645+
issetequal,
645646
keys,
646647
keytype,
647648
length,

base/set.jl

+30-14
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,11 @@ function symdiff!(s::AbstractSet, itr)
261261
s
262262
end
263263

264-
==(l::Set, r::Set) = (length(l) == length(r)) && (l <= r)
265-
<( l::Set, r::Set) = (length(l) < length(r)) && (l <= r)
266-
<=(l::Set, r::Set) = issubset(l, r)
264+
==(l::AbstractSet, r::AbstractSet) = length(l) == length(r) && l r
265+
# convenience functions for AbstractSet
266+
# (if needed, only their synonyms ⊊ and ⊆ must be specialized)
267+
<( l::AbstractSet, r::AbstractSet) = l r
268+
<=(l::AbstractSet, r::AbstractSet) = l r
267269

268270
"""
269271
issubset(a, b)
@@ -290,21 +292,35 @@ function issubset(l, r)
290292
end
291293
return true
292294
end
293-
294295
# use the implementation below when it becoms as efficient
295296
# issubset(l, r) = all(_in(r), l)
296297

297298
const = issubset
298-
(l::Set, r::Set) = <(l, r)
299-
(l::Set, r::Set) = !(l, r)
300-
(l, r) = issubset(r, l)
301-
(l::Set, r::Set) = !(l, r)
302-
(l::Set, r::Set) = <(r, l)
303299

304-
(l::T, r::T) where {T<:AbstractSet} = <(l, r)
305-
(l::T, r::T) where {T<:AbstractSet} = !(l, r)
306-
(l::T, r::T) where {T<:AbstractSet} = !(l, r)
307-
(l::T, r::T) where {T<:AbstractSet} = <(r, l)
300+
"""
301+
issetequal(a, b)
302+
303+
Determine whether `a` and `b` have the same elements. Equivalent
304+
to `a ⊆ b && b ⊆ a`.
305+
306+
# Examples
307+
```jldoctest
308+
julia> issetequal([1, 2], [1, 2, 3])
309+
false
310+
311+
julia> issetequal([1, 2], [2, 1])
312+
true
313+
```
314+
"""
315+
issetequal(l, r) = length(l) == length(r) && l r
316+
issetequal(l::AbstractSet, r::AbstractSet) = l == r
317+
318+
(l, r) = length(l) < length(r) && l r
319+
(l, r) = !(l, r)
320+
321+
(l, r) = r l
322+
(l, r) = r l
323+
(l, r) = r l
308324

309325
"""
310326
unique(itr)
@@ -534,7 +550,7 @@ function mapfilter(pred, f, itr, res)
534550
end
535551

536552
const hashs_seed = UInt === UInt64 ? 0x852ada37cfe8e0ce : 0xcfe8e0ce
537-
function hash(s::Set, h::UInt)
553+
function hash(s::AbstractSet, h::UInt)
538554
hv = hashs_seed
539555
for x in s
540556
hv ⊻= hash(x)

test/sets.jl

+46
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ end
6161
@test !isequal(Set{Any}([1,2,3,4]), Set{Int}([1,2,3]))
6262
@test !isequal(Set{Int}([1,2,3,4]), Set{Any}([1,2,3]))
6363
end
64+
65+
@testset "hash and == for Set/BitSet" begin
66+
for s = (Set([1]), Set(1:10), Set(-100:7:100))
67+
b = BitSet(s)
68+
@test hash(s) == hash(b)
69+
@test s == b
70+
end
71+
end
72+
6473
@testset "eltype, empty" begin
6574
s1 = empty(Set([1,"hello"]))
6675
@test isequal(s1, Set())
@@ -536,3 +545,40 @@ end
536545
# avoid recursive call issue #25384
537546
@test_throws MethodError replace!("")
538547
end
548+
549+
@testset "⊆, ⊊, ⊈, ⊇, ⊋, ⊉, <, <=, issetequal" begin
550+
a = [1, 2]
551+
b = [2, 1, 3]
552+
for C = (Tuple, identity, Set, BitSet)
553+
A = C(a)
554+
B = C(b)
555+
@test A B
556+
@test A B
557+
@test !(A B)
558+
@test !(A B)
559+
@test !(A B)
560+
@test A B
561+
@test !(B A)
562+
@test !(B A)
563+
@test B A
564+
@test B A
565+
@test B A
566+
@test !(B A)
567+
@test !issetequal(A, B)
568+
@test !issetequal(B, A)
569+
if A isa AbstractSet && B isa AbstractSet
570+
@test A <= B
571+
@test A < B
572+
@test !(A >= B)
573+
@test !(A > B)
574+
@test !(B <= A)
575+
@test !(B < A)
576+
@test B >= A
577+
@test B > A
578+
end
579+
for D = (Tuple, identity, Set, BitSet)
580+
@test issetequal(A, D(A))
581+
@test !issetequal(A, D(B))
582+
end
583+
end
584+
end

0 commit comments

Comments
 (0)