# Add GroupBy method to Sequence

**URL:** <https://forums.swift.org/t/add-groupby-method-to-sequence/37680>\
**Category:** Pitches\
**Tags:** generics, collection\
**Created:** [June 17, 2020, 5:29pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680 "2020-06-17T17:29:35Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![tomhamming](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tomhamming/32/12153_2.png) [@tomhamming](https://forums.swift.org/u/tomhamming)\
**Post date:** [June 17, 2020, 5:29pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/1 "2020-06-17T17:29:35Z")

</div>

# Introduction

Early in my career I worked a lot with C#, and I found the `IEnumerable<T>` extension methods extremely useful. I think Swift would benefit from adding some equivalents. This pitch is for `GroupBy`, which would be a method on `Sequence` that takes a transformation closure to produce a key from each element and returns an array of `Group` objects that amount to all values that share one value of a key.

# Motivation

Grouping objects by values of some transformation is a common need. Take the Contacts app in iOS - it displays a list of contacts grouped by the first letter of last name (by default in en-us, at least) and shows a scrubber with all letters on the right. Tons of apps have lists like this. `GroupBy` would make generating this display from a flat collection of contacts trivial. Given a `Contact` struct with `firstName` and `lastName` and a `Group` class that has a `key` property:

```swift
let groups = allContacts.GroupBy({$0.lastName.prefix(1).uppercased()})
    .sorted {lhs, rhs) -> Bool in 
    return lhs.key < rhs.key
    }

```

Something close to this can be done today with the [grouping initializer on Dictionary](https://developer.apple.com/documentation/swift/dictionary/3127163-init), but I would suggest that having it available directly on `Collection` makes the flow a little more readable. And it's more applicable when you want a collection of groups and you care about the order.

# Proposed Solution

```swift
struct Group<Key, Element> {
    let key: Key
    private var elements: [Element]
    init(_ key: Key) {
        self.key = key
        self.elements = []
    }
    
    mutating func append(_ newElement: Element) {
        self.elements.append(newElement)
    }
}

//implement Collection on Group by using the internal Array

extension Sequence {
    func groupBy<T: Hashable>(_ transform: (Self.Element) throws -> T) rethrows -> [Group<T, Self.Element>] {
        var groups: [T: Group<T, Self.Element>] = [:]
        for element in self {
            let key = try transform(element)
            if groups[key] == nil {
                groups[key] = Group(key)
            }
            
            groups[key]?.append(element)
        }
        
        return Array(groups.values)
    }
}

```

---

<div class="post-metadata">

**Author:** ![wowbagger](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wowbagger/32/13847_2.png) [@wowbagger](https://forums.swift.org/u/wowbagger)\
**Post date:** [June 17, 2020, 5:46pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/2 "2020-06-17T17:46:46Z")

</div>

I've been experimenting with something similar, with a different approach:

```swift
extension Sequence {
	/// Group the sequence by the given criterion.
	/// - Parameter criterion: The basis by which to group the sequence.
	/// - Throws: `Error`.
	/// - Returns: A dictionary consisting of the sequence grouped by the given criterion.
	func grouped<Group>(
		by criterion: (_ transforming: Element) throws -> Group
	) rethrows -> [Group: [Element]] {
		var groups: [Group: [Element]] = [:]
		for element in self {
			try groups[criterion(_: element), default: []].append(element)
		}
		return groups
	}
}

```

Although, something (impossible for now) like

```swift
func grouped<Group>(
	by key: (Element) throws -> Group
) rethrows -> OrderedDictionary<Group, Self>

```

will be even better imo.

---

<div class="post-metadata">

**Author:** ![jjatie](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jjatie/32/24428_2.png) [@jjatie](https://forums.swift.org/u/jjatie)\
**Post date:** [June 17, 2020, 5:59pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/4 "2020-06-17T17:59:34Z")

</div>

Why not just use [`Dictionary.init(grouping:by:)`](https://developer.apple.com/documentation/swift/dictionary/3127163-init)?

---

<div class="post-metadata">

**Author:** ![tomhamming](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tomhamming/32/12153_2.png) [@tomhamming](https://forums.swift.org/u/tomhamming)\
**Post date:** [June 17, 2020, 6:33pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/5 "2020-06-17T18:33:07Z")

</div>

Functionally, we could indeed accomplish the same thing with the `Dictionary` init method.

```swift
let dict = Dictionary(grouping: allContacts, by: {$0.lastName.prefix(1).uppercased()})
let keys = dict.keys.sorted() //these are the letters in the scrubber and the section headers

```

But I'd argue that it reads more naturally as a method on the collection itself.

---

<div class="post-metadata">

**Author:** ![tomhamming](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tomhamming/32/12153_2.png) [@tomhamming](https://forums.swift.org/u/tomhamming)\
**Post date:** [June 17, 2020, 6:34pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/6 "2020-06-17T18:34:04Z")

</div>

Yeah the [sorted collection](https://forums.swift.org/t/sorted-collection-protocol/11738) functionality would make this a bit nicer.

---

<div class="post-metadata">

**Author:** ![xwu](https://avatars.discourse-cdn.com/v4/letter/x/b487fb/32.png) [@xwu](https://forums.swift.org/u/xwu)\
**Post date:** [June 17, 2020, 7:17pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/7 "2020-06-17T19:17:04Z")

</div>

> [@tomhamming](#):
>
> Functionally, we could indeed accomplish the same thing with the `Dictionary` init method.
> 
> ```swift
> let dict = Dictionary(grouping: allContacts, by: {$0.lastName.prefix(1).uppercased()})
> let keys = dict.keys.sorted() //these are the letters in the scrubber and the section headers
> 
> ```
> 
> But I'd argue that it reads more naturally as a method on the collection itself.

As a general rule, the standard library does not offer multiple distinct spellings for the same functionality. Occasionally, two APIs which are meant to provide distinct functionality can converge to offer the same behavior in certain cases, but it's another thing to add an API which is duplicative of another.

In the Swift standard library, all conversions of a value from one type to another (which includes collections, such as converting an array to a set of unique elements) are spelled as initializers. This is an explicit guideline; that is why `Dictionary(grouping:by:)` is the API that is offered for this behavior.

You do propose here a way to get the grouped values in order; I would agree that an `OrderedDictionary` type would make this API fall out naturally. I see you've found the discussions about sorted collections :)

---

<div class="post-metadata">

**Author:** ![taylorswift](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/taylorswift/32/43413_2.png) [@taylorswift](https://forums.swift.org/u/taylorswift)\
**Post date:** [June 17, 2020, 7:32pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/8 "2020-06-17T19:32:29Z")

</div>

this isn’t exactly what the thread is proposing, but a related operation that would be really useful to have and isn’t covered well by `Dictionary.init(grouping:by:)` would be to have some inverse to `flatMap`, that groups elements of a sequence by run, but is still aware of the ordering of the sequence, like for example, lexing tokens:

```swift
// classify is some function that returns a character class
" abcd89ef 123_".grouped(by: classify(_:))
// returns 
[
    (.whitespace, [" "]), 
    (.letter, ["a", "b", "c", "d"]), 
    (.number, ["8", "9"]),
    (.letter, ["e", "f"]), 
    (.whitespace, [" "]), 
    (.number, ["1", "2", "3"]), 
    (.punctuation,["_"])
]

```

---

<div class="post-metadata">

**Author:** ![Nevin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nevin/32/617_2.png) [@Nevin](https://forums.swift.org/u/Nevin)\
**Post date:** [June 17, 2020, 7:51pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/9 "2020-06-17T19:51:02Z")

</div>

> [@taylorswift](#):
>
> this isn’t exactly what the thread is proposing, but a related operation that would be really useful to have and isn’t covered well by `Dictionary.init(grouping:by:)` would be to have some inverse to `flatMap` , that groups elements of a sequence by run, but is still aware of the ordering of the sequence, like for example, lexing tokens:

If you put it on `Collection` rather than `Sequence` the return type can simply be an array of `(Classification, Range<Index>)`, so it doesn’t need to allocate additional storage.

---

<div class="post-metadata">

**Author:** ![tomhamming](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tomhamming/32/12153_2.png) [@tomhamming](https://forums.swift.org/u/tomhamming)\
**Post date:** [June 17, 2020, 7:51pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/10 "2020-06-17T19:51:08Z")

</div>

> [@xwu](#):
>
> I would agree that an `OrderedDictionary` type would make this API fall out naturally.

I tend to want operations on my collections to read somewhat like sentences because of my early experiences with `IEnumerable` in .NET, but now that I think about it, an `OrderedDictionary` type would actually solve the use case I identified very nicely. It would automatically enforce order and key uniqueness.

I think there are still cases where returning an array of groups would be nice, like preserving the order of the occurrences of keys (with or without key uniqueness, as noted by @taylorswift), but whether that's worth an addition to the standard library is definitely up for debate.

---

<div class="post-metadata">

**Author:** ![wowbagger](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wowbagger/32/13847_2.png) [@wowbagger](https://forums.swift.org/u/wowbagger)\
**Post date:** [June 17, 2020, 8:21pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/11 "2020-06-17T20:21:44Z")

</div>

> [@xwu](#):
>
> In the Swift standard library, all conversions of a value from one type to another (which includes collections, such as converting an array to a set of unique elements) are spelled as initializers.

I think this is only true when the conversion is relatively lossless, e.g. `Double(42)` and `String(describing: [foo, bar])`. For conversions of `Sequence`s that require custom transformations, they use methods like `map` and `reduce` instead of initialisers.

---

<div class="post-metadata">

**Author:** ![wowbagger](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wowbagger/32/13847_2.png) [@wowbagger](https://forums.swift.org/u/wowbagger)\
**Post date:** [June 17, 2020, 8:24pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/12 "2020-06-17T20:24:49Z")

</div>

or perhaps even better with `RangeSet`?

---

<div class="post-metadata">

**Author:** ![xwu](https://avatars.discourse-cdn.com/v4/letter/x/b487fb/32.png) [@xwu](https://forums.swift.org/u/xwu)\
**Post date:** [June 17, 2020, 10:39pm UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/13 "2020-06-17T22:39:27Z")

</div>

> [@wowbagger](#):
>
> I think this is only true when the conversion is relatively lossless, e.g. `Double(42)` and `String(describing: [foo, bar])` . For conversions of `Sequence` s that require custom transformations, they use methods like `map` and `reduce` instead of initialisers.

They do not. Conversions that are lossless are unlabeled; others use labels. As you say, `map` and `reduce` arbitrarily transform elements in the general case, and their raison d'être isn't type conversion (to `Array`).

---

<div class="post-metadata">

**Author:** ![wowbagger](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wowbagger/32/13847_2.png) [@wowbagger](https://forums.swift.org/u/wowbagger)\
**Post date:** [June 18, 2020, 3:27am UTC](https://forums.swift.org/t/add-groupby-method-to-sequence/37680/14 "2020-06-18T03:27:03Z")

</div>

> [@xwu](#):
>
> their raison d'être isn't type conversion (to `Array` )

I think I agree with you here, but just to be sure, would you mind elaborating what their exact raison d'être is? Because the pitched `group`'s seem to be similar.
