我一直在使用sort()函数,但它会打乱相对顺序。
这是我的代码样子。
recipes.sort { $0.skill.value <= $1.skill.value }
Swift API指出:
排序算法不稳定。非稳定排序可能会改变相等元素的相对顺序。
我该如何更改以使相对顺序保持不变?
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 }
}
}
稳定排序需要保持原始顺序。因此,我们为每个元素赋予一个除其值之外的顺序权重,即索引,然后原始的排序方法就可以正常工作,因为永远不会出现两个相同的元素。
areInIncreasingOrder(one.element, another.element)
为false时才使用稳定的offset
顺序。必须在仅当元素相对于排序areInIncreasingOrder
“相等”时,即!areInIncreasingOrder(one.element, another.element) && !areInIncreasingOrder(another.element, one.element)
时,才需要使用稳定的偏移量。如果我漏掉了什么并且没有错误,请谅解 - 谢谢! - Benjohn我很欣赏leavez的答案的优雅。我将其改编为与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 }
}
}
!areInIncreasingOrder
时会回退到offset
,但它也应该反转参数来检查是否稳定使用offset
,只有当元素相对于areInIncreasingOrder
“相等”时才使用。 - Benjohnlet 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
}
})
sort()
使用稳定的实现,很快它将正式保证是稳定的。...
On the other hand, the actual implementation calls
/// 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可能会使用不稳定的算法)。
我很欣赏Tom的回答的优雅。回想起我的Perl时代,我重新编写了它,使用了ComparisonResult
和太空船(<=>
)运算符:
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
}
}
我使用这个包装器
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]
}
}
<
。 - Justin Meiners