Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group and sort an array in Swift

Let's say that 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's a way to get an array with an 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 "name" descendant. @oisdk can you please check this out??

like image 444
Ruben Avatar asked Mar 16 '23 11:03

Ruben


2 Answers

This might seem like overkill but it's the solution that entered my mind.

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 is contains all the statEvents of the dinner type, the next list contains the events of the lunch type, etc.

The obvious disadvantage is that this is probably less performant than the other solution. The nice part is that you don't have to rely on parallel arrays in order to get the hours that are associated a particular date.

like image 127
Rikki Gibson Avatar answered Mar 25 '23 11:03

Rikki Gibson


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])
like image 42
Mario Zannone Avatar answered Mar 25 '23 09:03

Mario Zannone