SamW
SamW

Reputation: 77

Group dictionary by key in Swift

I'm trying to implement a groupBy functionality where all the numbers of a nested list are grouped. My code so far:

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

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

This yields me an Array of Ints. However I'd like to have a dictionary [Int : Int] with each unique number and the count of its occurence. So for example:

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

should be:

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

I know there's a groupBy operator, but Swift doesn't seem to have one. I've tried with reduce:

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

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

But it crashes when I want to run it. Not sure why, I get a EXC_BAD_INSTRUCTION.

I'd appreciate any help.

Upvotes: 3

Views: 766

Answers (5)

user887210
user887210

Reputation:

Here's an extension to Array that does what you're asking:

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

The key is the closure: { dict[$0] = (dict[$0] ?? 0) + 1 }.

It takes the current value in the array, tests to see if it's a key in the dictionary, returns the value for that key if it exists or 0 if it doesn't, then adds one and sets the key:value to be the pair of the current value and occurrences so far.

Example use:

[1,2,3,4,1,2,2,1].grouped // => [2: 3, 3: 1, 1: 3, 4: 1]

Upvotes: 2

GetSwifty
GetSwifty

Reputation: 7756

It's sort of unclear what you're asking for, but here's a function that will take an array of ints and return a dictionary with the number as the key, and the count as the value:

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
}

Upvotes: 0

Jack
Jack

Reputation: 16865

I would expect the crash to be ocurring on this line:

acc[num]! += 1

The first time this is called for a number, the entry doesn't exist in the dictionary yet so acc[num] is nil. Forcefully unwrapping it would cause a crash.

Not sure if this is the best solution but you can simple check for this case:

if (acc[num]) {
    acc[num]! += 1
} else {
    acc[num] = 1
}

Cleaner code from @vacawama in the comments:

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

Upvotes: 3

Eendje
Eendje

Reputation: 8883

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]

Actually I'm not very sure if this is what you want. I just looked at your examples.

Using 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]

Upvotes: 3

ryantxr
ryantxr

Reputation: 4217

You need something like this:

if let _ = acc.indexForKey(num) {
    acc[num]! += 1
}
else {
    acc[num] = 1
}

Upvotes: 0

Related Questions