Rapheal's solution really works. However, I would suggest changing the decision to support the assertion that the grouping is actually stable.
As of now, a call to grouped() will return a grouped array, but subsequent calls can return an array with groups in a different order, although the elements of each group will be in the expected order.
internal protocol Groupable { associatedtype GroupingType : Hashable var groupingKey : GroupingType? { get } } extension Array where Element : Groupable { typealias GroupingType = Element.GroupingType func grouped(nilsAsSingleGroup: Bool = false) -> [[Element]] { var groups = [Int : [Element]]() var groupsOrder = [Int]() let nilGroupingKey = UUID().uuidString.hashValue var nilGroup = [Element]() for element in self { // If it has a grouping key then use it. Otherwise, conditionally make one based on if nils get put in the same bucket or not var groupingKey = element.groupingKey?.hashValue ?? UUID().uuidString.hashValue if nilsAsSingleGroup, element.groupingKey == nil { groupingKey = nilGroupingKey } // Group nils together if nilsAsSingleGroup, element.groupingKey == nil { nilGroup.append(element) continue } // Place the element in the right bucket if let _ = groups[groupingKey] { groups[groupingKey]!.append(element) } else { // New key, track it groups[groupingKey] = [element] groupsOrder.append(groupingKey) } } // Build our array of arrays from the dictionary of buckets var grouped = groupsOrder.flatMap{ groups[$0] } if nilsAsSingleGroup, !nilGroup.isEmpty { grouped.append(nilGroup) } return grouped } }
Now that we are tracking the order in which new groupings are opened, we can return the grouped array more consistently than simply relying on the unordered values property.
struct GroupableInt: Groupable { typealias GroupingType = Int var grouping: Int? var content: String } var a = [GroupableInt(groupingKey: 1, value: "test1"), GroupableInt(groupingKey: 2, value: "test2"), GroupableInt(groupingKey: 2, value: "test3"), GroupableInt(groupingKey: nil, value: "test4"), GroupableInt(groupingKey: 3, value: "test5"), GroupableInt(groupingKey: 3, value: "test6"), GroupableInt(groupingKey: nil, value: "test7")] print(a.grouped()) // > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")],[GroupableInt(groupingKey: nil, value: "test7")]] print(a.grouped(nilsAsSingleGroup: true)) // > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4"),GroupableInt(groupingKey: nil, value: "test7")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")]]