Snippet of the Week: `grouped(by:)`


Last holiday, I had the opportunity to start on a new side-project. One of the techniques I touched on, was grouping an array of model objects into sections. Instead of writing my umpteenth for-loop for this, I opted to create a generic function for it.

My first attempt resulted in an Array extension group(by:);

["1", "1", "2", "3", "3", "1"].group(by: { return $0 } )
// [ ["1", "1", "1"], ["3", "3"], ["2"] ]

It worked, but I still would have to do any sorting of the sections myself. Also, I wouldn’t have any information about the section grouping. But, for a first attempt, it’s great! Ship it!

After ‘shipping it’ to the CocoaHeadsNL Slack for some critique, Tim Vermeulen came up with some improvements, which resulted in the following snippet.

A Sequence extension, which returns a grouped dictionary. Much handier than the original array!

public extension Sequence {
    typealias Element = Iterator.Element
    
    /// Returns all elements in arrays grouped by the closure.
    public func grouped<Key: Hashable>(by key: (Element)->(Key)) -> [Key : [Element]] {
        
        var dict: [Key : [Element]] = [:]
        for element in self {
            let key = key(element)
            var array = dict.removeValue(forKey: key) ?? []
            array.append(element)
            dict.updateValue(array, forKey: key)
        }
        return dict
    }
}

["1", "1", "2", "3", "3", "1"].grouped(by: { return $0 } )
// { "1" : ["1", "1", "1"], "3" : ["3", "3"], "2" : ["2"] }

What did I gain?

  • practice with generic functions
  • info about the Sequence function
  • renewed faith in shipping early and often

Thanks to the Tim and the other CocoaHeads for collaborating on this! If you think the snippet is useful – or have an improvement – send me tweet or a toot!