|
| 1 | +# AdjacentPairs |
| 2 | + |
| 3 | +[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Grouped.swift) | |
| 4 | + [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/GroupedTests.swift)] |
| 5 | + |
| 6 | +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. |
| 7 | + |
| 8 | +This operation is available for any sequence by calling the `grouped(by:)` |
| 9 | +method. |
| 10 | + |
| 11 | +```swift |
| 12 | +let fruits = ["Apricot", "Banana", "Apple", "Cherry", "Avocado", "Coconut"] |
| 13 | +let fruitsByLetter = fruits.grouped(by: { $0.first! }) |
| 14 | +// Results in: |
| 15 | +[ |
| 16 | + "B": ["Banana"], |
| 17 | + "A": ["Apricot", "Apple", "Avocado"], |
| 18 | + "C": ["Cherry", "Coconut"], |
| 19 | +] |
| 20 | +``` |
| 21 | + |
| 22 | +If you wish to achieve a similar effect but for single values (instead of Arrays of grouped values), see [`keyed(by:)`](Keyed.md). |
| 23 | + |
| 24 | +## Detailed Design |
| 25 | + |
| 26 | +The `grouped(by:)` method is declared as a `Sequence` extension returning |
| 27 | +`[GroupKey: [Element]]`. |
| 28 | + |
| 29 | +```swift |
| 30 | +extension Sequence { |
| 31 | + public func grouped<GroupKey>( |
| 32 | + by keyForValue: (Element) throws -> GroupKey |
| 33 | + ) rethrows -> [GroupKey: [Element]] |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +### Complexity |
| 38 | + |
| 39 | +Calling `grouped(by:)` is an O(_n_) operation. |
| 40 | + |
| 41 | +### Comparison with other languages |
| 42 | + |
| 43 | +| Language | Grouping API | |
| 44 | +|---------------|--------------| |
| 45 | +| Java | [`groupingBy`](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/stream/Collectors.html#groupingBy(java.util.function.Function)) | |
| 46 | +| Kotlin | [`groupBy`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/group-by.html) | |
| 47 | +| C# | [`GroupBy`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.groupby?view=net-7.0#system-linq-enumerable-groupby) | |
| 48 | +| Rust | [`group_by`](https://doc.rust-lang.org/std/primitive.slice.html#method.group_by) | |
| 49 | +| Ruby | [`group_by`](https://ruby-doc.org/3.2.2/Enumerable.html#method-i-group_by) | |
| 50 | +| Python | [`groupby`](https://docs.python.org/3/library/itertools.html#itertools.groupby) | |
| 51 | +| PHP (Laravel) | [`groupBy`](https://laravel.com/docs/10.x/collections#method-groupby) | |
| 52 | + |
| 53 | +#### Naming |
| 54 | + |
| 55 | +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](https://www.swift.org/documentation/api-design-guidelines/). |
| 56 | + |
| 57 | +#### Customization points |
| 58 | + |
| 59 | +Java and C# are interesting in that they provide multiple overloads with several points of customization: |
| 60 | + |
| 61 | +1. Changing the type of the groups. |
| 62 | + 1. E.g. the groups can be Sets instead of Arrays. |
| 63 | + 1. Akin to calling `.transformValues { group in Set(group) }` on the resultant dictionary, but avoiding the intermediate allocation of Arrays of each group. |
| 64 | +2. Picking which elements end up in the groupings. |
| 65 | + 1. The default is the elements of the input sequence, but can be changed. |
| 66 | + 2. Akin to calling `.transformValues { group in group.map(someTransform) }` on the resultant dictionary, but avoiding the intermediate allocation of Arrays of each group. |
| 67 | +3. Changing the type of the outermost collection. |
| 68 | + 1. E.g using an `OrderedDictionary`, `SortedDictionary` or `TreeDictionary` instead of the default (hashed, unordered) `Dictionary`. |
| 69 | + 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. |
| 70 | + |
| 71 | +It is not clear which of these points of customization are worth supporting, or what the best way to express them might be. |
0 commit comments