【问题标题】:Swift returning Array from ArraySlicesSwift 从 ArraySlices 返回 Array
【发布时间】:2021-10-17 23:28:28
【问题描述】:

我正在使用一个排序函数,该函数接受一个已经按降序排序的 Int 数组,并将一个新的 Int 放置在正确的位置。 (即,如果我的排序数组是 [10, 7, 2] 并且新的 int 是 5,则该函数将返回 [10, 7, 5, 2])。执行此操作的函数一旦找到新 Int 的正确位置,就会将原始数组切片为新 Int 位置之前和之后的项目,然后将切片与新 Int 组合。

我遇到的问题是这不会给我一个数组,而是一个数组切片。

代码:

func addToSorted(sorted: [Int], new: Int) ->  [Int] {
    if sorted.count == 0 {
        return [new]
    } else {
        for index in 0..<sorted.count {
            let item = sorted[index]
            if new > item {
                return sorted[..<index] + [new] + sorted[index...]
            }
        }
    }
}

let result = addToSorted(sorted: [10, 7, 2], new: 5)
print(result) // expected [10, 7, 5, 2]

【问题讨论】:

    标签: arrays swift sorting slice


    【解决方案1】:

    这是一个更通用(和高效)的替代方案,使用二分搜索

    extension RandomAccessCollection where Element : Comparable {
        func descendingInsertionIndex(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.endIndex
        }
    }
    

    并使用它

    var array = [10, 7, 5, 2]
    let index = array.descendingInsertionIndex(of: 4)
    array.insert(4, at: index)
    print(array) // [10, 7, 5, 4, 2]
    

    对于升序,将if value &gt; slice[middle] 替换为if value &lt; slice[middle],将return slice.endIndex 替换为return slice.startIndex

    【讨论】:

    • 这正是我的想法,太棒了!使用迭代而不是递归是最重要的。
    • 我对使用二分搜索犹豫不决,原因有两个:1) 我将使用的数组将具有较小的输入大小(最多考虑 10...20 个元素)和 2)我可以预期新的 Int 比排序数组中的平均元素大,所以我很少需要遍历整个排序数组来放置一个新的 Int。
    • 除了平均而言使用 5 个元素的二进制搜索已经更快这一事实之外,我的建议不会消耗任何额外的内存,因为切片不会创建任何新对象。
    • @JohnSorensen 您可以向descendingInsertionIndex(of:) 添加一条“快速路径”,该路径使用线性搜索来搜索低于某个阈值大小(通过分析确定)的足够小的数组。您还可以添加一个与第一个元素进行比较的检查,以便“始终预先设置”用例变得更快。尽管对于大型数据集,最好将其翻转过来,以便更频繁地附加到末尾。对于大型数据集,预置在数组上很慢。
    【解决方案2】:

    如果你使用Swift Algorithms,这个插入是单行的:

    var arr = [10, 7, 2]
    arr.insert(5, at: arr.partitioningIndex {$0 < 5})
    print (arr) // [10, 7, 5, 2]
    

    这是非常有效的 - O(log n) - 因为您的数组已经分区(排序),因此它使用二进制搜索。

    【讨论】:

      【解决方案3】:

      您必须将切片提升为数组:

      return Array(sorted[..<index]) + [new] + Array(sorted[index...])
      

      其他几点:

      1. 您应该养成使用sorted.isEmpty 而不是sorted.count == 0 的习惯,对于一些不存储其计数的集合(例如惰性集合甚至字符串(IIRC))来说,它要快得多。

      2. 更好的方法是只使用Array.insert(_:at:):

        var sorted = sorted // Make a local mutable copy
        sorted.insert(new, at: index)
        
      3. 顺便说一句,在您的 for 循环之后,您需要在数组末尾插入(这也消除了检查空大小写的需要):

        return sorted + [new]
        

        由于即使sorted 为空也有效,因此您可以删除该特殊情况。

      4. 由于您知道您的数据结构已经排序,您可以使用二分查找而不是线性查找来更快地找到插入索引。

      【讨论】:

      • @Rob 哈哈确实!我试图只保留它的格式,并在最后添加调用站点,但它偷偷溜进去了。修复!
      猜你喜欢
      • 2021-12-13
      • 2018-08-16
      • 2015-04-16
      • 2018-01-22
      • 1970-01-01
      • 2018-09-17
      • 2012-03-20
      • 2013-02-17
      相关资源
      最近更新 更多