0

Let's say that I am fetching messages (objects) from a database. They do not arrive at the same time, a message always arrives alone (one fetch -> one message; a function gets called for every message) and at any time.
Every message contains a time stamp that represents the date when the message was created. So I can call message.date and I will get the date when the message was created. The messages do not arrive in any order; it can be that the created last message is at the third/fourth/... position.

I'd like to group these messages by day in order to present them in a UITableView. Each section represents a day. Eeach section header includes the day and every cell includes the time (kind of like in WhatsApp Messenger).
I know how to create custom header views, insert sections, rows etc.
The problem is that I don't know how or as what data type to sort the messages in order to create the TableView easily and resource-saving and efficient (in terms of storage requirement and clarity). It would be easy if I had a two-dimensional array, but I am not clever enough to think up an efficient approach to sort (or rather group) the messages.

Thanks a lot for help!

j3141592653589793238
  • 1,810
  • 2
  • 16
  • 38

2 Answers2

1

The time stamp is a date, so sort by date (array) and group by day (dictionary).

  • Sorting an array that includes date information by date is a one-liner in Swift.
  • Grouping a sorted array that includes date information into a dictionary keyed by day is also a one-liner in Swift.

So that's two lines of code, and you didn't have to change anything.

However, a dictionary has no order, so I would then suggest taking a third step where you transform the dictionary into an array of some custom struct reflecting the section-row structure of your table. The correct data source for any sectioned table view is going to have a structure like this:

struct Row {
    // row properties
}
struct Section {
    var rowData : [Row]
    // section properties
}
var model : [Section]!

So after you've made your dictionary as a way of grouping, you just map it onto an array of Section and maintain that going forward.

Of course if you have no data to start with and the data arrives one item at a time, then you can omit the two bulleted steps above. Just start with the structured model and keep slotting each item into the right spot as it arrives.

EDIT: You expressed interest (in a comment) on how to insert an element into the right place in an already sorted array, so here's an example (see https://stackoverflow.com/a/26679191/341994):

extension Array {
    func insertionIndex(of elem: Element, by f: (Element, Element) -> Bool) -> Int {
        var lo = 0
        var hi = self.count - 1
        while lo <= hi {
            let mid = (lo + hi)/2
            if f(self[mid], elem) {
                lo = mid + 1
            } else if f(elem, self[mid]) {
                hi = mid - 1
            } else {
                return mid // found at position mid
            }
        }
        return lo // not found, would be inserted at position lo
    }
    mutating func insertSorted(_ elem:Element, by f: (Element, Element) -> Bool) {
        self.insert(elem, at:self.insertionIndex(of:elem, by:f))
    }
}

Here's a test; of course your ordering function won't be as simple as < but that's really the only difference:

var arr = [Int]()
arr.insertSorted(1, by:<)
arr.insertSorted(10, by:<)
arr.insertSorted(9, by:<)
arr.insertSorted(3, by:<)
arr.insertSorted(5, by:<)
arr.insertSorted(7, by:<)
arr.insertSorted(6, by:<)
// [1, 3, 5, 6, 7, 9, 10]
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Ok. Just to make it clear; I'd have to sort the messages array by date every time a new message arrives? – j3141592653589793238 Jul 31 '18 at 20:14
  • Mmmmm, not necessarily; if you're already sorted, you just slot the new message in where it belongs. – matt Jul 31 '18 at 20:15
  • That sounds great. How would I insert a new message? – j3141592653589793238 Jul 31 '18 at 20:15
  • There are well-known algorithms for quickly finding the right place to insert an item into a list of sorted items. That's the least of your worries. – matt Jul 31 '18 at 20:17
  • Just thinking about this.. I definitely have no data to start with. The third step seems very logic ;-) I think I can make it! Thanks, I'll just add another comment if questions arise. (And I'll also check your answer asa I have a solution). – j3141592653589793238 Jul 31 '18 at 20:26
  • Added an example of how to keep inserting into a sorted array. – matt Jul 31 '18 at 22:19
  • But what if there are messages with the same day? If I call `insertSorted()`, it won't be added to the section that already exists, but it will be added before. – j3141592653589793238 Aug 01 '18 at 16:33
  • 1
    Look at the structure I am suggesting you use. The Section struct has section properties. Your sections are days, so your Section will have a `day` property. So first you look to see if you have a Section whose `day` is this day. If there is, you insert the message into to its `rowData`. If there is not, you create a new Section and insert it in its proper place and add the message to its `rowData`. – matt Aug 01 '18 at 16:45
  • Yeah, you're the best. I made it ;-) Thanks a lot! That's a great answer! @matt – j3141592653589793238 Aug 01 '18 at 21:28
  • Hey @matt, some time later, I am now wondering how it's possible to add several rows at once (and updating the table view for every single row). May sound easy, but I am experiencing problems when simply storing the insertion index path in a variable and then calling tableView.insert(at:indexPath,:_) because there can be several index paths with the exact same row and section. I would have to update the index paths somehow afterwards, when I inserted every element into my array. – j3141592653589793238 Aug 07 '18 at 15:10
  • Yes, I saw your other question. :) The problem is that you are inserting sorted so you don't know _what row_ you just inserted into the model. Thus you have no way of telling the batch update that information when you try to modify the table. – matt Aug 07 '18 at 15:35
  • I do know that; my insertSorted function returns an `IndexPath` with a section and a row number. The problem is that when I add more and more messages, another message can get the same `IndexPath`. Then I got two similar IndexPaths, so `cellForRowAtIndexPath` will only get the last added message and this will result in an error because `numberOfRows inSection` looks up the number of rows from my model. But I have no idea how to fix that. I could call `index(of: ...)` in the end, when every element was inserted, but I absolutely like that solution. I hope that was understandable. – j3141592653589793238 Aug 07 '18 at 16:18
  • So when adding elements to my array, the `IndexPath` for the first added element can change when adding more elements before the first element (when the array grows). That's my problem. And then I have the same `IndexPath` several times. – j3141592653589793238 Aug 07 '18 at 16:19
0

It is very easy you can grouped it. for example messages contain these following:

struct message {
    let senderName:String
    let mess:String
    let reciever:String
    let time:Date
}

and you have some messages:

var messages = [message]()
        messages.append(message(senderName: "snow", mess: "Hello", reciever: "Dani", time: Date(timeIntervalSince1970: 1533078663)))
        messages.append(message(senderName: "john", mess: "Hello", reciever: "Dani", time: Date(timeIntervalSince1970: 1533078606)))
        messages.append(message(senderName: "alix", mess: "Hello", reciever: "Dani", time: Date(timeIntervalSince1970: 1533078633)))

you can grouped it easily by using this:

let groupedMessage = Dictionary(grouping: messages) { (mess) -> Date in
   return mess.time
}
Abdulameer
  • 34
  • 5