5

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??

4 Answers 4

2

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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

Comments

2

There are some answers already but what the heck, this is fun. My answer doesn't use a lot of the higher-order functions in Swift, but it gets the job done nevertheless:

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

Comments

1

The final result is either of type [[String : AnyObject]] or you create a new structtype that holds these values and the result is of type [String : NewStructType]:

struct NewStructType
{ 
    var dates: [String]?
    var hours: [Int]?
}

So you have to decide on that and then you have to write your own function to sort and group your StatEvents-objects. Maybe you can optimize its performance, but here's a first idea how to implement the second version (with 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 Comment

No need to make the date and hours optional in the struct definition. It can also have an initializer that takes a dates [String] and an hours [Int]. Also, if this is a one-off usage, the type can simply be '[String : (dates: [String], hours: [Int])]'. If you are passing this value around to callers outside the local context or the class, having the structure will be good, though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.