Сортировка массива Swift путем упорядочивания из другого массива

Скажем, у меня есть массив пользовательского класса [Player], каждый из которых содержит строковое свойство, называемое player.position

У меня также есть произвольный массив значений, называемый positionOrders, например:

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

Где моя цель - сортировать [Player] чтобы сначала иметь все "QB", затем "WR", "RB" и, наконец, "TE".

В текущем способе я делаю циклы через каждый элемент в positionOrders, а затем внутри, который проходит через все игроки, чтобы добавить к новому массиву. Однако я не мог представить более простой (и более эффективный) способ сделать это. Любые советы или указатели очень ценятся. Благодарю.

Ответ 1

Изменить: Мой оригинальный подход был дерьмо. Этот пост получил широкую поддержку, поэтому пришло время уделить ему больше внимания и улучшить его.


По сути, проблема проста. У нас есть два элемента, и у нас есть массив (или любой упорядоченный Collection), относительный порядок которого определяет порядок их сортировки. Для каждого элемента мы находим его позицию в упорядоченной коллекции и сравниваем два индекса, чтобы определить, какой из них "больше".

Однако, если мы наивно выполняем линейный поиск (например, Array.firstIndex(of:)), мы получим очень плохую производительность (O(array.count)), особенно если фиксированный порядок очень велик. Чтобы исправить это, мы можем построить Dictionary, который отображает элементы в их индексы. Словарь обеспечивает быстрый поиск O(1), который идеально подходит для работы.

Это именно то, что делает HardCodedOrdering. Он предварительно вычисляет словарь элементов по их порядку и предоставляет интерфейс для сравнения 2 элементов. Более того, его можно настроить так, чтобы он по-разному реагировал на встречающиеся элементы с неизвестным порядком. Он может поставить их первым перед всем остальным, последним после всего остального или полностью завершиться (поведение по умолчанию).

HardCodedOrdering

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 by the value 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)
    }
}

Пример использования:


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"]

Ответ 2

Основываясь на ответе Александра, я внедрил расширение для этого.

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"]

Ответ 3

Это общее решение Swift 4, основанное на коде OuSS, и требует, чтобы элементы массива были Equatable.

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"]

Ответ 4

Что я буду делать:

  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)")

Ответ 5

Для быстрого 4

Очень простой способ и прилежный (не стесняйтесь предлагать и корректировать меня)

func reOrder(array : [String] , order : [String]) -> [String]{ 

    //common elments in order
    // order.filter{array.contains($0)}

    // the rest of the array that the order doesnt specify
    // array.filter{!order.contains($0)}

 return order.filter{array.contains($0)} + array.filter{!order.contains($0)}
}


let list =     ["A", "Z", "B", "H", "C", "T", "D", "E"] 
let newOrder = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]




print("The ordered list is ",reOrder(array: list, order: newOrder))


 // The ordered list is  ["A", "B", "C", "D", "E", "H", "Z", "T"]

было бы хорошо, если бы кто-то мог сделать это расширением для Generic типа, я нехорошо с этим

Ответ 6

Основываясь на коде Эмили, я сделал некоторые изменения в расширении, потому что он не сделает элементы, которые не существуют в defaultOrder в конце

extension Array where Element == String {

func reordered() -> [String] {

    let defaultOrder = ["lemon", "watermelon", "tomatoes"]

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

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

        return first < second
    }
}

let arrayToSort = ["orange", "watermelon", "grapefruit", "lemon", "tomatoes"]
let sortedArray = arrayToSort.reordered()
print(sortedArray) // ["watermelon", "lemon", "tomatoes", "orange", "grapefruit"]