Back to Swift Algorithms

Grouped

Guides/Grouped.md

1.2.13.5 KB
Original Source

Grouped

[Source | Tests]

Groups up elements of a sequence into a new Dictionary, whose values are Arrays of grouped elements, each keyed by the result of the given closure.

swift
let fruits = ["Apricot", "Banana", "Apple", "Cherry", "Avocado", "Coconut"]
let fruitsByLetter = fruits.grouped(by: { $0.first! })
// Results in:
// [
//     "B": ["Banana"],
//     "A": ["Apricot", "Apple", "Avocado"],
//     "C": ["Cherry", "Coconut"],
// ]

If you wish to achieve a similar effect but for single values (instead of Arrays of grouped values), see keyed(by:).

Detailed Design

The grouped(by:) method is declared as a Sequence extension returning [GroupKey: [Element]].

swift
extension Sequence {
    public func grouped<GroupKey>(
        by keyForValue: (Element) throws -> GroupKey
    ) rethrows -> [GroupKey: [Element]]
}

Complexity

Calling grouped(by:) is an O(n) operation.

Comparison with other languages

LanguageGrouping API
JavagroupingBy
KotlingroupBy
C#GroupBy
Rustgroup_by
Rubygroup_by
Pythongroupby
PHP (Laravel)groupBy

Naming

All the surveyed languages name this operation with a variant of "grouped" or "grouping". The past tense grouped(by:) best fits Swift's API Design Guidelines.

Customization points

Java and C# are interesting in that they provide multiple overloads with several points of customization:

  1. Changing the type of the groups.
    1. E.g. the groups can be Sets instead of Arrays.
    2. Akin to calling .transformValues { group in Set(group) } on the resultant dictionary, but avoiding the intermediate allocation of Arrays of each group.
  2. Picking which elements end up in the groupings.
    1. The default is the elements of the input sequence, but can be changed.
    2. Akin to calling .transformValues { group in group.map(someTransform) } on the resultant dictionary, but avoiding the intermediate allocation of Arrays of each group.
  3. Changing the type of the outermost collection.
    1. E.g using an OrderedDictionary, SortedDictionary or TreeDictionary instead of the default (hashed, unordered) Dictionary.
    2. There's no great way to achieve this with the grouped(by:). One could wrap the resultant dictionary in an initializer to one of the other dictionary types, but that isn't sufficient: Once the Dictionary loses the ordering, there's no way to get it back when constructing one of the ordered dictionary variants.

It is not clear which of these points of customization are worth supporting, or what the best way to express them might be.