stdlib: add a hook for dynamic dispatch in CollectionType.find()

This hook allows Set.find() to be equally efficient in static and
generic contexts.

This time, with correct tests.

Swift SVN r27404
This commit is contained in:
Dmitri Hrybenko
2015-04-17 05:03:28 +00:00
parent e94b0f9b40
commit 02d254047b
5 changed files with 194 additions and 3 deletions

View File

@@ -77,6 +77,22 @@ extension _CollectionDefaultsType {
final public func _prext_underestimateCount() -> Int {
return numericCast(count(self))
}
/// Customization point for `SequenceType._prext_find()`.
///
/// Define this method if the collection can find an element in less than
/// O(N) by exploiting collection-specific knowledge.
///
/// Returns: `nil` if a linear search should be attempted instead,
/// `Optional(nil)` if the element was not found, or
/// `Optional(Optional(index))` if an element was found.
///
/// Complexity: O(N)
final public func _customFindEquatableElement(
element: Generator.Element
) -> Index?? {
return nil
}
}
/// A multi-pass *sequence* with addressable positions.
@@ -102,6 +118,18 @@ public protocol CollectionType
/// `position != endIndex`.
subscript(position: Index) -> Generator.Element {get}
/// Customization point for `SequenceType._prext_find()`.
///
/// Define this method if the collection can find an element in less than
/// O(N) by exploiting collection-specific knowledge.
///
/// Returns: `nil` if a linear search should be attempted instead,
/// `Optional(nil)` if the element was not found, or
/// `Optional(Optional(index))` if an element was found.
///
/// Complexity: O(N)
func _customFindEquatableElement(element: Generator.Element) -> Index??
// Do not use this operator directly; call `count(x)` instead
func ~> (_:Self, _:(_Count, ())) -> Index.Distance
}

View File

