Instead of incorporating element(s) dereference into the method, I think it may be better to segregate that to a separate step, and make the check pure:
extension Collection {
/**
Returns whether the count is exactly the given amount.
Testing goes through the minimal amount of index iteration when possible.
- Parameter n: The trial element-count to check against.
- Returns: `n == .count`.
- Complexity: O(1) for collections conforming to `RandomAccessCollection`, O(m) otherwise, where *m* is the smaller of `.count` and `n`.
*/
func isCount(exactly n: Int) -> Bool {
guard n >= 0 else { return false }
return index(startIndex, offsetBy: n, limitedBy: endIndex) == endIndex
}
}
var aa = Array<Int>()
aa.isCount(exactly: 0) // true
aa.isCount(exactly: -1) // false
aa.isCount(exactly: +1) // false
aa = [1, 2, 3]
aa.isCount(exactly: -1) // false
aa.isCount(exactly: 0) // false
aa.isCount(exactly: 1) // false
aa.isCount(exactly: 2) // false
aa.isCount(exactly: 3) // true
aa.isCount(exactly: 4) // true
aa.isCount(exactly: 5) // true
As far as I know, a 1-element test cannot be optimized to be better than a general n-element version.
A filtering version has to stop at the end of the collection or the (n + 1)th find, whichever comes first. This is nastier since we don't know where in the collection the determining index is; the performance could be O(m).
extension Sequence {
/**
Returns whether the count of elements matching a given predicate is exactly the given amount.
Testing goes through the minimal amount of iteration when possible.
- Precondition: This sequence should be finite.
- Parameter n: The trial element-count, post-filtering, to check against.
- Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the processed count.
- Returns: The same as `.filter(isIncluded).isCount(exactly: n)`.
- Throws: Whatever `isIncluded` may throw on calls.
- Complexity: At most O(m), where *m* is the number of elements in this sequence.
*/
func isFilteredCount(exactly n: Int, _ isIncluded: (Element) throws -> Bool) rethrows -> Bool {
guard n >= 0 else { return false }
var iterator = makeIterator(), remainingMatches = n
while let e = iterator.next() {
if try isIncluded(e) {
remainingMatches -= 1
if remainingMatches < 0 {
return false
}
}
}
return remainingMatches == 0
}
}
let aa = [1, 2, 3, 4, 5, 6]
aa.isFilteredCount(exactly: -1, { $0 % 2 == 0 }) // false
print(aa.isFilteredCount(exactly: 0, { $0 % 2 == 0 })) // false
print(aa.isFilteredCount(exactly: 1, { $0 % 2 == 0 })) // false
print(aa.isFilteredCount(exactly: 2, { $0 % 2 == 0 })) // false
print(aa.isFilteredCount(exactly: 3, { $0 % 2 == 0 })) // true
print(aa.isFilteredCount(exactly: 4, { $0 % 2 == 0 })) // false
print(aa.isFilteredCount(exactly: 5, { $0 % 2 == 0 })) // false
print(aa.isFilteredCount(exactly: 6, { $0 % 2 == 0 })) // false
print(aa.isFilteredCount(exactly: 7, { $0 % 2 == 0 })) // false
print()
aa.isFilteredCount(exactly: -1, { $0 > 10 }) // false
print(aa.isFilteredCount(exactly: 0, { $0 > 10 })) // true
print(aa.isFilteredCount(exactly: 1, { $0 > 10 })) // false
print(aa.isFilteredCount(exactly: 2, { $0 > 10 })) // false
print(aa.isFilteredCount(exactly: 3, { $0 > 10 })) // false
print(aa.isFilteredCount(exactly: 4, { $0 > 10 })) // false
print(aa.isFilteredCount(exactly: 5, { $0 > 10 })) // false
print(aa.isFilteredCount(exactly: 6, { $0 > 10 })) // false
print(aa.isFilteredCount(exactly: 7, { $0 > 10 })) // false
and I included an Equatable
variant:
extension Sequence where Element: Equatable {
/**
Returns whether the count of elements equaling a value is exactly the given amount.
Testing goes through the minimal amount of iteration when possible.
- Precondition: This sequence should be finite.
- Parameter n: The trial element-count, post-filtering, to check against.
- Parameter x: The element value to check against.
- Returns: The same as `.filter { $0 == x }.isCount(exactly: n)`.
- Complexity: At most O(m), where *m* is the number of elements in this sequence.
*/
func isMatchedCount(exactly n: Int, ofValue x: Element) -> Bool {
return isFilteredCount(exactly: n, { $0 == x })
}
}
print()
print(aa.isMatchedCount(exactly: 1, ofValue: 2))
print(aa.isMatchedCount(exactly: 0, ofValue: 2))
print(aa.isMatchedCount(exactly: -1, ofValue: 2))
print(aa.isMatchedCount(exactly: 1, ofValue: 5))
print(aa.isMatchedCount(exactly: 0, ofValue: 5))
print(aa.isMatchedCount(exactly: 2, ofValue: 5))
print(aa.isMatchedCount(exactly: 1, ofValue: 8))
print(aa.isMatchedCount(exactly: 0, ofValue: 8))
print(aa.isMatchedCount(exactly: 2, ofValue: 8))