Swift 3:对对象数组的分组求和值

Posted

技术标签:

【中文标题】Swift 3:对对象数组的分组求和值【英文标题】:Swift 3: sum value with group by of an array of objects 【发布时间】:2016-11-17 14:15:48 【问题描述】:

我的 viewController 中有这段代码

var myArray :Array<Data> = Array<Data>()
for i in 0..<mov.count     
   myArray.append(Data(...))


class Data 
    var value :CGFloat
    var name  :String=""
    init(...)

我输入的数据如下:

10.5个苹果 20.0 柠檬 15.2 苹果 45

一旦循环,我想返回一个新数组:

sum(value) 按名称分组 删除最后一行,因为没有名字 按值排序

基于输入的预期结果:

25.7 苹果 20.0 柠檬 别无其他

我写了很多行代码,太混乱了,无法发布。我会找到更简单的方法,有人对此有想法吗?

【问题讨论】:

考虑重命名您的 Data 类,因为 Foundation 包含一个同名的类。 请向我们展示您目前最好的解决方案,我们可以让您知道哪里出了问题 【参考方案1】:

首先Data在Swift 3中是保留的,这个例子使用了一个名为Itemstruct

struct Item 
    let value : Float
    let name  : String

使用给定值创建数据数组

let dataArray = [Item(value:10.5, name:"apple"), 
                 Item(value:20.0, name:"lemon"), 
                 Item(value:15.2, name:"apple"), 
                 Item(value:45, name:"")]

和一个结果数组:

var resultArray = [Item]()

现在过滤所有不为空的名称并创建一个Set - 每个名称在集合中出现一次:

let allKeys = Set<String>(dataArray.filter(!$0.name.isEmpty).map$0.name)

遍历键,过滤dataArray中所有同名的项目,将值相加并创建一个带有总值的新Item

for key in allKeys 
    let sum = dataArray.filter($0.name == key).map($0.value).reduce(0, +)
    resultArray.append(Item(value:sum, name:key))

最后按值降序对结果数组进行排序:

resultArray.sorted(by: $0.value < $1.value)

---

编辑:

在 Swift 4 中引入了一个更高效的 API,可以通过谓词 Dictionary(grouping:by: 对数组进行分组

var grouped = Dictionary(grouping: dataArray, by:$0.name)
grouped.removeValue(forKey: "") // remove the items with the empty name

resultArray = grouped.keys.map  (key) -> Item in
    let value = grouped[key]!
    return Item(value: value.map$0.value.reduce(0.0, +), name: key)
.sorted$0.value < $1.value

print(resultArray)

【讨论】:

我正在玩你的答案来理解。我写了let allKeys = Set&lt;String&gt; (dataArray.filter(!$0.name.isEmpty))。我故意没有写map$0.name) 来查看/理解我得到的...但是我收到一个错误,提示对成员过滤器的模糊引用。我看了here,但仍然无法解决。我做错了什么? 没有map,你会得到Set&lt;Item&gt; 我明白了那个。但是现在,如果我只在您的答案中写这么多,为什么会出现该错误。 Set 中使用的类型必须符合 Hashable,而简单结构不符合。 该错误可能具有误导性。歧义是类型不匹配 - Set&lt;String&gt; 已注释但推断的类型为 Set&lt;Item&gt; 或无法创建集合,因为 Item 不可散列。【参考方案2】:

首先,你不应该将你的类命名为Data,因为那是基础类的名称。我使用了一个名为 MyData 的结构:

struct MyData 
    let value: CGFloat
    let name: String


let myArray: [MyData] = [MyData(value: 10.5, name: "apple"),
                         MyData(value: 20.0, name: "lemon"), 
                         MyData(value: 15.2, name: "apple"),
                         MyData(value: 45,   name: "")]

您可以使用字典将与每个名称关联的值相加:

var myDictionary = [String: CGFloat]()
for dataItem in myArray 
    if dataItem.name.isEmpty 
        // ignore entries with empty names
        continue
     else if let currentValue = myDictionary[dataItem.name] 
        // we have seen this name before, add to its value
        myDictionary[dataItem.name] = currentValue + dataItem.value
     else 
       // we haven't seen this name, add it to the dictionary
        myDictionary[dataItem.name] = dataItem.value
    

然后您可以将字典转换回MyData 对象的数组,对它们进行排序并打印它们:

// turn the dictionary back into an array
var resultArray = myDictionary.map  MyData(value: $1, name: $0) 

// sort the array by value
resultArray.sort  $0.value < $1.value 

// print the sorted array
for dataItem in resultArray 
    print("\(dataItem.value) \(dataItem.name)")

【讨论】:

显然这是可行的,但它是以一种非常不迅速的方式编写的。 :)【参考方案3】:

首先更改您的数据类,使字符串成为可选的,它变得更容易处理。所以现在如果没有名字,那就是零。如果需要,您可以将其保留为“”,但需要在下面进行一些细微的更改。:

class Thing 
    let name: String?
    let value: Double

    init(name: String?, value: Double)
        self.name = name
        self.value = value
    

    static func + (lhs: Thing, rhs: Thing) -> Thing? 
        if rhs.name != lhs.name 
            return nil
         else 
            return Thing(name: lhs.name, value: lhs.value + rhs.value)
        
    

我给了它一个操作符,所以它们可以很容易地添加。它返回一个可选项,所以使用时要小心。

然后让我们为充满事物的数组做一个方便的扩展:

extension Array where Element: Thing 

func grouped() -> [Thing] 

    var things = [String: Thing]()

    for i in self 
        if let name = i.name 
            things[name] = (things[name] ?? Thing(name: name, value: 0)) + i
        
    
    return things.map$0.1.sorted$0.value > $1.value


快速测试一下:

let t1 = Thing(name: "a", value: 1)
let t2 = Thing(name: "b", value: 2)
let t3 = Thing(name: "a", value: 1)
let t4 = Thing(name: "c", value: 3)
let t5 = Thing(name: "b", value: 2)
let t6 = Thing(name: nil, value: 10)


let bb = [t1,t2,t3,t4,t5,t6]

let c = bb.grouped()

// ("b",4), ("c",3) , ("a",2)

编辑:添加了一个名称为 nil 的示例,该示例被 grouped() 函数中的 if let 过滤掉了

【讨论】:

以上是关于Swift 3:对对象数组的分组求和值的主要内容,如果未能解决你的问题,请参考以下文章

返回匹配object.date和求和object.value的对象数组[关闭]

请问js中对象数组求和代码怎么实现?

如何通过特定对象键对数组中的值求和?

如何使用 AngularJs 对对象数组中的属性值求和

如何对对象中特定键的所有值求和?

在 Swift 中过滤对象数组并求和它们的属性