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 ForEach
es 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
Detect Tap on a Button in Uitableviewcell for Uitableview Containing Multiple Sections
Ansible Regex_Findall Multiple Strings
React Native App Works on Debug Mode, But Not Works Release Mode on Ios
Uistackview Hiding/Unhiding Arrangedsubview Issue
Open Facebook Page in Native Facebook iOS App Via Simple Url Sheme Issue
Remove Alpha Channel in an Image
Swift - Integer Conversion to Hours/Minutes/Seconds
Cell Size Not Updating After Changing Flow Layout'S Itemsize
Nsattributedstring Click Event in Uilabel Using Swift
How to Programmatically Scroll Through a Collection View
Passing Data Between View Controllers
Using Auto Layout in Uitableview For Dynamic Cell Layouts & Variable Row Heights
What Are Unwind Segues For and How to Use Them
How to Load Image from Local Path iOS Swift (By Path)
Take Screenshots in the iOS Simulator
Why Is the Tab Bar Disappearing
Airpods Not Working as an Input Source for Voice Recorder App
How to Have Stored Properties in Swift, the Same Way I Had on Objective-C