How to Group by the Elements of an Array in Swift

How to group by the elements of an array in Swift

Swift 4:

Since Swift 4, this functionality has been added to the standard library. You can use it like so:

Dictionary(grouping: statEvents, by: { $0.name })
[
"dinner": [
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1)
],
"lunch": [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "lunch", date: "01-01-2015", hours: 1)
]

Swift 3:

public extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
var categories: [U: [Iterator.Element]] = [:]
for element in self {
let key = key(element)
if case nil = categories[key]?.append(element) {
categories[key] = [element]
}
}
return categories
}
}

Unfortunately, the append function above copies the underlying array, instead of mutating it in place, which would be preferable. This causes a pretty big slowdown. You can get around the problem by using a reference type wrapper:

class Box<A> {
var value: A
init(_ val: A) {
self.value = val
}
}

public extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U:[Iterator.Element]] {
var categories: [U: Box<[Iterator.Element]>] = [:]
for element in self {
let key = key(element)
if case nil = categories[key]?.value.append(element) {
categories[key] = Box([element])
}
}
var result: [U: [Iterator.Element]] = Dictionary(minimumCapacity: categories.count)
for (key,val) in categories {
result[key] = val.value
}
return result
}
}

Even though you traverse the final dictionary twice, this version is still faster than the original in most cases.

Swift 2:

public extension SequenceType {

/// Categorises elements of self into a dictionary, with the keys given by keyFunc

func categorise<U : Hashable>(@noescape keyFunc: Generator.Element -> U) -> [U:[Generator.Element]] {
var dict: [U:[Generator.Element]] = [:]
for el in self {
let key = keyFunc(el)
if case nil = dict[key]?.append(el) { dict[key] = [el] }
}
return dict
}
}

In your case, you could have the "keys" returned by keyFunc be the names:

currentStat.statEvents.categorise { $0.name }
[
dinner: [
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "01-01-2015", hours: 1)
], lunch: [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "lunch", date: "01-01-2015", hours: 1)
]
]

So you'll get a dictionary, where every key is a name, and every value is an array of the StatEvents with that name.

Swift 1

func categorise<S : SequenceType, U : Hashable>(seq: S, @noescape keyFunc: S.Generator.Element -> U) -> [U:[S.Generator.Element]] {
var dict: [U:[S.Generator.Element]] = [:]
for el in seq {
let key = keyFunc(el)
dict[key] = (dict[key] ?? []) + [el]
}
return dict
}

categorise(currentStat.statEvents) { $0.name }

Which gives the output:

extension StatEvents : Printable {
var description: String {
return "\(self.name): \(self.date)"
}
}
print(categorise(currentStat.statEvents) { $0.name })
[
dinner: [
dinner: 01-01-2015,
dinner: 01-01-2015,
dinner: 01-01-2015
], lunch: [
lunch: 01-01-2015,
lunch: 01-01-2015
]
]

(The swiftstub is here)

Swift - Group elements of Array by value (2 by 2, 3 by 3, etc...)

Here is my version where I filter on a range based on first value of the array and the coef variable, based on the result I slice away those elements already counted and filter again on the smaller array in a loop. This solution requires the input array to be sorted in ascending order

func group(_ array: [Int], coef: Int) -> [Int: Int] {
var result:[Int:Int] = [:]

var start = array[0]
var end = start + coef - 1
var arr = array

while start <= array[array.count - 1] {
let count = arr.filter({ $0 >= start && $0 <= end}).count

result[start] = count
start = end + 1
end = start + coef - 1
arr = Array(arr[count...])
}
return result
}

And here is a recursive version of the above function

func group(_ array: [Int], coef: Int) -> [Int: Int] {
var result:[Int:Int] = [:]
if array.isEmpty { return result }

let end = array[0] + coef - 1
let count = array.filter({ $0 >= array[0] && $0 <= end}).count
result[array[0]] = count
result = result.merging(group(Array(array[count...]), coef: coef)) { $1 }
return result
}

How to group an array of objects by name and sum

Just make sure your collection is sorted by name and use reduce to accumulate the count value:

let grouped: [Test] = tests.sorted(by: {$0.name < $1.name}).reduce(into: []){
if let last = $0.last, last.name == $1.name,
last.innerNumber == $1.innerNumber {
$0[$0.indices.last!] = .init(count: last.count + $1.count, name: last.name, innerNumber: last.innerNumber)
} else {
$0.append($1)
}
}

How to group Arrays

You can extend Collection, constrain its Element to Equatable protocol and create a computed property to return the grouped elements using reduce(into:) method. You just need to check if there is a last element on the last collection equal to the current element and append the current element to the last collection if true otherwise append a new collection with it:

extension Collection where Element: Equatable {
var grouped: [[Element]] {
return reduce(into: []) {
$0.last?.last == $1 ?
$0[$0.index(before: $0.endIndex)].append($1) :
$0.append([$1])
}
}
}

let array = ["1","1","1","2","2","1","1"]
let grouped = array.grouped
print(grouped) // "[["1", "1", "1"], ["2", "2"], ["1", "1"]]\n"

How to group items from array that match, into another array?

Use Dictionary initializer init(grouping:by:)
Then just get arrays by accessing values property.

Example:

let dic = Dictionary(grouping: someArray) { $0 }
let values = Array(dic.values)
print(values)

Result:

[["2"], ["1", "1"]]

how to group by swift array?

You should create a data structure that represents the structure of your view first: try transforming 1...self.getDays() into a 2D array, with each inner array being one row of your "grid". For example, if self.getDays were 10, the 2D array would look like:

[
[1,2,3,4,5,6,7],
[8,9,10]
]

After that, we can use two ForEaches to "loop through" (We aren't actually looping through anything. ForEach is not really a loop...) the 2D array and create the view.

To create the 2D array, you can use one of the ways from this answer.

And then you can do:

VStack(alignment: .center, spacing: 10) {
// "chunked" does the aforementioned transformation
ForEach(Array(1...self.getDays()).chunked(by: 7), id: \.self) { row in
HStack(alignment: .center, spacing: 10) {
ForEach(row, id: \.self) { day in
DayView(dayNumber: day)
}
}
}
}

How to group Array of Dictionaries by a key in swift?

Swift has a nice function that does this for you...

let people = [["Country":"Egypt","Name":"Mustafa","Age":"20"],["Country":"Palestine","Name":"Omar","Age":"15"],["Country":"Egypt","Name":"Ali","Age":"40"],["Country":"Jordan","Name":"Ahmad","Age":"25"],["Country":"Palestine","Name":"Amani","Age":"30"],["Country":"Jordan","Name":"Mustafa","Age":"20"]]

let peopleByCountry = Dictionary(grouping: people, by: { $0["Country"]! } )

peopleByCountry will now be the format that you want.

You can read more about this function in the documentation.

Just to add to Hamish's comment.

You really shouldn't be working with Dictionaries here. You should be working with Structs...

struct Person {
let countryName: String
let name: String
let age: Int
}

Even better would be to have a Country struct...

struct Country {
let name: String
}

and use that in the Person for their country property instead of String.



Related Topics



Leave a reply



Submit