【问题标题】:iOS Swift - Sort array by enum pattern [closed]iOS Swift - 按枚举模式排序数组[关闭]
【发布时间】:2021-04-06 15:21:21
【问题描述】:

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

  • .电影
  • .tv
  • .预告片
  • .流派
  • .文章

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

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

[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 ...]

【问题讨论】:

  • 你试过什么?你被困在哪里了?
  • 你只需要让你的枚举符合Comarable。顺便说一句,我会将您的枚举名称更改为 Kind
  • 我已经使枚举符合 Comparable - 但不会让所有电影都列在电视之前,依此类推[电影,电影电影,电视,电视,电视,预告片,预告片,预告片等...]?
  • 您不想对它们进行排序,而是希望按照某种模式对它们进行排序。只需按它们的种类将它们分组到数组中,然后遍历数组数组,在每次传递时获取每个数组的一项。

标签: ios arrays swift sorting


【解决方案1】:

这是处理它的一种方法。首先使用map 将排序Int index 与每个项目相关联。使用字典来跟踪与每个 Kind 关联的最后一个索引,并将其增加不同种类的数量。这将为数组中的每个项目提供一个唯一的排序索引,由于添加到重复的Kinds 的增量,项目被排序到所需的模式中。

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

struct Item: CustomStringConvertible {
    var description: String { "\(name): \(kind)" }
    
    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)]

// Dictionary used to generate a unique sorting index for each kind
var dict: [Kind: Int] = [:]

typealias IndexedItem = (index: Int, element: Item)

// Assign a sorting index to each item.  Repeated Kinds will be incremented by
// allCases.count so that they sort into the next group
let items2: [IndexedItem] = items.map { item in
    dict[item.kind, default: item.kind.rawValue] += Kind.allCases.count
    return (dict[item.kind]!, item)
}

let result = items2.sorted { $0.index < $1.index }.map(\.element)
print(result)

输出

[B:电影,D:电视,F:预告片,H:流派,J:文章,A:电影,C:电视,E:预告片,G:流派,I:文章]


基数排序 - 更快的排序

由于所有索引都是唯一的,我们可以使用基数排序创建 result 数组:

// Assign a sorting index to each item.  Repeated Kinds will be incremented by
// allCases.count so that they sort into the next group
let cases = Kind.allCases.count
let items2: [IndexedItem] = items.map { item in
    dict[item.kind, default: item.kind.rawValue - cases] += cases
    return (dict[item.kind]!, item)
}

// Use a radix sort to order the items
let maxIndex = dict.values.max() ?? -1
var slots = [Item?](repeating: nil, count: maxIndex + 1)
items2.forEach { slots[$0.index] = $0.element }
let result = slots.compactMap { $0 }

这相当于创建一个足够大的nil 数组以容纳最大索引,使用它们的index 将项目放入数组中,然后使用compactMap() 删除nils(空槽)。这种排序算法是O(n),而不是像一般排序算法那样的O(n log n)。

【讨论】:

  • @LeoDabus,现在我已经用基数排序更新了我的解决方案,我们的方法的复杂性类似。两者都是 O(n)。
【解决方案2】:

在旧的 Swift 版本(Swift 5.2.x 或更早版本)中,当枚举符合 Comparable 协议时,您需要将其 rawValue 声明为 Int 而不是 String 否则它不会'没有任何意义,因为枚举通常不是按字典顺序排序的。在 Swift 5.3 或更高版本中,如果您希望它自动合成,则不能声明任何 rawValue 类型。你可以在 Swift evolution 上查看这篇关于Synthesized Comparable conformance for enum types的帖子

选择加入综合 Comparable 一致性的枚举类型 将根据案例声明顺序与后面的案例进行比较 比以前的情况要大。只有枚举类型没有 只有 Comparable 关联的关联值和枚举类型 值将有资格获得综合一致性。后一种 的枚举将首先按案例声明顺序进行比较,然后 按有效载荷值按字典顺​​序排列。没有带有原始值的枚举类型 符合条件。

Swift 5.3 或更高版本

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

完成此操作后,您可以使用 custom sort 简单地对您的收藏集进行排序,我指出此问题是重复的:

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


编辑/更新

我不知道是否有更简单的方法来完成这种排序(我很想得到一些反馈)但是您可以按名称对项目进行排序,按种类分组,然后transpose您的项目。您需要使您的枚举 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)]]

【讨论】:

  • 如果缺少信息,我已经添加了最低 Swift 版本以使其正常工作。除此之外,这篇文章没有任何问题,所以请发表评论。
  • 感谢您的详细回复。您的 Playground 测试首先显示所有电影,然后是电视,依此类推。我要完成的是具有以下模式的排序:[电影,电视,预告片,流派,文章,电影,电视,预告片,流派,文章] ...等等。 1种,一个接一个@Leo Dabus
  • 这是一种非常不寻常的排序方式,仍然不清楚您的问题的逻辑。如果它们具有相同的种类(类型),排序方法是什么?顺便说一句,您在帖子中说[movie, movie, movie, tv, tv, tv, trailer, trailer, trailer, etc...]
  • 我认为我的帖子令人困惑。我说这是我不想要的:[movie, movie, movie, tv, tv, tv, trailer, trailer, trailer, etc...]
【解决方案3】:

这可以通过 rawValue 实现。您应该声明 Int 类型的 Enum。

enum Catagory: Int {
    case movie, tv, trailer, genre, article
}

然后,您可以使用排序函数对具有枚举变量“类型”的对象数组进行排序

let sortedArray = array.sorted(by: {$0.type.rawValue < $1.type.rawValue})

【讨论】:

  • 根本不需要声明任何rawValue。检查我在下面发布的关于枚举类型的 Synthesized Comparable 一致性的链接((Swift 5.3)。这是过去在旧 Swift 版本中完成的方式。
猜你喜欢
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
  • 2021-05-07
  • 1970-01-01
  • 1970-01-01
  • 2017-07-21
  • 2020-06-07
  • 2015-10-02
相关资源
最近更新 更多