2

I have an array of custom model:

struct Event {
    var day: Int // let's assume its Int for now
    var title: String
}

as:

let array = [Event(day: 1, title: "Pizza Party!"),
             Event(day: 1, title: "Another Pizza Party"),
             Event(day: 2, title: "Cinema - Moive 01"),
             Event(day: 2, title: "Cinema - Moive 02")]

I want to transform array to be a 2 dimensional array, each array should contains events that have the same day; According to array, the result should be:

[
    [Event(day: 1, title: "Pizza Party!"), Event(day: 1, title: "Another Pizza Party")]
    [Event(day: 2, title: "Cinema - Moive 01"), Event(day: 2, title: "Cinema - Moive 02")]
]

The first array -in the outer 2d array- contains events for day 1 and the second one contains events for day 2.

Is there a way to get the above result using the reduce(into:_:) method?


Despite of wanting to do it using reduce(into:_:), I was able to achieve by implementing:

func transfrom(_ models: [Event]) -> [[Event]] {
    let uniqueDates = Set(array.map { $0.day }).sorted()
    var twoDArray = [[Event]]()

    for date in uniqueDates {
        var array = [Event]()
        for model in models {
            if date == model.day {
                array.append(model)
            }
        }

        twoDArray.append(array)
    }

    return twoDArray
}

let transfomredArray = transfrom(array) // wanted result
Ahmad F
  • 30,560
  • 17
  • 97
  • 143
  • Why `reduce(into:_:)` ? Something like `Array(Dictionary(grouping: array, by: { $0.day }).values)` should do the trick. – Martin R Nov 05 '18 at 14:27
  • What makes you think that `reduce(into:-:)` is suitable for the task? I mean, is it really a requirement? – Alladinian Nov 05 '18 at 14:33
  • You can't do what you want using `reduce(into:_:)` Reduce reduces all the elements of an array into a single value. D V's answer of using `dictionary(grouping:)` should work. – Duncan C Nov 05 '18 at 14:38
  • Related (duplicate?): https://stackoverflow.com/questions/31220002/how-to-group-by-the-elements-of-an-array-in-swift – Martin R Nov 05 '18 at 14:49
  • @DuncanC I beg to differ – ielyamani Nov 05 '18 at 15:01
  • Thanks @MartinR. Do think that it should be closed as duplicated with the one you mentioned? – Ahmad F Nov 05 '18 at 15:39
  • @Carpsen90, I guess you could write a reduce() closure that would build the array-of-arrays as a side-effect, and the output of the reduce would be discarded, but that seems like poor fit to me. (So my "you can't do that" was too strong, in the same way that you can use a wrench to pound in a nail if you really want to.) – Duncan C Nov 05 '18 at 15:54

3 Answers3

4

You can use the grouping functionality of Dictionary to achieve your requirement:

// This dictionary will have all your events grouped based on the day it belongs to.
let dict = Dictionary(grouping: array) { (element) -> Int in
    return element.day
}

// If you need a sorted list of events based on the day.
let groupedItems = dict.sorted {
    return $0.key < $1.key
}

// groupedItems would now have all the data, grouped and ready to be used.
for day in groupedItems {
    print("\(day.key): \(day.value.count)")
}
D V
  • 348
  • 2
  • 10
0

If you still want reduce(into:):

let result: [[Event]] = array.sorted { $0.day < $1.day }
    .reduce(into: [[Event]]())
    { tempo, event in
        if let lastArray = tempo.last {
            if lastArray.first?.day == event.day {
                let head: [[Event]] = Array(tempo.dropLast())
                let tail: [[Event]] = [lastArray + [event]]
                tempo = head + tail
            }
            else {
                tempo.append([event])
            }
        } else {
            tempo = [[event]]
        }
}
ielyamani
  • 17,807
  • 10
  • 55
  • 90
0

To do it in simpler way, you can use @MartinR's comment:

let resultArray = Dictionary(grouping: array, by: { $0.day }).values

But there might be some extra operations you want to perform over it, lets say sorting. Then you can use following approach:

  1. Group the data, based on day parameter. Result would be [Int: [Event]]

    let grouped = Dictionary(grouping: array, by: { $0.day })
    
  2. Sort the grouped data on basis of key. result would be [Int: [Event]]

    let sorted = grouped.sorted(by: { $0.key < $1.key })
    
  3. You can use reduce to get output in desired format, note that simply applying reduce will flatten the array so use { $0 + [$1.value] } instead.

    let resultArray: [[Event]] = sorted.reduce([]) { $0 + [$1.value] }
    
Ankit Jayaswal
  • 5,549
  • 3
  • 16
  • 36