Skip to content

Commit 510e5e1

Browse files
authored
Merge pull request #3486 from xwu/SR-1818
Fix IndexSet intersection and symmetric difference [SR-1818]
2 parents 0e838c4 + c0c7046 commit 510e5e1

File tree

2 files changed

+42
-28
lines changed

2 files changed

+42
-28
lines changed

stdlib/public/SDK/Foundation/IndexSet.swift

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -551,21 +551,19 @@ public struct IndexSet : ReferenceConvertible, Equatable, BidirectionalCollectio
551551

552552
while let i = boundaryIterator.next() {
553553
if !flag {
554-
// Starting a range; if the edge is contained or not depends on the xor of this particular value.
555-
let startInclusive = self.contains(i) != other.contains(i)
556-
start = startInclusive ? i : i + 1
557-
flag = true
554+
// Start a range if one set contains but not the other.
555+
if self.contains(i) != other.contains(i) {
556+
flag = true
557+
start = i
558+
}
558559
} else {
559-
// Ending a range; if the edge is contained or not depends on the xor of this particular value.
560-
let endInclusive = self.contains(i) != other.contains(i)
561-
let end = endInclusive ? i + 1 : i
562-
if start < end {
563-
// Otherwise, we had an empty range
564-
result.insert(integersIn: start..<end)
560+
// End a range if both sets contain or both sets do not contain.
561+
if self.contains(i) == other.contains(i) {
562+
flag = false
563+
result.insert(integersIn: start..<i)
565564
}
566-
flag = false
567565
}
568-
// We never have to worry about having flag set to false after exiting this loop because the iterator will always return an even number of results; ranges come in pairs, and we always alternate flag
566+
// We never have to worry about having flag set to false after exiting this loop because the last boundary is guaranteed to be past the end of ranges in both index sets
569567
}
570568

571569
return result
@@ -591,10 +589,10 @@ public struct IndexSet : ReferenceConvertible, Equatable, BidirectionalCollectio
591589
start = i
592590
}
593591
} else {
594-
// If both sets contain then end a range.
595-
if self.contains(i) && other.contains(i) {
592+
// If both sets do not contain then end a range.
593+
if !self.contains(i) || !other.contains(i) {
596594
flag = false
597-
result.insert(integersIn: start..<(i + 1))
595+
result.insert(integersIn: start..<i)
598596
}
599597
}
600598
}
@@ -783,8 +781,8 @@ private struct IndexSetBoundaryIterator : IteratorProtocol {
783781
private var i2 : IndexSet.RangeView.Iterator
784782
private var i1Range : CountableRange<Element>?
785783
private var i2Range : CountableRange<Element>?
786-
private var i1UsedFirst : Bool
787-
private var i2UsedFirst : Bool
784+
private var i1UsedLower : Bool
785+
private var i2UsedLower : Bool
788786

789787
private init(_ is1 : IndexSet, _ is2 : IndexSet) {
790788
i1 = is1.rangeView.makeIterator()
@@ -793,9 +791,9 @@ private struct IndexSetBoundaryIterator : IteratorProtocol {
793791
i1Range = i1.next()
794792
i2Range = i2.next()
795793

796-
// A sort of cheap iterator on [i1Range.first, i1Range.last]
797-
i1UsedFirst = false
798-
i2UsedFirst = false
794+
// A sort of cheap iterator on [i1Range.lowerBound, i1Range.upperBound]
795+
i1UsedLower = false
796+
i2UsedLower = false
799797
}
800798

801799
private mutating func next() -> Element? {
@@ -805,29 +803,34 @@ private struct IndexSetBoundaryIterator : IteratorProtocol {
805803

806804
let nextIn1 : Element
807805
if let r = i1Range {
808-
nextIn1 = i1UsedFirst ? r.last! : r.first!
806+
nextIn1 = i1UsedLower ? r.upperBound : r.lowerBound
809807
} else {
810808
nextIn1 = Int.max
811809
}
812810

813811
let nextIn2 : Element
814812
if let r = i2Range {
815-
nextIn2 = i2UsedFirst ? r.last! : r.first!
813+
nextIn2 = i2UsedLower ? r.upperBound : r.lowerBound
816814
} else {
817815
nextIn2 = Int.max
818816
}
819817

820818
var result : Element
821819
if nextIn1 <= nextIn2 {
822-
// 1 has the next element, or they are the same. We need to iterate both the value from is1 and is2 in the == case.
820+
// 1 has the next element, or they are the same.
823821
result = nextIn1
824-
if i1UsedFirst { i1Range = i1.next() }
825-
i1UsedFirst = !i1UsedFirst
822+
if i1UsedLower { i1Range = i1.next() }
823+
// We need to iterate both the value from is1 and is2 in the == case.
824+
if result == nextIn2 {
825+
if i2UsedLower { i2Range = i2.next() }
826+
i2UsedLower = !i2UsedLower
827+
}
828+
i1UsedLower = !i1UsedLower
826829
} else {
827830
// 2 has the next element
828831
result = nextIn2
829-
if i2UsedFirst { i2Range = i2.next() }
830-
i2UsedFirst = !i2UsedFirst
832+
if i2UsedLower { i2Range = i2.next() }
833+
i2UsedLower = !i2UsedLower
831834
}
832835

833836
return result

test/1_stdlib/TestIndexSet.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,12 @@ class TestIndexSet : TestIndexSetSuper {
516516
expectEqual(expected, is1.symmetricDifference(is2))
517517
expectEqual(expected, is2.symmetricDifference(is1))
518518
}
519+
520+
do {
521+
is1 = IndexSet([0, 2])
522+
is2 = IndexSet([0, 1, 2])
523+
expectEqual(IndexSet(integer: 1), is1.symmetricDifference(is2))
524+
}
519525
}
520526

521527
func testIntersection() {
@@ -587,7 +593,12 @@ class TestIndexSet : TestIndexSetSuper {
587593
expectEqual(expected, is1.intersection(is2))
588594
expectEqual(expected, is2.intersection(is1))
589595
}
590-
596+
597+
do {
598+
is1 = IndexSet([0, 2])
599+
is2 = IndexSet([0, 1, 2])
600+
expectEqual(is1, is1.intersection(is2))
601+
}
591602
}
592603

593604
func testUnion() {

0 commit comments

Comments
 (0)