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?

+3


source to share


4 answers


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.

+2


source


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])
    }
}

      

+1


source


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])

      

+1


source


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))
}

      

+1


source







All Articles