NSArray
有- (NSUInteger)indexOfObject:(id)obj inSortedRange:(NSRange)r options:(NSBinarySearchingOptions)opts usingComparator:(NSComparator)cmp
来确定排序数组中新对象的插入位置。
在纯Swift中执行此操作的最佳和高性能方法是什么?
有些事情:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
myArray.append("c")
myArray.sort { $0 < $1 }
// myArray is now [a, b, c, d, e]
我想找出正确的位置并插入元素,而不是追加新元素然后对数组进行排序:
let index = [... how to calculate this index ??? ...]
myArray.insert("c", atIndex: index)
答案 0 :(得分:37)
这是Swift中使用二进制搜索的可能实现(来自 http://rosettacode.org/wiki/Binary_search#Swift略有修改):
extension Array {
func insertionIndexOf(elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int {
var lo = 0
var hi = self.count - 1
while lo <= hi {
let mid = (lo + hi)/2
if isOrderedBefore(self[mid], elem) {
lo = mid + 1
} else if isOrderedBefore(elem, self[mid]) {
hi = mid - 1
} else {
return mid // found at position mid
}
}
return lo // not found, would be inserted at position lo
}
}
与indexOfObject:inSortedRange:options:usingComparator:
一样,假设为
数组按比较器排序。
如果元素已存在于元素中,则返回元素的(任意)索引
数组,或保留顺序时可插入的索引。这个
对应于NSBinarySearchingInsertionIndex
方法的NSArray
。
用法:
let newElement = "c"
let index = myArray.insertionIndexOf(newElement) { $0 < $1 } // Or: myArray.indexOf(c, <)
myArray.insert(newElement, atIndex: index)
答案 1 :(得分:15)
在swift 3中,您可以使用index(where:)
:
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
if let index = myArray.index(where: { $0 > newElement }) {
myArray.insert(newElement, at: index)
}
请注意,在这种情况下,您需要反转闭包内的条件(即>
而不是<
以增加数组中的元素),因为您感兴趣的索引是第一个元素与谓词不匹配。此外,如果新插入的元素将成为数组中的最后一个(nil
,则此方法将返回newElement = "z"
。
为方便起见,这可以包装到一个单独的函数中来处理所有这些问题:
extension Collection {
func insertionIndex(of element: Self.Iterator.Element,
using areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> Index {
return index(where: { !areInIncreasingOrder($0, element) }) ?? endIndex
}
}
用法:
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
let index = myArray.insertionIndex(of: newElement, using: <)
myArray.insert(newElement, at: index)
答案 2 :(得分:6)
如果您知道您的数组已排序,则可以使用此方法 - 它将适用于任何类型的数组。它每次都会遍历整个阵列,所以不要在大阵列中使用它 - 如果你有更大的需求,可以选择其他数据类型!
func insertSorted<T: Comparable>(inout seq: [T], newItem item: T) {
let index = seq.reduce(0) { $1 < item ? $0 + 1 : $0 }
seq.insert(item, atIndex: index)
}
var arr = [2, 4, 6, 8]
insertSorted(&arr, newItem: 5)
insertSorted(&arr, newItem: 3)
insertSorted(&arr, newItem: -3)
insertSorted(&arr, newItem: 11)
// [-3, 2, 3, 4, 5, 6, 8, 11]
答案 3 :(得分:2)
这里有二元搜索树。
在有序数组上,取中间的元素,查看该位置的对象是否大于新对象。这样你就可以通过一次比较忘记一半的数组元素。
用剩下的一半重复这一步。同样,通过单一比较,您可以忘记剩余对象的一半。您的目标元素数量现在只是数组大小的四分之一,只有两次比较。
重复此操作,直到找到插入新元素的正确位置。
答案 4 :(得分:1)
根据WWDC 2018 Session 406: Swift Generics (Expanded),通过切片收集对象,可以以更有效,更通用的方式执行二进制搜索。
Slice
有两个明显的优点:
let slice = array[2..<4]
进行切片,则slice.startIndex
是2
而不是0
。RandomAccessCollection是一种协议(从BidirectionalCollection继承),各种结构/类都符合
extension RandomAccessCollection where Element : Comparable {
func insertionIndex(of value: Element) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex
}
}
您可以扩展该函数以检查谓词闭包而不是单个值
extension RandomAccessCollection { // the predicate version is not required to conform to Comparable
func insertionIndex(for predicate: (Element) -> Bool) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if predicate(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}