在Swift中按键分组字典

3

我正在尝试实现一个groupBy功能,将嵌套列表中的所有数字分组。到目前为止,我的代码如下:

struct MyClass {
    var numbers: [Int]
    ...
}

var dict: [String : MyClass] = ...
let numbers = dict
   .filter{ $0.0.containsString(searchString) }
   .flatMap{ $0.1.numbers }

这会给我一个由整数数组组成的 Array。然而,我想要一个包含每个唯一数字及其出现次数的字典 [Int : Int]。例如:
[1,2,3,4,1,2,2,1]

should be:

[1 : 2, 2 : 3, 3 : 1, 4 : 1]

我知道有一个groupBy运算符,但是Swift似乎没有这个。我尝试使用reduce

func reducer(accumulator: [Int: Int], num: Int) -> [Int : Int] {
    var acc = accumulator
    acc[num]! += 1
    return acc
}

filtered.reduce([:], combine: reducer)

但是当我想运行它时,它会崩溃。不确定为什么,我收到了EXC_BAD_INSTRUCTION的错误。

我会非常感激任何帮助。


抱歉,是字典的意思。 - SamW
如果代码可以复制和粘贴,那将非常有帮助。 - ryantxr
你说的复制粘贴是什么意思? - SamW
我认为 @ryantxr 的意思是提供一个可重现和可测试的示例,就像我自己在 Playground 中所做的那个示例(https://www.evernote.com/l/AOwWtxGnQyVOr5b-sX9I_lSfo4eG4OGj6VE)。 - Eric Aya
如果可能的话,我建议避免使用reduce来创建字典。 - Eendje
我把你的代码粘贴到 Playground 中,但在运行之前必须“修复”几个问题。例如,“...”,以及变量 filtered 不存在。 - ryantxr
5个回答

3
我会将崩溃出现在此行的期望传达给您:

我预计崩溃会发生在这一行:

acc[num]! += 1

第一次对一个数字调用此函数时,字典中尚不存在该项,因此acc[num]nil。强制解包会导致崩溃。
不确定这是否是最佳解决方案,但您可以简单地检查此情况:
if (acc[num]) {
    acc[num]! += 1
} else {
    acc[num] = 1
}

评论中@vacawama提供的更简洁的代码:

acc[num] = (acc[num] ?? 0) + 1

@Swalker,没问题,很高兴能帮到你。:] 欢迎来到SO! - Jack
4
acc[num] = (acc[num] ?? 0) + 1 - vacawama

3
let numbers = [1,2,3,4,1,2,2,1]
var results = [Int: Int]()

Set(numbers).forEach { number in results[number] = numbers.filter { $0 == number }.count }

print(results) // [2: 3, 3: 1, 1: 3, 4: 1]

实际上我不太确定这是否符合您的要求。我只是查看了您的示例。

使用NSCountedSet

var objects = [1,2,3,4,1,2,2,1]
let uniques = NSCountedSet(array: objects)
uniques.forEach { results[$0 as! Int] = uniques.countForObject($0) }

print(results) // [2: 3, 3: 1, 1: 3, 4: 1]

这太棒了。 - ryantxr

2

这是一个扩展Array的方法,可以实现你所要求的功能:

extension Array where Element: Hashable {
  var grouped: [Element:Int] {
    var dict = [Element:Int]()
    self.forEach { dict[$0] = (dict[$0] ?? 0) + 1 }
    return dict
  }
}

重点在于闭包表达式:{ dict[$0] = (dict[$0] ?? 0) + 1 }
它获取数组中当前的值,检查它是否是字典中的键,并返回该键的值(如果存在)或0(如果不存在)。然后将值加1,并将键:值设置为当前值和到目前为止出现的次数对。
示例用法:
[1,2,3,4,1,2,2,1].grouped // => [2: 3, 3: 1, 1: 3, 4: 1]

0
你需要像这样的东西:
if let _ = acc.indexForKey(num) {
    acc[num]! += 1
}
else {
    acc[num] = 1
}

0

不太清楚你需要什么,但是这里有一个函数,它将接受一个整数数组并返回一个字典,其中数字作为键,计数作为值:

func getDictionaryOfCounts(accumulator: [Int]) -> [Int : Int] {
    var countingDictionary: [Int : Int] = [:]
    accumulator.forEach { (value) in
        if countingDictionary[value] != nil {
            countingDictionary[value]! += 1
        }
        else{
            countingDictionary[value] = 1
        }
    }
    return countingDictionary
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接