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!
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]
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
}
Related
I dont know how doint that. I try find replacement obj method
indexOfObject on the swift but not find
i have array = [1,3,6,2,3] and input in array value 3, i am need find repeating value with min index from array. Answer with index 1. How do it?
You have to loop through the entire collection, for each item, see if we had seen this before. And as we are doing that, let’s keep track of the index of the first item that has been repeated somewhere in the collection, to see if this is the first repeated item or not:
extension Collection where Element: Hashable {
func indexOfFirstRepeated() -> Index? {
var result: Index? // the index of the first item repeated anywhere else in the collection
var firstOccurrences: [Element: Index] = [:] // dictionary to keep track of the first item that every element was first encountered in the collection
for (index, element) in zip(indices, self) {
if let firstOccurrence = firstOccurrences[element] { // find previous occurrence of this value, if any
if let previousLowestIndex = result { // if we found this element before, let's see if we had already found another repeated element
if firstOccurrence < previousLowestIndex { // if so, let’s see if the first occurrence of this element occurred before the first occurrence of the previously discovered repeated element
result = firstOccurrence
}
} else { // otherwise, no prior repeated element found, so this is our first repeated element found thus far
result = firstOccurrence
}
} else {
firstOccurrences[element] = index // if we got here, this is the first time we've seen this element, so record the index of this first occurrence
}
}
return result
}
}
Thus:
let array = [9,8,7,1,3,6,2,3,1]
if let index = array.indexOfFirstRepeated() {
print(index) // 3
}
Now, obviously, as we iterate through this array, the value 3 is the first value that we will see repeated, but that doesn’t matter, because the repeated value 1 will be found at the very end of the array, and 1’s first index is lower than 3’s first index.
Two observations on the above:
I made it generic, so that it works on any hashable type, e.g.:
let array = ["bill", "sam", "susan", "sam", "bill"]
if let index = array.indexOfFirstRepeated() {
print(index)
} else {
print("not found")
}
I made this a Collection extension (rather than an Array extension) so that it would work on other collection types (e.g. array slices, etc.). You can make it an Array extension, just as easily, but we prefer to use the most abstract type that is convenient, to make it as flexible as possible.
This is basically a riff on uniqued.
import Algorithms
public extension BidirectionalCollection where Element: Hashable {
var firstDuplicate: (index: Index, element: Element)? {
var set: Set<Element> = []
return indexed().reversed().reduce(into: nil) {
if !set.insert($1.element).inserted {
$0 = $1
}
}
}
}
You can get the last duplicate by not reversing before reducing.
Assume you have an array of n integers, with a[i] = i, except that a[1] = a[2] = 1, and I may or may not have have changed a[i] = 0 for some i >= 2.
The smallest index of a duplicate element is either 0 or 1. To find out which you have to find the i >= 2 with a[i] = 0 or find that no such i exists. So you have to visit all array elements.
I am currently having a big issue sorting my Data alphabetically in a 2D array. I'm going to try to give you every detail to be as clear as possible.
Currently, I am fetching my contacts with the CNContactStore. This all works fine. I am able to retrieve all the data I want out of my contacts.
Now, I created the following struct:
struct FavoritableContact {
let contact: CNContact
var hasFavorited: Bool
}
With this, I declared and initialized the following array:
var favoritableContacts = [FavoritableContact]()
Once I retrieved my contacts, I simply appended them to favoritableContacts;
try store.enumerateContacts(with: request, usingBlock: { (contact, stopPointerIfYouWantToStopEnumerating) in
favoritableContacts.append(FavoritableContact(contact: contact, hasFavorited: false))
})
To sort them in alphabetical order in the same array, I simply did the following:
var sortedContacts = favoritableContacts.sorted { $0.contact.familyName < $1.contact.familyName }
Now if possible, I want to create the following 2D array,
var 2D = [
[FavoritableContact] //"A"
[FavoritableContact], //"B"
[FavoritableContact], //"C"
[FavoritableContact], //"D"
...
]
I am just not sure how to take my sortedContacts array and separate alphabetically.
I am very new here, If I forgot something, or I didn't do somethign right please let me know.
As was pointed out in the comments, a dictionary with first letters as keys is probably the better way to go as it is much easier to access, though perhaps you have a reason for wanting to use a 2d array instead. To achieve that you could do something like this:
//Create an empty array filled with 26 arrays of FavorableContact
var array2d = Array<[FavoritableContact]>(repeating: [FavoritableContact](), count: 26)
//Find the ascii value for "A" to use as your base
let aAscii = Int("A".unicodeScalars.filter({ $0.isASCII }).map({ $0.value })[0]) //This returns 65, btw, so you could also just hardcode
//Go through your original array, find the first letter of each contact, and append to the correct array
favoritableContacts.forEach { (contact) in
//Get the ascii value for the first letter
let firstLetter = Int(contact.contact.familyName.prefix(1).uppercased().unicodeScalars.filter({ $0.isASCII }).map({ $0.value })[0])
//Append to the array for this letter by subtracting the ascii value for "A" from the ascii value for the uppercased version of this letter.
array2d[firstLetter - aAscii].append(contact)
}
This is not the cleanest thing in the world, and it assumes standard English language alphabet with no diacritics, symbols, numbers or anything else. Assuming that is true it gets the job done.
Could use something like this.
var contactsLeftToSort : [FavoritableContact] = []
var doubleArray : [[FavoritableContact]?] = [[FavoritableContact]?]()
var index : Int = 0
for char in "ABCDEFGHIJKLMNOPQRSTUV" {
doubleArray.append(nil)
var i = 0
while i < contactsLeftToSort.count {
let contact = contactsLeftToSort[i]
if contact.name.first == char {
doubleArray[index] == nil ? doubleArray[index] = [contact] : doubleArray[index]!.append(contact)
contactsLeftToSort.remove(at: i)
}
//assuming original list is alphabetized.. if not, delete this line.
if contact.name.first! > char { break }
i += 1
}
index += 1
}
As I wrote in the comments above, I think you can achieve this in a much more elegant way by using a dictionary instead of an array.
SWIFT 4
let sortedContacts: [FavoritableContact] = ... // An array of FavoritableContact objects, they should be sorted
let groupedContacts = Dictionary(grouping: contacts, by { $0.familyName.first! })
You now have a dictionary of all your contacts where the keys are the alphabetical letters (ie. A-Z) and the values are arrays of sorted FavoritableContact objects (assuming you sorted the big array of FavoritableContacts before creating the dictionary).
If you wanted to use this as the datasource for your tableview, you would make the number of sections all the possible first letters of family names. For the number of rows in each section, you return the count of the array for the key like so:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
let letterForSection = letterForSection() // Custom method to get the section of the letter
return contactsDict[letterForSection].count
}
The rest of the datasource methods would work in a similar way.
Man, all of these answers are really over-complicating this. All you need is something along the lines of:
let groupedContacts = Dictionary(grouping: contacts, by: { $0.contact.firstName.first! })
for initial, contacts in groupedContacts.lazy.sorted().{ $0.key < $1.key} {
print("#################", initial)
contacts.forEach{ print($0) }
}
I am new here so please forgive me if I do not ask the right question. I am trying to create a function that will take array of strings ( medications) and then will tell if it belongs to certain categories by comparing it against other arrays. I am trying to achieve this with case switch method. But it is giving me error "can't form range upperBound
The code if have is :
//This is list of medications a patient may be on. This array will be generated by user input.
var medicationArray = ["metoprolol", "Dulera", "Atrovastatin", "Albuterol", "lasix", "Sprinolactone", "Lisnopril","Aspirin","Apixaban"]
//Function to compare medications above to arrays of different categories of medications.
func medDetails(medications : [String]) {
//Arrays of list of different types of mjedications
let betaBlockerList = ["metoprolol", "carvedilol", "propanolol"]
let anticoagulantList = ["warfarin", "Apixaban","rivroxaban"]
var otherMedicationList : String = ""
// For loop to loop thru different medications patient is on.
for medication in medications {
//switch function to take the medication name and then comparing it against different arrays.
switch medication {
//comparing medication against the range of of elements of first array.
case anticoagulantList[0]...anticoagulantList[anticoagulantList.count-1]:
print("Patinet is on \(medication) for anticoagultion")
//comparing medication against the range of of elements of second array.
case betaBlockerList[0]...betaBlockerList[betaBlockerList.count-1]:
print("Patient is on \(medication) for betablocker")
//list of medications that do not fit any of the above two categorias.
default:
otherMedicationList = medication + ", "
if medication == medications[medications.count - 1]{
print("Patients other medications inculde \(otherMedicationList) .")
}
}
}
}
medDetails(medications: medicationArray
let betaBlockerList = ["metoprolol", "carvedilol", "propanolol"]
Your switch case for "betaBlockerList" works fine. It is taking characters from "m" to "p" as parameters. Here these two values are in ascending order.
let anticoagulantList = ["warfarin", "Apixaban","rivroxaban"]
Your switch case for "anticoagulantList" is not working due to non ascending order of "(w)arfarin" and "(r)ivroxaban"
Switch cases here is not taking the whole strings as their parameters. Your betaBlockerList case is executing for all below values too
var medicationArray = ["metoprolol", "n", "o"]
I think a switch is not really a best practice here. A good approach would be to use a search function that searches or filters your array based on a given premise. But if you want to implement a more naive solution, simply do two for loops. One for the medicines and one for the other array that you are comparing against. Then add an if statement inside the loops, checking if the medicine is part of that list, if so, you have found your answer and you can break the loop at that point.
#Nirav already commented on the error, but the thing is that a switch might not be the best solution for your problem (what if you had 300 groups for example?)
So, here is a version that would require only a definition of the groups:
var medicationArray = ["metoprolol", "Dulera", "Atrovastatin", "Albuterol", "lasix", "Sprinolactone", "Lisnopril","Aspirin","Apixaban"]
func medDetails(medications: [String]) {
let input = Set(medications)
let betaBlockerList = Set(["metoprolol", "carvedilol", "propanolol"])
let anticoagulantList = Set(["warfarin", "Apixaban","rivroxaban"])
let groups = [
"betablocker": betaBlockerList,
"anticoagultion": anticoagulantList
]
// Get rid of any element from input that is present in groups
let unmatched = input.subtracting(groups.values.flatMap({$0}))
for medication in input {
for (groupName, groupValues) in groups {
if groupValues.contains(medication) {
print("Patient is on \(medication) for \(groupName)")
break
}
}
}
print("Patients other medications include: \(unmatched.joined(separator: ", "))")
}
Which when called like medDetails(medications: medicationArray) prints
Patient is on metoprolol for betablocker
Patient is on Apixaban for anticoagultion
Patients other medications include: Sprinolactone, Atrovastatin, Dulera, Albuterol, Aspirin, Lisnopril, lasix
I'm using childByAutoId() to generate my children. Each child looks like:
{
user_id: 1
}
I'd like to get the last 10 most recently added, sorted by time DESC. What's the easiest way to do this?
The answer is that you need to use a bit of reverse logic, and also store a timestamp key:value pair within each node as a negative value. I omitted the user_id: 1 to keep the answer cleaner.
Here's the Firebase structure
"test" : {
"-KFUR91fso4dEKnm3RIF" : {
"timestamp" : -1.46081635550362E12
},
"-KFUR9YH5QSCTRWEzZLr" : {
"timestamp" : -1.460816357590991E12
},
"-KFURA4H60DbQ1MbrFC1" : {
"timestamp" : -1.460816359767055E12
},
"-KFURAh15i-sWD47RFka" : {
"timestamp" : -1.460816362311195E12
},
"-KFURBHuE7Z5ZvkY9mlS" : {
"timestamp" : -1.460816364735218E12
}
}
and here's how that's written out to Firebase; I just used a IBAction for a button to write out a few nodes:
let testRef = self.myRootRef.childByAppendingPath("test")
let keyRef = testRef.childByAutoId()
let nodeRef = keyRef.childByAppendingPath("timestamp")
let t1 = Timestamp
nodeRef.setValue( 0 - t1) //note the negative value
and the code to read it in
let ref = self.myRootRef.childByAppendingPath("test")
ref.queryOrderedByChild("timestamp").queryLimitedToFirst(3).observeEventType(.ChildAdded, withBlock: { snapshot in
print("The key: \(snapshot.key)") //the key
})
and I declared a little function to return the current Timestamp
var Timestamp: NSTimeInterval {
return NSDate().timeIntervalSince1970 * 1000
}
and the output
The key: -KFURBHuE7Z5ZvkY9mlS
The key: -KFURAh15i-sWD47RFka
The key: -KFURA4H60DbQ1MbrFC1
As you can see, they are in reverse order.
Things to note:
Writing out your timestamp as negative values
When reading in use .queryLimitedToFirst instead of last.
On that note, you can also just read the data as usual and add it to an Array then then sort the array descending. That puts more effort on the client and if you have 10,000 nodes may not be a good solution.
I'm assuming your data actually looks like this:
someDataSet: {
longUID-1: {
timeCreated: 9999999999, // (seconds since the javascript epoch)
user_id: 1
},
longUID-2: {
timeCreated: 1111111111,
user_id: 2
},
longUID-3: {
timeCreated: 3141592653,
user_id: 3
}
}
You could automate that by calling Firebase.push({user_id: ###, timeCreated: ###}) multiple times in a for loop or any other method. Maybe you're adding news stories to a webpage, but you only want your user to see the most current stories--- IDK. But the answer to your question is to use Firebase's ref.orderByChild() and ref.limitToLast().
var ref = new Firebase("<YOUR-FIREBASE-URL>.firebaseio.com/someDataSet");
//the "/someDataSet" comes from the arbitrary name that I used up above
var sortedRef = ref.orderByChild('timeCreated');
//sort them by timeCreated, ascending
sortedRef.limitToLast(2).on("child_added", function(snapshot){
var data = snapshot.val();
console.log(data);
/* do something else with the data */
});
//The console would look like this
// Object {timeCreated: 9999999999, user_id: 1}
// Object {timeCreated: 3141592653, user_id: 3}
This happened because the program took the child with the greatest timeCreated value first and then the second greatest (value) second...
Also note, the longUID means nothing when you sort them by child and neither do the other values (user_id in this case)
Here is the documentation for:
Firebase .push() method (Sorry, I'm not allowed to post this link- I dont have enough reputation)
Firebase .orderByChild method
And also, Firebase .limitToLast method
The code: ref.queryOrderedByKey().queryLimitedToLast(10) can be used for getting the most recent 10 data. However, this is an ascending order by default.
Alternatively, you can order your data via
ref.orderByChild("id").on("child_added", function(snapshot) {
console.log(snapshot.key());
});
This also presents an ascending order by default. To change it into descending order is little bit tricky. What I would suggest it to multiply ids by -1 as shown below and then sort them.
var ref= new Firebase("your data");
ref.once("value", function(allDataSnapshot) {
allDataSnapshot.forEach(function(dataSnapshot) {
var updatedkey = -1 * dataSnapshot.key();
ref.update({ element: { id: updatedkey}});
});
});
This two SO page might be useful for you also, please check:
How to delete all but most recent X children in a Firebase node?
firebaseArray descending order?
I have an array of objects of type Thing:
class Thing: NSObject {
var data: String
var type: String
var created: NSDate
}
These things have an NSDate property called created. My aim is to write a function that reads the created property of every thing in the array and returns the thing that has the most recent date. The function looks like this:
public func getLastSwipe(list: Array<Thing>) -> Thing {
return someThing
}
Another approach is using Swift's .max, like this:
dates.max(by: <)
The following is my old answer. The above is updated in feb 2023.
let mostRecentDate = dates.max(by: {
$0.timeIntervalSinceReferenceDate < $1.timeIntervalSinceReferenceDate
})
This is the most performant solution I've found.
Returns the sequence’s most recent date if the sequence is not empty; otherwise, nil.
You could use reduce if you wanted. This will find the object with the highest timestamp.
var mostRecent = list.reduce(list[0], { $0.created.timeIntervalSince1970 > $1.created.timeIntervalSince1970 ? $0 : $1 } )
If your dates are not all in the past, you'll have to also compare against the current date to determine a cutoff. If your dates are all in the future, you'll want to switch the > to < to find the next future date (lowest timestamp).
You can sort the array, then find the first/last element. For example...
let objects: [Thing] = ... //Set the array
let mostResent = array.sorted { (firstThing, secondThing) -> Bool in
firstThing.created.timeIntervalSince1970 > secondThing.created.timeIntervalSince1970
}.first
This will return the most resent Thing as an Optional (because there is no guarantee that the array is not empty. If you know that the array is not empty, then you can end that line with .first!