Group and sort array in Swift
Let's say I have this code:
class Stat {
var statEvents : [StatEvents] = []
}
struct StatEvents {
var name: String
var date: String
var hours: Int
}
var currentStat = Stat()
currentStat.statEvents = [
StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
StatEvents(name: "dinner", date: "02-01-2015", hours: 2),
StatEvents(name: "dinner", date: "03-01-2015", hours: 3),
StatEvents(name: "lunch", date: "04-01-2015", hours: 4),
StatEvents(name: "dinner", date: "05-01-2015", hours: 5),
StatEvents(name: "breakfast", date: "06-01-2015", hours: 6),
StatEvents(name: "lunch", date: "07-01-2015", hours: 7),
StatEvents(name: "breakfast", date: "08-01-2015", hours: 8)
]
I would like to know if there is a way to get an array with output like this:
- [0]
- name : "lunch"
- date
- [0] : "01-01-2015"
- [1] : "04-01-2015"
- [2] : "07-01-2015"
- hours
- [0] : 1
- [1] : 4
- [2] : 7
- [1]
- name : "dinner"
- date
- [0] : "02-01-2015"
- [1] : "03-01-2015"
- [2] : "05-01-2015"
- hours
- [0] : 2
- [1] : 3
- [2] : 5
- [2]
- name : "breakfast"
- date
- [0] : "06-01-2015"
- [1] : "08-01-2015"
- hours
- [0] : 6
- [1] : 8
As you can see, the final array should be grouped by the name "name". @oisdk can you check this?
source to share
It may seem like overkill, but this is the solution that went into my head.
extension Array {
/**
Indicates whether there are any elements in self that satisfy the predicate.
If no predicate is supplied, indicates whether there are any elements in self.
*/
func any(predicate: T -> Bool = { t in true }) -> Bool {
for element in self {
if predicate(element) {
return true
}
}
return false
}
/**
Takes an equality comparer and returns a new array containing all the distinct elements.
*/
func distinct(comparer: (T, T) -> Bool) -> [T] {
var result = [T]()
for t in self {
// if there are no elements in the result set equal to this element, add it
if !result.any(predicate: { comparer($0, t) }) {
result.append(t)
}
}
return result
}
}
let result = currentStat.statEvents
.map({ $0.name })
.distinct(==)
.sorted(>)
.map({ name in currentStat.statEvents.filter({ $0.name == name }) })
Now you have a list of lists where the first list contains all the statEvents of the dinner type, the next list contains the events of the lunch type, etc.
The obvious downside is that this is probably less efficient than the other solution. The nice part is that you don't have to rely on parallel arrays to get the clock associated with a specific date.
source to share
The end result is either type [[String: AnyObject]], or you create a new structtype that contains these values, and the result is of type [String: NewStructType]:
struct NewStructType
{
var dates: [String]?
var hours: [Int]?
}
So, you need to solve this, and then you have to write your own function to sort and group StatEvents objects. Maybe you can optimize your performance, but here is the first idea on how to implement the second version (using NewStructType):
var result = [String : NewStructType]()
for statEvent in currentStat.statEvents
{
if (result[statEvent.name] != nil)
{
var newStructType = result[statEvent.name]!
newStructType.dates.append(statEvent.date)
newStructType.hours.append(statEvent.hours)
}
else
{
result[statEvent.name] = NewStructType(dates: [statEvent.date], hours: [statEvent.hours])
}
}
source to share
My take:
extension StatEvents : Comparable {}
func < (lhs:StatEvents, rhs:StatEvents) -> Bool {
if lhs.name != rhs.name {
return lhs.name > rhs.name
} else if lhs.date != rhs.date {
return lhs.date < rhs.date
} else {
return lhs.hours < rhs.hours
}
}
func == (lhs:StatEvents, rhs:StatEvents) -> Bool {
return lhs.name == rhs.name
&& lhs.date == rhs.date
&& lhs.hours == rhs.hours
}
struct ResultRow {
var name: String
var dates: [String]
var hours: [Int]
}
var result : [ResultRow] = []
let sorted = currentStat.statEvents.sort()
for event in sorted {
if result.last?.name != event.name {
result.append(ResultRow(name: event.name, dates: [], hours: []))
}
result[result.endIndex - 1].dates.append(event.date)
result[result.endIndex - 1].hours.append(event.hours)
}
Test:
for r in result { print(r) }
prints:
p.ResultRow(name: "lunch", dates: ["01-01-2015", "04-01-2015", "07-01-2015"], hours: [1, 4, 7])
p.ResultRow(name: "dinner", dates: ["02-01-2015", "03-01-2015", "05-01-2015"], hours: [2, 3, 5])
p.ResultRow(name: "breakfast", dates: ["06-01-2015", "08-01-2015"], hours: [6, 8])
source to share
There are already answers, but damn it's fun. My answer doesn't use many higher order functions in Swift, but it gets the job done nonetheless:
// Get the list of unique event names
var eventNames = [String]()
for event in currentStat.statEvents {
if !eventNames.contains(event.name) {
eventNames.append(event.name)
}
}
// The type of the result
struct ResultType {
var name : String
var date : [String]
var hours : [Int]
}
var result = [ResultType]()
for name in eventNames {
let matchingEvents = currentStat.statEvents.filter { $0.name == name }
let dates = matchingEvents.map { $0.date }
let hours = matchingEvents.map { $0.hours }
result.append(ResultType(name: name, date: dates, hours: hours))
}
source to share