首页 > 解决方案 > iOS Swift - 按枚举模式排序数组

问题描述

我有一组自定义对象。这些对象有一个自定义的枚举 var 'type'。不同的类型如下:

我想按模式对数组进行排序 [电影、电视、预告片、流派、文章、电影、电视、预告片、流派、文章……等]

我已经使枚举符合可比较的但是(也许我错了),如果我按类型排序,它不会像这样对数组进行排序:

[movie, movie, movie, tv, tv, tv, trailer, trailer, trailer, etc...]

..事实上,我希望它们以一种又一种的模式出现。

[movie, tv, trailer, genre, article, movie, tv, trailer, genre, article, movie, tv, trailer, genre, article, and so on ...]

标签: iosarraysswiftsorting

解决方案


在旧的 Swift 版本(Swift 5.2.x 或更早版本)中,当使枚举符合Comparable协议时,您需要声明它的 rawValue IntString否则它没有任何意义,因为枚举通常不是按字典顺序排序的。在Swift 5.3 或更高版本中,如果您希望它自动合成,则不能声明任何 rawValue 类型。您可以在 Swift evolution 中查看有关枚举类型的 Synthesized Comparable 一致性的这篇文章

选择加入综合 Comparable 一致性的枚举类型将根据案例声明顺序进行比较,后期案例的比较大于早期案例。只有没有关联值的枚举类型和只有 Comparable 关联值的枚举类型才有资格获得综合一致性。后一种枚举将首先按 case 声明顺序进行比较,然后按字典顺序按有效负载值进行比较。没有具有原始值的枚举类型符合条件。

Swift 5.3 或更高版本

enum Kind: Comparable {
    case movie, tv, trailer, genre, article
}

完成此操作后,您可以使用我为这个问题指出为重复的自定义排序来简单地对集合进行排序:

extension MutableCollection where Self: RandomAccessCollection {
    mutating func sort<T: Comparable>(_ predicate: (Element) -> T, by areInIncreasingOrder: (T, T) -> Bool = (<)) {
        sort { areInIncreasingOrder(predicate($0),predicate($1)) }
    }
}

extension Sequence {
    func sorted<T: Comparable>(_ predicate: (Element) -> T, by areInIncreasingOrder: (T,T)-> Bool = (<)) -> [Element] {
        sorted { areInIncreasingOrder(predicate($0),predicate($1)) }
    }
}

游乐场测试:

struct Item {
    let id: Int
    let name: String
    let kind: Kind
}

let items: [Item] = [
    .init(id:  1, name: "D", kind: .tv),
    .init(id:  2, name: "B", kind: .movie),
    .init(id:  3, name: "F", kind: .trailer),
    .init(id:  4, name: "H", kind: .genre),
    .init(id:  5, name: "J", kind: .article),
    .init(id:  6, name: "C", kind: .tv),
    .init(id:  7, name: "A", kind: .movie),
    .init(id:  8, name: "E", kind: .trailer),
    .init(id:  9, name: "G", kind: .genre),
    .init(id: 10, name: "I", kind: .article)]

items.sorted(\.kind)  // [{id 2, name "B", movie}, {id 7, name "A", movie}, {id 1, name "D", tv}, {id 6, name "C", tv}, {id 3, name "F", trailer}, {id 8, name "E", trailer}, {id 4, name "H", genre}, {id 9, name "G", genre}, {id 5, name "J", article}, {id 10, name "I", article}]


编辑/更新

我不知道是否有更简单的方法来完成这种排序(我很想得到一些反馈)但是您可以按名称对项目进行排序,按种类分组,然后转置您的项目。您需要使您的枚举 CaseIterable 并将其 rawValue 声明为 Int 从零开始。因此,将这些助手添加到您的项目中:


extension Collection where Element: RandomAccessCollection, Element.Indices == Range<Int> {
    func transposed() -> [[Element.Element]] {
        (0..<(max(\.count)?.count ?? .zero)).map {
            index in compactMap { $0.indices ~= index ? $0[index] : nil }
        }
    }
}

extension Sequence {
    func max<T: Comparable>(_ predicate: (Element) -> T)  -> Element? {
        self.max(by: { predicate($0) < predicate($1) })
    }
}

接着:

enum Kind: Int, CaseIterable {
    case movie = 0, tv, trailer, genre, article
}

let grouped: [[Item]] = items.reduce(into: .init(repeating: [], count: Kind.allCases.count)) { result, item in
    result[item.kind.rawValue].append(item)
}
let transposed = grouped.map{$0.sorted(\.name)}.transposed()

print(transposed)  // [[Item(id: 7, name: "A", kind: Kind.movie), Item(id: 6, name: "C", kind: Kind.tv), Item(id: 8, name: "E", kind: Kind.trailer), Item(id: 9, name: "G", kind: Kind.genre), Item(id: 10, name: "I", kind: Kind.article)], [Item(id: 2, name: "B", kind: Kind.movie), Item(id: 1, name: "D", kind: Kind.tv), Item(id: 3, name: "F", kind: Kind.trailer), Item(id: 4, name: "H", kind: Kind.genre), Item(id: 5, name: "J", kind: Kind.article)]]

推荐阅读