在@vadian's 和@Martin R's 答案的基础上,我注意到一些细微的差异,主要是插入索引与集合中等效元素的索引不匹配,或者它与 first 不匹配 em> 等价元素序列的索引。
例如:
- 如果您想在
[4, 5, 6] 中查找5 的插入索引,则会返回索引2,如果您只想简单地搜索该值,这可能会出现问题。
- 在
[5, 5, 5] 中,再次搜索5 会返回索引1,这不是第一个插入索引。
这与 NSArray 的实现行为及其各种选项不匹配,因此这里是另一个尝试考虑这一点的解决方案:
extension RandomAccessCollection {
/// Get the index of or an insertion index for a new element in
/// a sorted collection in ascending order.
/// - Parameter value: The element to insert into the array.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
@inlinable
public func sortedInsertionIndex(
of element: Element
) -> Index where Element: Comparable {
sortedInsertionIndex(of: element, by: <)
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
/// - Parameters:
/// - value: The element to insert into the array.
/// - areInIncreasingOrder:
/// A closure that determines if the first element should
/// come before the second element. For instance: `<`.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
@inlinable
public func sortedInsertionIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index {
try sortedInsertionIndex { try areInIncreasingOrder($0, element) }
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
///
/// This variation is useful when comparing an element that
/// is of a different type than those already in the array.
/// - Parameter isOrderedAfter:
/// Return `true` if the new element should come after the one
/// provided in the closure, or `false` otherwise. For instance
/// `{ $0 < newElement }` to sort elements in increasing order.
/// - Returns: The index suitable for inserting the new element into
/// the array, or the first index of an existing element.
@inlinable
public func sortedInsertionIndex(
where isOrderedAfter: (Element) throws -> Bool
) rethrows -> Index {
var slice: SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count/2)
if try isOrderedAfter(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
由于有时您不关心插入索引,而是关心与给定元素匹配的第一个或最后一个索引,因此以下是满足这些要求的上述变体:
extension RandomAccessCollection {
@inlinable
public func sortedFirstIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedFirstIndex(of: element, by: <)
}
@inlinable
public func sortedFirstIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element, by: areInIncreasingOrder)
guard insertionIndex < endIndex, self[insertionIndex] == element else { return nil }
return insertionIndex
}
@inlinable
public func sortedLastIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedLastIndex(of: element, by: <)
}
@inlinable
public func sortedLastIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element) { try areInIncreasingOrder($1, $0) }
let finalIndex = index(insertionIndex, offsetBy: -1)
guard finalIndex >= startIndex, self[finalIndex] == element else { return nil }
return finalIndex
}
}