在Swift中用字典对数组元素进行分组

在Swift中用字典对数组元素进行分组

hudson 译 原文

想象一下,您有一个Device对象数组,并且您想按类别对它们进行分组,如下图所示:

你应该如何解决这个问题?

在Swift 5之前,最直接的方法是循环遍及数组中的每个设备,并手动将每个元素分配给其各自的类别。在Swift 5中,苹果引入了范型字典初始化器,以帮助开发人员只需一行代码即可处理这种情况。

init(grouping:by:)介绍

在Swift 5中,苹果引入了"分组"字典初始化器。根据文档,初始化器有以下定义:

创建一个新的字典,其键是给定闭包返回的分 组,其值是返回每个键的元素的数组。

为了更好地理解定义,让我们重温我们在本文开头看到的示例。

假设您有一个Device结构和一个Device对象数组,如下所示:

swift 复制代码
struct Device {
    let category: String
    let name: String
}

let deviceArray = [
    Device(category: "Laptop", name: "Macbook Air"),
    Device(category: "Laptop", name: "Macbook Pro"),
    Device(category: "Laptop", name: "Galaxy Book"),
    Device(category: "Laptop", name: "Chromebook"),
    Device(category: "Mobile Phone", name: "iPhone SE"),
    Device(category: "Mobile Phone", name: "iPhone 11"),
    Device(category: "Mobile Phone", name: "Galaxy S"),
    Device(category: "Mobile Phone", name: "Galaxy Note"),
    Device(category: "Mobile Phone", name: "Pixel")
]

要按类别对所有Device数组进行分组,我们可以通过创建一个新的字典,并向其初始化器提供源数组和一个闭包,该闭包返回需要进行分组的键。 看看下面的代码片段:

swift 复制代码
let groupByCategory = Dictionary(grouping: deviceArray) { (device) -> String in
    return device.category
}

/*
// Output of groupByCategory equivalent to:
[
    "Laptop": [
        Device(category: "Laptop", name: "Macbook Air"),
        Device(category: "Laptop", name: "Macbook Pro"),
        Device(category: "Laptop", name: "Galaxy Book"),
        Device(category: "Laptop", name: "Chromebook")
    ],
    "Mobile Phone": [
        Device(category: "Mobile Phone", name: "iPhone SE"),
        Device(category: "Mobile Phone", name: "iPhone 11"),
        Device(category: "Mobile Phone", name: "Galaxy S"),
        Device(category: "Mobile Phone", name: "Galaxy Note"),
        Device(category: "Mobile Phone", name: "Pixel")
    ]
]
*/

如您所见,我们只需要传递设备数组并将类别作为分组键返回,初始化器将为我们处理分组。

我们甚至可以通过使用Swift中的速记参数名称,将上述代码片段进一步简化为一行代码。

swift 复制代码
let groupByCategory = Dictionary(grouping: deviceArray) { $0.category }

很整洁,不是吗?

上面的示例演示了使用初始化器的最标准方式。然而,由于初始化器允许我们定义确定分组键的闭包,因此它比这灵活得多。

通过使用相同的deviceArray 数组 ,假设我们想将所有苹果产品组合在一起,我们实际上可以定义一个闭包,检查设备名称,并将所有设备分组为包含"Macbook"和"iPhone"的名称。

swift 复制代码
let groupByCategoryWithApple = Dictionary(grouping: deviceArray) { (device) -> String in
    
    // Group all devices that name contain the word "Macbook" and "iPhone"
    if device.name.contains("Macbook") || device.name.contains("iPhone") {
        return "Apple"
    } else {
        return "Others"
    }
}

/*
// Output of groupByCategoryWithApple equivalent to:
[
    "Apple": [
        Device(category: "Laptop", name: "Macbook Air"),
        Device(category: "Laptop", name: "Macbook Pro"),
        Device(category: "Mobile Phone", name: "iPhone SE"),
        Device(category: "Mobile Phone", name: "iPhone 11"),
    ],
    "Others": [
        Device(category: "Laptop", name: "Galaxy Book"),
        Device(category: "Laptop", name: "Chromebook"),
        Device(category: "Mobile Phone", name: "Galaxy S"),
        Device(category: "Mobile Phone", name: "Galaxy Note"),
        Device(category: "Mobile Phone", name: "Pixel")
    ]
]
*/

