【问题标题】:How to stable sort an array in swift?如何快速稳定地对数组进行排序?
【发布时间】:2017-04-02 02:03:35
【问题描述】:

我一直在使用 sort() 函数,但它混淆了相对顺序。

这就是我的代码的样子。

recipes.sort { $0.skill.value <= $1.skill.value }

Swift API 说:

排序算法不稳定。不稳定的排序可能会改变 比较相等的元素的相对顺序。

如何更改此设置以使相对顺序与以前保持一致?

【问题讨论】:

标签: arrays swift sorting


【解决方案1】:

下面的实现就像标准库中的sorted方法一样工作,没有额外的限制。

extension RandomAccessCollection {

    /// return a sorted collection
    /// this use a stable sort algorithm
    ///
    /// - Parameter areInIncreasingOrder: return nil when two element are equal
    /// - Returns: the sorted collection
    public func stableSorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element] {

        let sorted = try enumerated().sorted { (one, another) -> Bool in
            if try areInIncreasingOrder(one.element, another.element) {
                return true
            } else {
                return one.offset < another.offset
            }
        }
        return sorted.map { $0.element }
    }
}

一个稳定的排序需要保持原来的顺序。所以我们给每个元素一个顺序的权重,除了它的值,索引,然后原来的排序方法就可以工作了,因为永远不会有 2 个相等的元素。

【讨论】:

  • @kelin 您的两次编辑实际上不起作用。请撤消或修复它们!谢谢。
  • 警告 这里的代码似乎有一个错误(在Tom's answer 中更正了,只要areInIncreasingOrder(one.element, another.element) 为假,就会使用稳定的offset 顺序。它是有必要诉诸稳定偏移当且仅当 !areInIncreasingOrder(one.element, another.element) &amp;&amp; !areInIncreasingOrder(another.element, one.element) - 即,当元素相对于排序areInIncreasingOrder“相等”时。如果我遗漏了什么并且有没有错 - 谢谢!
【解决方案2】:

我很欣赏leavez's answer 的优雅。我对其进行了调整,使其具有与Sequence.sorted(by:) 相同的签名:

extension Sequence {
  func stableSorted(
    by areInIncreasingOrder: (Element, Element) throws -> Bool)
    rethrows -> [Element]
  {
    return try enumerated()
      .sorted { a, b -> Bool in
        try areInIncreasingOrder(a.element, b.element) ||
          (a.offset < b.offset && !areInIncreasingOrder(b.element, a.element))
      }
      .map { $0.element }
  }
}

【讨论】:

  • 我认为@leavez 答案存在错误,您的答案已更正。当!areInIncreasingOrder 时,他们的代码恢复为offset,但它还应该使用反转的参数进行检查,以便仅当元素相对于areInIncreasingOrder 为“相等”时才使用稳定的offset
【解决方案3】:
let sortedArray = (recipes as NSArray).sortedArray(options: .stable, usingComparator: { (lhs, rhs) -> ComparisonResult in
    let lhs = (lhs as! Recipe)
    let rhs = (rhs as! Recipe)
    if lhs.skill.value == rhs.skill.value {
        return ComparisonResult.orderedSame
    } else if lhs.skill.value < rhs.skill.value {
        return ComparisonResult.orderedAscending
    } else {
        return ComparisonResult.orderedDescending
    }
})

取自这里:https://medium.com/@cocotutch/a-swift-sorting-problem-e0ebfc4e46d4

【讨论】:

  • 我们不得不为此恢复到Objective-C,这真是太糟糕了。为什么他们会选择不稳定的排序作为默认值?难道他们真的没有预见到这样的问题吗?说我疯了,但我会在任何一天选择稳定性而不是性能,至少作为默认选项,更不用说只有选项了。
  • 他们只是假设开发人员知道他们在做什么。应该是一个合理的假设……“稳定性”在这里是一个数学术语,不要与其他含义混淆,安全性在这里不适用。
【解决方案4】:

在 Swift 5 中 sort() 使用稳定的实现,很快它将成为官方保证稳定的。

来自Swift forums

...
另一方面,实际的实现调用

/// Sorts the elements of this buffer according to `areInIncreasingOrder`,
/// using a stable, adaptive merge sort.
///
/// The adaptive algorithm used is Timsort, modified to perform a straight
/// merge of the elements using a temporary buffer.
@inlinable
public mutating func _stableSortImpl(
    by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows { ... }

如果我记得,sort() 目前是稳定的,但还不能保证 是稳定的(意思是,它稳定的事实目前是 实现细节,未来版本的 Swift 可以发布 不稳定的算法)。

【讨论】:

  • 我没有看到任何表明它可以保证任何一天都稳定的信息?事实上,你的引用与“未来版本的 Swift 可能会发布不稳定的算法”正好相反。
  • @numist,阅读论坛链接。
【解决方案5】:

我使用这个包装器

extension Array where Element: Comparable, Element: AnyObject {
    public func stableSorted() -> [Element] {
        let array = self as NSArray
        let result = array.sortedArray(options: .stable) { (left, right) -> ComparisonResult in
            let left = left as! Element
            let right = right as! Element
            if left < right {
                return ComparisonResult.orderedAscending
            }
            if left > right {
                return ComparisonResult.orderedDescending
            }
            return ComparisonResult.orderedSame
        }
        return result as! [Element]
    }
    public func stableReversed() -> [Element] {
        let array = self as NSArray
        let result = array.sortedArray(options: .stable) { (left, right) -> ComparisonResult in
            let left = left as! Element
            let right = right as! Element
            if left > right {
                return ComparisonResult.orderedAscending
            }
            if left < right {
                return ComparisonResult.orderedDescending
            }
            return ComparisonResult.orderedSame
        }
        return result as! [Element]
    }
}

【讨论】:

    【解决方案6】:

    我很欣赏Tom's answer 的优雅。回到我的 Perl 时代,我对其进行了重新设计以使用 ComparisonResult 和 spaceship (&lt;=&gt;) 运算符:

    extension Sequence {
        func sorted(with comparator: (Element, Element) throws -> ComparisonResult) rethrows -> [Element]
        {
            return try enumerated()
                .sorted { (try comparator($0.element, $1.element) || $0.offset <=> $1.offset) == .orderedAscending }
                .map { $0.element }
        }
    }
    
    extension Comparable {
        static func <=> (lhs: Self, rhs: Self) -> ComparisonResult {
            if lhs < rhs { return .orderedAscending }
            if lhs > rhs { return .orderedDescending }
            return .orderedSame
        }
    }
    
    extension ComparisonResult {
        static func || (lhs: Self, rhs: @autoclosure () -> Self) -> ComparisonResult {
            if lhs == .orderedSame {
                return rhs()
            }
            return lhs
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-02
      • 1970-01-01
      • 2016-09-25
      • 1970-01-01
      • 2014-10-18
      • 1970-01-01
      • 2011-06-02
      • 1970-01-01
      相关资源
      最近更新 更多