@@ -49,9 +49,11 @@ extension CollectionType where Self.${GElement} : Equatable {
///
/// Complexity: O(\ `self.count()`\ )
final public func _prext_find(element: ${GElement}) -> Index? {
// FIXME: dynamic dispatch for Set and Dictionary.
// FIXME: _prext_indices
for i in indices(self) {
if let result? = _customFindEquatableElement(element) {
return result
}
for i in self._prext_indices {
if self[i] == element {
return i
}

View File

@@ -640,6 +640,10 @@ public struct Set<T : Hashable> :
public func _customContainsEquatableElement(member: T) -> Bool? {
return contains(member)
}
public func _customFindEquatableElement(member: T) -> Index?? {
return Optional(indexOf(member))
}
}
/// Check for both subset and equality relationship between

View File

@@ -2292,6 +2292,132 @@ SequenceTypeAlgorithms.test("find/WhereElementIsEquatable/${dispatch}") {
% end
struct CollectionWithCustomFindMethod : CollectionType {
static var timesFindWasCalled: Int = 0
internal let _elements: [Int]
init(_ elements: [Int]) {
self._elements = elements
}
func generate() -> MinimalGenerator<MinimalEquatableValue> {
// Lie from our generate() method about sequence contents.
// Tests using this type should not call generate() anyway.
expectUnreachable()
return MinimalSequence<MinimalEquatableValue>([]).generate()
}
var startIndex: MinimalForwardIndex {
return MinimalForwardIndex(
position: 0, startIndex: 0, endIndex: _elements.endIndex)
}
var endIndex: MinimalForwardIndex {
return MinimalForwardIndex(
position: _elements.endIndex,
startIndex: 0,
endIndex: _elements.endIndex)
}
subscript(i: MinimalForwardIndex) -> MinimalEquatableValue {
return MinimalEquatableValue(_elements[i.position])
}
func _customFindEquatableElement(
element: MinimalEquatableValue
) -> MinimalForwardIndex?? {
++CollectionWithCustomFindMethod.timesFindWasCalled
for i in _elements._prext_indices {
if _elements[i] == element.value {
return MinimalForwardIndex(
position: i,
startIndex: 0,
endIndex: _elements.endIndex)
}
}
return Optional(nil)
}
}
func callStaticFind(
sequence: CollectionWithCustomFindMethod,
element: MinimalEquatableValue
) -> CollectionWithCustomFindMethod.Index? {
return sequence._prext_find(element)
}
% for dispatch in [ 'Static', 'Generic' ]:
SequenceTypeAlgorithms.test("find/WhereElementIsEquatable/CustomImplementation/${dispatch}") {
for test in findTests {
let s = CollectionWithCustomFindMethod(test.sequence)
CollectionWithCustomFindMethod.timesFindWasCalled = 0
expectEqual(
test.expected,
call${dispatch}Find(s, MinimalEquatableValue(test.element))
.map { $0.position },
stackTrace: test.loc.withCurrentLoc())
expectEqual(1, CollectionWithCustomFindMethod.timesFindWasCalled)
}
}
% end
// FIXME: underscores are a workaround for:
// <rdar://problem/20582358> Commenting out one line determines whether a
// completely different line type-checks
func callGenericFind_<
C : CollectionType where C.Generator.Element : Equatable
>(collection: C, element: C.Generator.Element) -> C.Index? {
return collection._prext_find(element)
}
func callStaticFind_(
set: Set<MinimalHashableValue>,
element: MinimalHashableValue
) -> Set<MinimalHashableValue>.Index? {
return set._prext_find(element)
}
% for dispatch in [ 'Static', 'Generic' ]:
// FIXME: implement the same optimization for Dictionary.
// FIXME: move to the file where other Set tests live.
SequenceTypeAlgorithms.test("Set<T>.find/WhereElementIsEquatable/CustomImplementation/${dispatch}") {
for test in findTests {
let s = Set<MinimalHashableValue>(
test.sequence.map { MinimalHashableValue($0) })
MinimalHashableValue.timesEqualEqualWasCalled = 0
MinimalHashableValue.timesHashValueWasCalled = 0
expectEqual(
test.expected
.map { _ in MinimalHashableValue(test.element) },
call${dispatch}Find_(s, MinimalHashableValue(test.element))
.map { s[$0] },
stackTrace: test.loc.withCurrentLoc())
if test.sequence.isEmpty {
expectEqual(
0, MinimalHashableValue.timesEqualEqualWasCalled,
stackTrace: test.loc.withCurrentLoc())
expectEqual(
0, MinimalHashableValue.timesHashValueWasCalled,
stackTrace: test.loc.withCurrentLoc())
} else {
expectNotEqual(
0, MinimalHashableValue.timesHashValueWasCalled,
stackTrace: test.loc.withCurrentLoc())
}
if test.expected != nil {
expectNotEqual(
0, MinimalHashableValue.timesEqualEqualWasCalled,
stackTrace: test.loc.withCurrentLoc())
}
}
}
% end
SequenceTypeAlgorithms.test("find/Predicate") {
for test in findTests {
let s = MinimalForwardCollection<MinimalEquatableValue>(

View File

@@ -3226,6 +3226,14 @@ SetTestSuite.test("first") {
expectEmpty(emptySet.first)
}
SetTestSuite.test("isEmpty") {
let s1 = Set([1010, 2020, 3030])
expectFalse(s1.isEmpty)
let emptySet = Set<Int>()
expectTrue(emptySet.isEmpty)
}
SetTestSuite.test("count") {
let s1 = Set([1010, 2020, 3030])
var s2 = Set([4040, 5050, 6060])
@@ -3240,6 +3248,29 @@ SetTestSuite.test("contains") {
expectFalse(Set<Int>().contains(1010))
}
SetTestSuite.test("_customContainsEquatableElement") {
let s1 = Set([1010, 2020, 3030, 4040, 5050, 6060])
expectTrue(s1._customContainsEquatableElement(1010)!)
expectFalse(s1._customContainsEquatableElement(999)!)
expectFalse(Set<Int>()._customContainsEquatableElement(1010)!)
}
SetTestSuite.test("indexOf") {
let s1 = Set([1010, 2020, 3030, 4040, 5050, 6060])
let foundIndex1 = s1.indexOf(1010)!
expectEqual(1010, s1[foundIndex1])
expectEmpty(s1.indexOf(999))
}
SetTestSuite.test("_customFindEquatableElement") {
let s1 = Set([1010, 2020, 3030, 4040, 5050, 6060])
let foundIndex1 = s1._customFindEquatableElement(1010)!!
expectEqual(1010, s1[foundIndex1])
expectEmpty(s1._customFindEquatableElement(999)!)
}
SetTestSuite.test("commutative") {
let s1 = Set([1010, 2020, 3030])
let s2 = Set([2020, 3030])