通过从另一个数组排序来对Swift数组进行排序

das*_*ist 14 arrays sorting swift

假设我有一个自定义类的数组[Player],每个数组都包含一个名为的字符串属性player.position

我也有一个任意的值数组positionOrders,如下所示:

let positionOrders = ["QB", "WR", "RB", "TE"]

我的目标是[Player]先将所有"QB" 排序,然后是"WR","RB",最后是"TE".

我正在做的当前方式循环遍历每个元素positionOrders,然后在内部循环遍历所有玩家以附加到新数组.但是,我无法想出一种更简单(更有效)的方法.非常感谢任何提示或指示.谢谢.

Ale*_*ica 30

对于小尺寸Collection,这是一种可行的方法:

public struct HardCodedOrdering<Element> where Element: Hashable {
    public enum UnspecifiedItemSortingPolicy {
        case first
        case last
        case assertAllItemsHaveDefinedSorting
    }

    private let ordering: [Element: Int]
    private let sortingPolicy: UnspecifiedItemSortingPolicy

    public init(
        ordering: Element...,
        sortUnspecifiedItems sortingPolicy: UnspecifiedItemSortingPolicy = .assertAllItemsHaveDefinedSorting
    ) {
        self.init(ordering: ordering, sortUnspecifiedItems: sortingPolicy)
    }

    public init<S: Sequence>(
        ordering: S,
        sortUnspecifiedItems sortingPolicy: UnspecifiedItemSortingPolicy = .assertAllItemsHaveDefinedSorting
    ) where S.Element == Element {

        self.ordering = Dictionary(uniqueKeysWithValues: zip(ordering, 1...))
        self.sortingPolicy = sortingPolicy
    }

    private func sortKey(for element: Element) -> Int {
        if let definedSortKey = self.ordering[element] { return definedSortKey }

        switch sortingPolicy {
            case .first:    return Int.min
            case .last:     return Int.max

            case .assertAllItemsHaveDefinedSorting:
                fatalError("Found an element that does not have a defined ordering: \(element)")
        }
    }

    public func contains(_ element: Element) -> Bool {
        return self.ordering.keys.contains(element)
    }

    // For use in sorting a collection of `T`s by the value's yielded by `keyDeriver`.
    // A throwing varient could be introduced, if necessary.
    public func areInIncreasingOrder<T>(by keyDeriver: @escaping (T) -> Element) -> (T, T) -> Bool {
        return { lhs, rhs in
            self.sortKey(for: keyDeriver(lhs)) < self.sortKey(for: keyDeriver(rhs))
        }   
    }

