Skip to content

Commit a415ade

Browse files
committed
Merge pull request #854 from an0/master
Add O(1) `contains()` implementation for Range with Comparable Element
2 parents 3df3b17 + 1ae71d4 commit a415ade

File tree

3 files changed

+81
-3
lines changed

3 files changed

+81
-3
lines changed

stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2607,6 +2607,48 @@ public func < (
26072607
return MinimalComparableValue.lessImpl.value(lhs.value, rhs.value)
26082608
}
26092609

2610+
/// A type that conforms only to `Comparable` and `ForwardIndexType`.
2611+
///
2612+
/// This type can be used to check that generic functions don't rely on any
2613+
/// other conformances.
2614+
public struct MinimalComparableIndexValue : Comparable, ForwardIndexType {
2615+
public static var timesEqualEqualWasCalled = 0
2616+
public static var timesLessWasCalled = 0
2617+
2618+
public func successor() -> MinimalComparableIndexValue {
2619+
return MinimalComparableIndexValue(value.successor())
2620+
}
2621+
2622+
public var value: Int
2623+
public var identity: Int
2624+
2625+
public init(_ value: Int) {
2626+
self.value = value
2627+
self.identity = 0
2628+
}
2629+
2630+
public init(_ value: Int, identity: Int) {
2631+
self.value = value
2632+
self.identity = identity
2633+
}
2634+
}
2635+
2636+
public func == (
2637+
lhs: MinimalComparableIndexValue,
2638+
rhs: MinimalComparableIndexValue
2639+
) -> Bool {
2640+
MinimalComparableIndexValue.timesEqualEqualWasCalled += 1
2641+
return lhs.value == rhs.value
2642+
}
2643+
2644+
public func < (
2645+
lhs: MinimalComparableIndexValue,
2646+
rhs: MinimalComparableIndexValue
2647+
) -> Bool {
2648+
MinimalComparableIndexValue.timesLessWasCalled += 1
2649+
return lhs.value < rhs.value
2650+
}
2651+
26102652
// ${'Local Variables'}:
26112653
// eval: (read-only-mode 1)
26122654
// End:

stdlib/public/core/Range.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,29 @@ extension Range : CustomReflectable {
153153
}
154154
}
155155

156+
/// O(1) implementation of `contains()` for ranges of comparable elements.
157+
extension Range where Element: Comparable {
158+
@warn_unused_result
159+
func _customContainsEquatableElement(element: Generator.Element) -> Bool? {
160+
return element >= self.startIndex && element < self.endIndex
161+
}
162+
163+
// FIXME: copied from SequenceAlgorithms as a workaround for https://bugs.swift.org/browse/SR-435
164+
@warn_unused_result
165+
public func contains(element: Generator.Element) -> Bool {
166+
if let result = _customContainsEquatableElement(element) {
167+
return result
168+
}
169+
170+
for e in self {
171+
if e == element {
172+
return true
173+
}
174+
}
175+
return false
176+
}
177+
}
178+
156179
@warn_unused_result
157180
public func == <Element>(lhs: Range<Element>, rhs: Range<Element>) -> Bool {
158181
return lhs.startIndex == rhs.startIndex &&
@@ -207,8 +230,6 @@ public func ... <Pos : ForwardIndexType where Pos : Comparable> (
207230
public func ~= <I : ForwardIndexType where I : Comparable> (
208231
pattern: Range<I>, value: I
209232
) -> Bool {
210-
// Intervals can check for containment in O(1).
211-
return
212-
HalfOpenInterval(pattern.startIndex, pattern.endIndex).contains(value)
233+
return pattern.contains(value)
213234
}
214235

validation-test/stdlib/SequenceType.swift.gyb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,21 @@ SequenceTypeTests.test("Set<T>.contains/CustomImplementation/${dispatch}") {
608608

609609
% end
610610

611+
SequenceTypeTests.test("Range<Element>.contains/WhereElementIsComparable/dispatch") {
612+
MinimalComparableIndexValue.timesLessWasCalled = 0
613+
let start = 0
614+
let end = 10
615+
let range = Range(start: MinimalComparableIndexValue(start), end: MinimalComparableIndexValue(end))
616+
let count = 20
617+
for test in 0..<count {
618+
expectEqual(
619+
test >= start && test < end,
620+
range.contains(MinimalComparableIndexValue(test)))
621+
}
622+
expectEqual(
623+
count * 2, MinimalComparableIndexValue.timesLessWasCalled)
624+
}
625+
611626
SequenceTypeTests.test("contains/Predicate") {
612627
for test in findTests {
613628
let s = MinimalSequence<OpaqueValue<Int>>(

0 commit comments

Comments
 (0)