如何在Swift中将正确位置的元素插入到排序数组中?

时间:2014-10-31 15:37:36

标签: arrays sorting swift

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)

5 个答案:

答案 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)

这里有二元搜索树。

在有序数组上,取中间的元素,查看该位置的对象是否大于新对象。这样你就可以通过一次比较忘记一半的数组元素。

用剩下的一半重复这一步。同样,通过单一比较,您可以忘记剩余对象的一半。您的目标元素数量现在只是数组大小的四分之一,只有两次比较。

重复此操作,直到找到插入新元素的正确位置。

这是a good article on binary search trees with swift

答案 4 :(得分:1)

根据WWDC 2018 Session 406: Swift Generics (Expanded),通过切片收集对象,可以以更有效,更通用的方式执行二进制搜索。

Slice有两个明显的优点:

  1. 切片始终是原始对象的子集,而不会分配额外的内存。
  2. 切片的所有索引均引用原始对象。
    例如,如果您对5个对象的数组let slice = array[2..<4]进行切片,则slice.startIndex2而不是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
    }
}