Hi Guys I briefly explain my problem.
From my database I get an array of HistoryItem, a custom type that contains a simple Date property inside it:
struct HistoryItem {
let date: Date
let status: Status // Not important for my problem
}
I want to group this data by year and month, I thought the best way was a dictionary with key DateComponents:
// ungroupedHistory is already fetched and is of type [HistoryItem]
var groupedHistory: [DateComponents : [HistoryItem]]
groupedHistory = Dictionary(grouping: ungroupedHistory) { (historyItem) -> DateComponents in
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month], from: hisoryItem.date)
return components
}
The result is as expected but the problem is that it is unsorted, and it is obvious that this is the case since the dictionary by definition is an unsorted collection.
How can i get a sorted by date copy of this dictionary?
I've tried something like this:
let sortedDict = groupedHistory.sorted {
$0.key.date.compare($1.key.date) == .orderedDescending
}
But I just get an array with keys of type:
[Dictionary<DateComponents, [HistoryItem]>.Element]
Thanks in advance!
You need to do this in 2 steps, first get an array with the dictionary keys sorted
let sortedKeys = groupedHistory.keys.sorted {
$0.year! == $1.year! ? $0.month! < $1.month! : $0.year! < $1.year!
}
and then use that array to access the values in your dictionary in a sorted manner
for key in sortedKeys {
print(groupedHistory[key])
}
A dictionary is unordered by definition.
To get a sorted array you could create a wrapper struct
struct History {
let components : DateComponents
let items : [HistoryItem]
}
then sort the keys and map those to an array of History
let sortedKeys = groupedHistory.keys.sorted{($0.year!, $0.month!) < ($1.year!, $1.month!)}
let history = sortedKeys.map{History(components: $0, items: groupedHistory[$0]!)}
Related
I'm looking to filter an array of arrays by specific value of one of the keys located within each array. Each nested array is read in from Firestore.
As an object, each nested array would look like this:
struct Video {
var url: String
var submissionDate: Timestamp
var submittingUser: String
}
I'm reading it in like this:
videos = document.get("videos") as? [[String : Any]] ?? nil
So far so good, but when I filter it like this:
filteredVideos = videos.filter { $0[2].contains(self.userIdentification) }
I can't do it without getting the error "Reference to member 'contains' cannot be resolved without a contextual type," an error which I was unable to find any relevant information on SO about.
I have read that some people say "Don't use arrays in Firestore!" but this is a build requirement.
Anyone have any ideas? Basically just need all arrays within the array where userId == submittingUser.
Reference Article:
I tried the answer from here: How to filter out a Array of Array's but no luck for this situation.
It's actually an array of dictionaries, not an array of arrays. All you need to do is construct the right predicate for the filter.
This is basically what your Firestore data looks like:
let videos: [[String: Any]] = [
["url": "http://www...", "submittingUser": "user1"],
["url": "http://www...", "submittingUser": "user2"]
]
This is the user you're looking for:
let userIdentification = "user2"
This is the predicate for the filer:
let filteredVideos = videos.filter { (video) -> Bool in
if let submittingUser = video["submittingUser"] as? String,
submittingUser == userIdentification {
return true
} else {
return false
}
}
You can shorthand this down to a single line if you're okay with force-unwrapping the dictionary (if you're 100% certain every video will have a valid submittingUser value):
let filteredVideos = videos.filter({ $0["submittingUser"] as! String == userIdentification })
I'm trying to query multiple objects from Realm using a List of Primary Key Strings. I know I can do this using a for..in loop but I'd rather use a filter if possible.
primaryKeyArray contains a number of Strings
class Item : Object {
#objc dynamic var itemKey = NSUUID().uuidString
}
var primaryKeyArray : List<String>?
//Assume Realm DB already contains multiple Item Objects
//primaryKeyArray contains "key1", "key2", "key3", etc..
let predicate = NSPredicate(format: "itemKey == %#", primaryKeyArray)
let items = realm.objects(Item.self).filter(predicate)
I know the problem is with my predicate format. Not sure whether to use some form of CONTAINS or what? Any help with the predicate syntax would be greatly appreciated!
I think you are asking how to query Realm for items that have keys that match a set of keys in an array.
So given a DogClass Realm Object
class DogClass: Object {
#objc dynamic var dog_id = NSUUID().uuidString
#objc dynamic var dog_name = ""
override static func primaryKey() -> String? {
return "dog_id"
}
}
and suppose we know we want to retrieve three dogs that match some given primary keys
let keysToMatch = ["302AC133-3980-41F3-95E8-D3E7F639B769", "54ECC485-4910-44E5-98B9-0712BB99783E", "71FE403B-30CD-4E6C-B88A-D6FDBB08C509"]
let dogResults = realm.objects(DogClass.self).filter("dog_id IN %#", keysToMatch)
for dog in dogResults {
print(dog.dog_id, dog.dog_name)
}
Note the use of IN in the filter, which will match any dogs with id's in the given array.
You can also pass in a Realm List Object instead of a Swift array and get the same result.
let listOfKeysToMatch = List<String>()
listOfKeysToMatch.append("302AC133-3980-41F3-95E8-D3E7F639B769")
listOfKeysToMatch.append("54ECC485-4910-44E5-98B9-0712BB99783E")
listOfKeysToMatch.append("71FE403B-30CD-4E6C-B88A-D6FDBB08C509")
let dogResults2 = realm.objects(DogClass.self).filter("dog_id in %#", listOfKeysToMatch)
for dog in dogResults2 {
print(dog.dog_id, dog.dog_name)
}
let predicate = NSPredicate(format: "itemKey IN %#", primaryKeyArray)
I want to sort an array in ascending order. The dates are in string format
Optional(["2019-07-08", "2019-07-09", "2019-07-10", "2019-07-11", "2019-07-12", "2019-07-02"])
I am trying using below code but it's not working.
aryEventTime = aryEventTime.sorted(by: { ($0 as AnyObject).date.compare($1.date) == ComparisonResult.orderedAscending })
aryEventTime = aryEventTime.sorted(by: {
($0 as AnyObject).date.compare(($1 as AnyObject).date) == .orderedDescending}) as? NSMutableArray
I would not advise making it a practice doing lexicographic sorting on dates. It's simple enough just to parse these strings to proper Date objects and sort on those:
let dateStrings = Optional(["2019-07-08", "2019-07-09", "2019-07-10", "2019-07-11", "2019-07-12", "2019-07-02"])
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let sortedDates = dateStrings?.compactMap(formatter.date(from:)).sorted()
I got the answer:
let sortedArray = aryEventTime.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedAscending }
The array of String that you have is,
let arr = ["2019-07-08", "2019-07-09", "2019-07-10", "2019-07-11", "2019-07-12", "2019-07-02"]
Instead of converting the String to Date, you can simply sort this array using sorted(), i.e.
let sortedArr = arr.sorted()
print(sortedArr)
Output:
["2019-07-02", "2019-07-08", "2019-07-09", "2019-07-10", "2019-07-11", "2019-07-12"]
Note: date strings can only be sorted without conversion in special cases such as this one. If the strings were in the format yyyy-dd-MM, for example, then simple string sorting would not work.
I am working on a firebase project and decided to make some changes in the data structure. Now, I decided it would be better to add a placeholder image to the node below (Before, I used a dummy image to serve as a placeholder). Is there a way I can still get the dates below? and add the placeholder URL to another structure?
So what I would want is an array dates = ["20180203","20180204","20180205"] and then another array containing placeholders = ["https.googleapis.whateverIsInTheDay80180203", "https.dummydefaultimage","https.dummydefaultimage"]
public func getAvailableDates(spotTitle:String, handler: #escaping (_ dateList:[String])->())
{
var datesList:[String] = []
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd"
let allowedDays = 30
ref.child("ImageLocationDates").child(spotTitle).observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for mydata in snapshot.children.allObjects as! [DataSnapshot]
{
let date = mydata.key
if date.count == 8 {
let testDate = formatter.date(from: date)
let cal = Calendar.current
let components = cal.dateComponents([.day], from: testDate!, to: cal.startOfDay(for: Date()))
let dateDistance = components.day! // integer of distance between today and date provided
if dateDistance < allowedDays {
if !datesList.contains(convertDate(stringDate: date)){
datesList.insert(convertDate(stringDate: date), at: 0)
}
else {
print("getting duplicate data")
}
}
}
}
handler(datesList)
}
})
}
In Firebase real time database you can have a few ways to structure you data. Keep in mind you want your data not to get too deep. Usually my limit is 3 folds not more. To keep things shallow then, you can normalize and distribute your data which includes a bit of duplication sometimes but it can speed up queries and will decrease the amount of data users download each time.
Look at three structures below :
(A)
D_street:
2012491203 :
placeholder: "https://...."
2012491203 :
placeholder: "https://...."
...
(B)
D_street_dates:
2012491203 : true
2012491203 :
...
D_street_placeholder:
2012491203 : "https://...."
2012491203 : "https://...."
...
(C)
D_street_dates:
id1 :
date: 2012491203
placeholder: "https://...."
id2 :
date: 2012491203
placeholder: "https://...."
...
Each one of structures above can be great depending on your use case. in A you can query by the date (key) and once you get the results you have actually downloaded the placeholders too because they are the values.
In B you only download date when you query D_street_dates and then you have to do another query to get the placeholder per each date from D_street_placeholder.
In C you structure data per an ID or userID or something that both date and placeholders are properties of and whenever you query ids you get both dates and placeholder for that.
I'm trying to figure out the best way in Swift to add values to an Array that is a Value in a Dictionary. I want to build a dictionary of contacts sorted by the first letter of their first name. For example [A : [Aaron, Adam, etc...], B : [Brian, Brittany, ect...], ...]
I found this function:
updateValue(_:forKey:)
And tried using it in a loop:
for contact in self.contacts.sorted() {
self.contactDictionary.updateValue([contact], forKey: String(describing: contact.characters.first))
}
But when I tried to use that it replaced the existing array with a new one. I know I can manually check to see if the key in the dictionary exists, if it does, retrieve the array and then append a new value, otherwise add the new key/value pair but I'm not sure if Swift provides an easier/better way to do this.
Any insight would be much appreciated!
You can use reduce(into:) method (Swift4) and as follow:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce(into: [String:[String]]()) { result, element in
// make sure there is at least one letter in your string else return
guard let first = element.first else { return }
// create a string with that initial
let initial = String(first)
// initialize an array with one element or add another element to the existing value
result[initial] = (result[initial] ?? []) + [element]
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
If you are using Swift3 or earlier you would need to create a mutable result dictionary inside the closure:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce([String:[String]]()) { result, element in
var result = result
guard let first = element.first else { return result }
let initial = String(first)
result[initial] = (result[initial] ?? []) + [element]
return result
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
Note that the result is not sorted. A dictionary is an unordered collection. If you need to sort your dictionary and return an array of (key, Value) tuples you can use sorted by key as follow:
let sorted = dictionary.sorted {$0.key < $1.key}
print(sorted)
"[(key: "A", value: ["Aaron", "Adam"]), (key: "B", value: ["Brian", "Brittany"])]\n"
Swift 4's new dictionary initializers can do it all for you:
let contactInitials = contacts.filter{!$0.isEmpty}.map{ ($0.first!,[$0]) }
let dict = [Character:[String]](contactInitials, uniquingKeysWith:+)