    // For use in sorting a collection of `Element`s
    public func areInIncreasingOrder(_ lhs: Element, rhs: Element) -> Bool {        
        return sortKey(for: lhs) < sortKey(for: rhs)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个更复杂的解决方案,对于更大尺寸的解决方案来说会更快 Array.firstIndex(of:)


let rankOrdering = HardCodedOrdering(ordering: "Private", "Lieutenant", "Captain", "Admiral") // ideally, construct this once, cache it and share it

let someRanks = [
    "Admiral", // Should be last (greatest)
    "Gallactic Overlord", // fake, should be removed
    "Private", // Should be first (least)
]
let realRanks = someRanks.lazy.filter(rankOrdering.contains)
let sortedRealRanks = realRanks.sorted(by: rankOrdering.areInIncreasingOrder) // works with mutating varient, `sort(by:)`, too.

print(sortedRealRanks) // => ["Private", "Admiral"]
Run Code Online (Sandbox Code Playgroud)

这两种解决方案都假设所有可能的玩家位置都根据positionOrders具有定义的顺序,因此它们O(array.count)用于简洁.如果不是这样,请告诉我.


小智 8

基于亚历山大的答案,我实施了一个扩展来做到这一点.

extension Array where Element == String {

func reordered() -> [String] {

    let defaultOrder = ["orange", "pear", "watermelon", "grapefruit", "apple", "lemon", "tomatoes"]

    return self.sorted { (a, b) -> Bool in
        if let first = defaultOrder.index(of: a), let second = defaultOrder.index(of: b) {
            return first < second
        }
        return false
    }
}

let arrayToSort = ["lemon", "watermelon", "tomatoes"]
let sortedArray = arrayToSort.reordered()
print(sortedArray) // ["watermelon", "lemon", "tomatoes"]
Run Code Online (Sandbox Code Playgroud)


Ale*_*tev 8

SwifterSwift有这个实现

/// SwifterSwift: Sort an array like another array based on a key path. If the other array doesn't contain a certain value, it will be sorted last.
    ///
    ///        [MyStruct(x: 3), MyStruct(x: 1), MyStruct(x: 2)].sorted(like: [1, 2, 3], keyPath: \.x)
    ///            -> [MyStruct(x: 1), MyStruct(x: 2), MyStruct(x: 3)]
    ///
    /// - Parameters:
    ///   - otherArray: array containing elements in the desired order.
    ///   - keyPath: keyPath indiciating the property that the array should be sorted by
    /// - Returns: sorted array.
    func sorted<T: Hashable>(like otherArray: [T], keyPath: KeyPath<Element, T>) -> [Element] {
        let dict = otherArray.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
        return sorted {
            guard let thisIndex = dict[$0[keyPath: keyPath]] else { return false }
            guard let otherIndex = dict[$1[keyPath: keyPath]] else { return true }
            return thisIndex < otherIndex
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 这是为什么不使用库的一个很好的例子。该函数完全内置于 Swift 中。 (2认同)

Fat*_*tie 8

这非常简单:

positionOrders.filter{player.contains($0)}
Run Code Online (Sandbox Code Playgroud)

这里的所有都是它的。

一个更明显的方法:

排序的方式就像这样:

    player.sort{
        .. your formula for sorting goes here
    }
Run Code Online (Sandbox Code Playgroud)

例如,如果您想按“银行余额”排序

    player.sort{
        $0.bankBalance < $1.bankBalance
    }
Run Code Online (Sandbox Code Playgroud)

要按“另一个数组”排序,只需

    player.sort{
        let i = positionOrders.firstIndex(of: $0) ?? 0
        let j = positionOrders.firstIndex(of: $1) ?? 0
        return i < j
    }
Run Code Online (Sandbox Code Playgroud)

这里的所有都是它的。

(请注意,如果您希望“丢失”的项目出现在末尾而不是开头,如果您的用例中可以想象丢失的项目,那么显然您只需使用而不是使用。Int.max0


(请注意,此页面上有关性能的评论偏离了很多很多数量级。在所述环境中,性能可能是令人难以置信的问题。如果令人难以置信地,令人难以置信的是,性能是一个问题,那么您只需执行显然,添加一行代码来散列索引。不过要小心,在几乎所有情况下,进行散列实际上!!性能较差!!再说一次,只是 TBC,性能不太可能成为问题;使用上面的代码)


小智 7

这是一个基于OuSS代码的通用Swift 4解决方案,要求数组元素是相等的。

extension Array where Element: Equatable {

    func reorder(by preferredOrder: [Element]) -> [Element] {

        return self.sorted { (a, b) -> Bool in
            guard let first = preferredOrder.index(of: a) else {
                return false
            }

            guard let second = preferredOrder.index(of: b) else {
                return true
            }

            return first < second
        }
    }
}

let currentPositions = ["RB", "AA", "BB", "CC", "WR", "TE"]
let preferredOrder = ["QB", "WR", "RB", "TE"]
let sorted = currentPositions.reorder(by: preferredOrder)
print(sorted) // ["WR", "RB", "TE", "AA", "BB", "CC"]
Run Code Online (Sandbox Code Playgroud)

  • 很好的解决方案。尽管您使用了相同类型的对象,但它可以适应在每个数组中使用不同类型的对象,例如,首选顺序仅表示要排序的更大对象中的一个字段。 (3认同)

ant*_*014 5

我将要做的:

  1. 创建一个以位置为键的字典,并以该位置的玩家数组为值。O(n),其中 n 是玩家数量。
  2. 循环遍历您positionOrders并获取每个键(位置)的值。

这是代码:

    let preSortPlayerList = [Player]() // Filled with your players.
    let positionOrders = ["QB", "WR", "RB", "TE"]
    let dict = preSortPlayerList.reduce([String : [Player]]()) {
        var map = $0
        if var tmp = map[$1.position] {
            tmp.append($1)
            map[$1.position] = tmp
        } else {
            map[$1.position] = [$1]
        }
        return map
    }

    let playersArray: [Player] = positionOrders.flatMap { dict[$0] ?? [Player]() }
    print("\(playersArray)")
Run Code Online (Sandbox Code Playgroud)