Arrays 循环并合并结构的Swift数组?

Arrays 循环并合并结构的Swift数组?,arrays,swift,struct,Arrays,Swift,Struct,我试图创建一个结构数组(下面的User structs),如果User.name在数组中还不存在,它会附加User——但是如果User.name已经存在(下面的示例中是“麦当劳”),那么它只会将item.amount添加到现有结构中。 换句话说,下面的代码应该创建一个包含4个用户的数组,其中用户Mcdonalds item.amount的总数为23.44+12.33。 我记得我毫不费力地做了这类事情,但我一辈子都不知道如何用Swift做这件事。谢谢你的帮助 struct User { v

我试图创建一个结构数组(下面的User structs),如果User.name在数组中还不存在,它会附加User——但是如果User.name已经存在(下面的示例中是“麦当劳”),那么它只会将item.amount添加到现有结构中。 换句话说,下面的代码应该创建一个包含4个用户的数组,其中用户Mcdonalds item.amount的总数为23.44+12.33。 我记得我毫不费力地做了这类事情,但我一辈子都不知道如何用Swift做这件事。谢谢你的帮助

struct User {
    var name: String
    var amount: Double
}

var user1 = User(name: "Mcdonalds", amount: 23.44)
var user2 = User(name: "Wendys", amount: 15.44)
var user3 = User(name: "Cabanos", amount: 12.22)
var user4 = User(name: "Shell Gas", amount: 23.33)
var user5 = User(name: "Mcdonalds", amount: 12.33)

要在用户上循环,他们需要在一个数组中才能开始

然后,您可以使用
.reduce(into:)
将它们简化为一个压缩字典(字典将允许您拥有唯一的键(此处的用户名),这样您就不会有重复的条目)。然后,您可以使用
.map()
仅获取该字典的值而不是键,这样最终结果将是一个用户数组

struct User {
    var name: String
    var amount: Double
}

var users = [
    User(name: "Mcdonalds", amount: 23.44),
    User(name: "Wendys", amount: 15.44),
    User(name: "Cabanos", amount: 12.22),
    User(name: "Shell Gas", amount: 23.33),
    User(name: "Mcdonalds", amount: 12.33)
]

var reducedUsers = users.reduce(into: [String: User]()) { (result, nextUser) in
    if let existing = result[nextUser.name] {
        result[nextUser.name] = User(name: nextUser.name, amount: existing.amount + nextUser.amount)
    } else {
        result[nextUser.name] = nextUser
    }
}.map { $0.value }

一种简洁而快速的方法是为
数组
编写扩展。Swift是高度面向协议的,这意味着您可以使用新函数扩展任何现有系统或自行编写的类

这只是一个简单的实现,它使用一个函数附加或更新任何给定的用户对象:

extension Array where Element == User {
    
    /// Appends a not existing user to the array or updates the amount value if user is already present
    mutating func appendOrUpdate(_ userObject: Element) {
        
        // Check if user is already in the array
        if let index = self.firstIndex(where: { $0.name == userObject.name }) {
            
            // Update the amount
            self[index].amount += userObject.amount
            
        }
        else {
            
            // Append the user
            self.append(userObject)
            
        }
        
    }
    
}
由于
where
子句指定了仅当给定对象是您的
User
结构时才应用的数组的
元素的扩展,因此它仅在传入用户对象时可用

用法:

var userArray: [User] = []
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 23.44))
userArray.appenOrUpdate(User(name: "Wendys", amount: 15.44))
userArray.appenOrUpdate(User(name: "Cabanos", amount: 12.22))
userArray.appenOrUpdate(User(name: "Shell Gas", amount: 23.33))
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 12.33))
这将导致一个仅包含四个条目的数组,以及双条目“麦当劳”用户的累计金额

请注意,函数前面有
mutating
关键字,如果没有,您将无法修改数组及其条目。这是必要的,因为数组本身就是结构

您还可以编写一个类似于know数组的
append(contentsOf:)
的函数,传入一个用户对象数组,并在其中循环更新或追加给定对象

最好的方法是根据最佳命名惯例,将此扩展名放入名为
Array+User.swift
的单独文件中


您可以在此处阅读更多有关Swift中扩展及其功能的信息:

Matthew Gray的回答非常好,可以用于比此问题更复杂的各种问题。但对于这个具体问题,可以做得简单得多

let reducedUsers = users.reduce(into: [:]) { (result, user) in
    result[user.name, default: 0] += user.amount
}
.map(User.init)
这一点是,它将结构拆分为字典中的键和值,然后在最后将值重新组合到数组中。Swift足够聪明,可以判断出
[:]
的类型,所以不需要指定它


注意,这里有一个时空权衡。这将创建一个临时字典,它可能非常大。如果这种操作是常见的,而且数据集很大,则应该考虑将数据存储在字典(<代码> [字符串:用户] < /代码>或<代码> [字符串:双] < /代码>)中,而不是来回转换。

这是一个很好的“快速”解决方案,但是请注意,<代码>第一个索引()/<代码>有<代码> o(n)< /代码>性能,这意味着,使用
appendOrUpdate()
函数将对象添加到这样一个数组中会有大致
O(n²)
的性能,除了少量元素外,其他元素的性能都会非常慢。另一个使用reduce的解决方案(由matthew Gray提出)对于大型阵列来说速度更快。我不认为将阵列扩展到如此特定于业务逻辑的东西是合适的。