Ответ 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"]