Skip to content

Commit 3bd45ec

Browse files
authored
Merge pull request swiftlang#59417 from lorentey/fix-generic-set-intersection
[stdlib] Fix handling of duplicate items in generic `Set.intersection`
2 parents 0bd04f8 + 01d7b57 commit 3bd45ec

File tree

2 files changed

+44
-15
lines changed

2 files changed

+44
-15
lines changed

stdlib/public/core/NativeSet.swift

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -739,20 +739,29 @@ extension _NativeSet {
739739
internal __consuming func intersection(
740740
_ other: _NativeSet<Element>
741741
) -> _NativeSet<Element> {
742-
// Prefer to iterate over the smaller set. However, we must be careful to
743-
// only include elements from `self`, not `other`.
744-
guard self.count <= other.count else {
745-
return genericIntersection(other)
746-
}
747-
// Rather than directly creating a new set, mark common elements in a bitset
748-
// first. This minimizes hashing, and ensures that we'll have an exact count
749-
// for the result set, preventing rehashings during insertions.
750-
return _UnsafeBitset.withTemporaryBitset(capacity: bucketCount) { bitset in
742+
// Rather than directly creating a new set, mark common elements in a
743+
// bitset first. This minimizes hashing, and ensures that we'll have an
744+
// exact count for the result set, preventing rehashings during
745+
// insertions.
746+
_UnsafeBitset.withTemporaryBitset(capacity: bucketCount) { bitset in
751747
var count = 0
752-
for bucket in hashTable {
753-
if other.find(uncheckedElement(at: bucket)).found {
754-
bitset.uncheckedInsert(bucket.offset)
755-
count += 1
748+
// Prefer to iterate over the smaller set. However, we must be careful to
749+
// only include elements from `self`, not `other`.
750+
if self.count > other.count {
751+
for element in other {
752+
let (bucket, found) = find(element)
753+
if found {
754+
// `other` is a `Set`, so we can assume it doesn't have duplicates.
755+
bitset.uncheckedInsert(bucket.offset)
756+
count += 1
757+
}
758+
}
759+
} else {
760+
for bucket in hashTable {
761+
if other.find(uncheckedElement(at: bucket)).found {
762+
bitset.uncheckedInsert(bucket.offset)
763+
count += 1
764+
}
756765
}
757766
}
758767
return extractSubset(using: bitset, count: count)
@@ -771,8 +780,9 @@ extension _NativeSet {
771780
var count = 0
772781
for element in other {
773782
let (bucket, found) = find(element)
774-
if found {
775-
bitset.uncheckedInsert(bucket.offset)
783+
// Note: we need to be careful not to increment `count` here if the
784+
// element is a duplicate item.
785+
if found, bitset.uncheckedInsert(bucket.offset) {
776786
count += 1
777787
}
778788
}

validation-test/stdlib/SetOperations.swift.gyb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ suite.test("intersection.${inputKind}.${argumentKind}") {
188188
% end
189189
% end
190190

191+
% for inputKind, inputGenerator in inputKinds.items():
192+
% for argumentKind, argumentGenerator in argumentKinds.items():
193+
% needsCocoa = inputKind == "cocoa" or argumentKind == "cocoa"
194+
% if needsCocoa:
195+
#if _runtime(_ObjC)
196+
% end
197+
suite.test("intersection.same-number-of-duplicates-as-missing-items") {
198+
let a = ${inputGenerator}([3, 6, 0, 1, 5, 2, 4], identity: 1)
199+
let b = ${argumentGenerator}([0, 1, 1, 2, 3, 4, 5], identity: 2)
200+
let c = a.intersection(b)
201+
expectEqual(c.count, 6)
202+
expectEqual(c, makeNativeSet(0 ..< 6, identity: 1))
203+
}
204+
% if needsCocoa:
205+
#endif
206+
% end
207+
% end
208+
% end
209+
191210
% for inputKind, inputGenerator in inputKinds.items():
192211
% for argumentKind, argumentGenerator in argumentKinds.items():
193212
% needsCocoa = inputKind == "cocoa" or argumentKind == "cocoa"

0 commit comments

Comments
 (0)