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中是保留的,这个例子使用了一个名为Item
的struct。
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<String> (dataArray.filter(!$0.name.isEmpty))
。我故意没有写map$0.name)
来查看/理解我得到的...但是我收到一个错误,提示对成员过滤器的模糊引用。我看了here,但仍然无法解决。我做错了什么?
没有map
,你会得到Set<Item>
我明白了那个。但是现在,如果我只在您的答案中写这么多,为什么会出现该错误。
Set
中使用的类型必须符合 Hashable
,而简单结构不符合。
该错误可能具有误导性。歧义是类型不匹配 - Set<String>
已注释但推断的类型为 Set<Item>
或无法创建集合,因为 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:对对象数组的分组求和值的主要内容,如果未能解决你的问题,请参考以下文章