按自定义对象分组

在本节中,我们将研究如何使用自定义对象作为init(grouping:by:)初始化器的分组键。

出于演示目的,让我们通过定义Company公司结构来更新前面的示例,并将company变量添加到设备结构中。随后,我们将尝试按公司对所有设备进行分组。

swift 复制代码
struct Company {
    let name: String
    let founder: String
}

struct Device {
    let category: String
    let name: String
    let company: Company
}

// Define Company objects
let samsung = Company(name: "Samsung", founder: "Lee Byung-chul")
let apple = Company(name: "Apple", founder: "Steve Jobs")
let google = Company(name: "Google", founder: "Larry Page")

let deviceArray = [
    Device(category: "Laptop", name: "Macbook Air", company: apple),
    Device(category: "Laptop", name: "Macbook Pro", company: apple),
    Device(category: "Laptop", name: "Galaxy Book", company: samsung),
    Device(category: "Laptop", name: "Chromebook", company: google),
    Device(category: "Mobile Phone", name: "iPhone SE", company: apple),
    Device(category: "Mobile Phone", name: "iPhone 11", company: apple),
    Device(category: "Mobile Phone", name: "Galaxy S", company: samsung),
    Device(category: "Mobile Phone", name: "Galaxy Note", company: samsung),
    Device(category: "Mobile Phone", name: "Pixel", company: google)
]

接下来,让我们检查Dictionary结构的定义,找出成为字典键需要满足的要求。

swift 复制代码
public struct Dictionary<Key, Value> where Key : Hashable

从上述定义中可以看出,任何符合Hashable协议的对象都可以用作字典键。

专业提示:

查看这篇很棒的文章,了解有关Hashable协议的更多信息。

因此,我们可以继续使Company结构符合Hashable协议,并利用init(grouping:by:)初始化器按公司对所有设备进行分组。

swift 复制代码
// Conform to Hashable protocol
struct Company: Hashable {
    let name: String
    let founder: String
}

// ...
// ...

// Use Company object as grouping key
let groupByCompany = Dictionary(grouping: deviceArray) { $0.company }

就这些,我们已经成功地按公司对所有设备对象进行了分组。

正如Reddit用户svetlyo所建议的那样,我们可以通过使用键路径语法使代码更加简洁。

swift 复制代码
let groupByCategory = Dictionary(grouping: deviceArray, by: \.category)

小结

字典的 Init(grouping:by:)初始化器非常有用,并且非常易于使用。

当我想把数组数据分组到字典中,以便在具有多个节的表格视图或集合视图上显示它们时,我发现它特别方便。

下次您想创建包含多个节(Section)的表格视图时,请务必尝试此方法。

我希望这篇文章能让您清楚地了解如何正确使用Dictionary.init(grouping:by:)初始化器。

如果您喜欢这篇文章,请随时查看我的其他 与Swift相关的文章

如果您有任何问题,请随时在下面的评论部分留下,或者您可以在 推特上联系我。

感谢您的阅读。🧑🏻‍💻

相关推荐
大熊猫侯佩13 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(五)
swiftui·swift·apple watch
大熊猫侯佩2 天前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)
数据库·swiftui·swift
大熊猫侯佩2 天前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)
数据库·swiftui·swift
大熊猫侯佩2 天前
用异步序列优雅的监听 SwiftData 2.0 中历史追踪记录(History Trace)的变化
数据库·swiftui·swift
大熊猫侯佩2 天前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)
数据库·swiftui·swift
season_zhu2 天前
iOS开发:关于日志框架
ios·架构·swift
大熊猫侯佩2 天前
SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效
swiftui·swift·apple
大熊猫侯佩3 天前
SwiftData 共享数据库在 App 中的改变无法被 Widgets 感知的原因和解决
swiftui·swift·apple
大熊猫侯佩3 天前
使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用
数据库·swift·apple
大熊猫侯佩3 天前
SwiftUI 在 iOS 18 中的 ForEach 点击手势逻辑发生改变的解决
swiftui·swift·apple