Iterate array of dictionary and sort based on key - arrays

I have array of dictionary and the values are as follows:
[["-MXpvzmZdbqzrjND8w9F": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = hi;
timeStamp = 1617960107264;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}, "-MXq5NAyrkk4ZcvRFM7T": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = "how ru?";
timeStamp = 1617962828647;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}], ["-MXqa5-pkC28lY_Q_hpZ": {
lid = "-MWwEpHAhIdhN0i5sltB";
message = "hi nice cycle";
timeStamp = 1617971142820;
title = "Cycle for kids";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}]]
Here there are 2 elements in the array. I want to take the last element in both of the array and sort it based on the timestamp value.
How to do it? Please help me.

From comments, it seems that that the type of the array is:
typealias SomeDataArray = [[String: [String: Any]]]
So we're missing type safety from the beginning. The first thing I'd do is define a struct to represent Any in that definition. For now I'll just use a struct as a wrapper for the inner dictionary, using computed properties for all the fields:
struct SomeData
{
let dict: [String: Any]
var lid: String? { dict["lid"] as? String }
var message: String? { dict["message"] as? String }
var timeStamp: Int { dict["timeStamp"] as? Int ?? 0 }
var title: String? { dict["title"] as? String }
// Skipping user, because I have no idea what to make of its type.
}
Really this should be decoded into some real Swift type, but that's a topic for another day.
So now we redo the typealias
typealias SomeDataArray = [[String: SomeData]]
In chat it was explained what within the "last" element for each of these dictionaries can be defined as the one with the largest timeStamp value. Given that this will give this solution (retaining the keys)
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
If you want to transform it back the [[String;Any]] you get from Firebase, then it would be this:
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
.map { [$0.0: $0.1.dict as Any] }

Related

How can I merge 2 dictionaries into one array?

My JSON data look like this image below. Now I wanna merge the value of Shop Type and Promotion into one to use as collection view data. How can I do that?
I just filter the response data from the server like this:
var dataBanDau: [SDFilterModel] = []
var quickData: [SDFilterModel] = []
let filters: [SDFilterModel] = data
self.filterEntries = filters
//let nsarray = NSArray(array: self.filterEntries! , copyItems: true)
// self.filterEntriesStoreConstant = nsarray as! Array
self.dataBanDau = filters
for i in 0..<self.dataBanDau.count {
if self.dataBanDau[i].search_key.count == 0 {
self.quickData.append(self.dataBanDau[i])
}
}
self.quickData = self.quickData.filter {
$0.type != "range"
}
DispatchQueue.main.async {
//Note: Reload TableView
self.quickFilterCollection.reloadData()
completed(true)
}
}
the class SDFilterModel:
class SDFilterModel: DSBaseModel {
var name = String()
var type = String()
var is_expanded = Int()
var search_key = String()
var filterEntries : [SDFilterModel]?
override func copy(with zone: NSZone? = nil) -> Any {
// This is the reason why `init(_ model: GameModel)`
// must be required, because `GameModel` is not `final`.
let copy = SDFilterModel(dict: self.dictionary)
if let arrAttribute = NSArray(array: self.value , copyItems: true) as? [AttributeValueModel] {
copy.value = arrAttribute
}
return copy
}
override init(dict: Dictionary<String, Any>) {
super.init(dict: dict);
value = self.valueParse()
name = dict.getString(forKey: "name")
type = dict.getString(forKey: "type")
search_key = dict.getString(forKey: "search_key")
is_expanded = dict.getInt(forKey: "is_expanded")!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var value: [AttributeValueModel] = [];
func valueParse()-> [AttributeValueModel] {
guard let childs = (self.dictionary["value"]) as? [Dictionary<String, AnyObject>]
else { return [] }
var output: [AttributeValueModel] = [];
for aDict in childs {
let item = AttributeValueModel(dict:aDict);
// if type == .Range && item.option_id == "0" {
// item.setRangeOptionID(aValue: item.option_name!)
// }
//
output.append(item);
}
return output;
}
Let be Assume you have let myArray = [1,2,3,4,5,6,7,8]
Now you wanted to square of each and every element in the array,
With for loop you do like this
for item in myArray {
print(item * item)
}
Now assume item = $0
With for map you jus do
myArray.map({ $0 * $0 })
Both will gave same output.
map : Use to do same operation on every element of array.
flatmap : It is used to flattern the array of array.
let myArr = [[1,2,3],[4,5,6,7]]
and you want o/p as [1,2,3,4,5,6,7]
So can get above output with myArr.flatMap({$0})
Now back to your question.
let reqArray = myModel.data.map({ $0.value }).flatMap({ $0 })
First, map gaves you array-of-array of key value but you need a single array, so for that you need to use flatmap.
You can take ref : https://medium.com/#Dougly/higher-order-functions-in-swift-sorted-map-filter-reduce-dff60b5b6adf
Create the models like this
struct Option {
let name: String
let searchKey: String
let id: String
}
struct Model {
let type: String
let name: String
let isExpanded: Bool
let value: [Option]
}
You should get the options array values and join all the arrays
let models:[Model] = //...
let collectionViewArray = models.map { $0.value }.reduce([Option](), +)
Using for loop
var collectionViewArray = [Option]()
for model in models {
collectionViewArray.append(contentsOf: model.value)
}

Sorting an array of Dictionaries in swift and getting operator can't be applied to Any

I am trying to sort an array of Dictionaries by their timeStamp value from newest to oldest
error: Binary operator '<' cannot be applied to two 'Any?' operands
timeStamp value is saved as an Int into the Dictionary:
timeStamp = inviteDict["timeStamp"] as? Int
Dictionary:
[
"type": "invite",
"complimentId": key,
"status": status,
"timeStamp": timeStamp!,
"fromUser": user
]
Function - combines two arrays of tuples, I need to sort the array by timeStamp before passing via handler:
func getInvitesAndCompliments(forUserId forId: String, handler: #escaping ([[String : Any]], Bool) -> ()){
var invitesAndCompliments = [[String : Any]]()
var invites = [[String : Any]]()
var compliments = [[String : Any]]()
getComplimentsReceived(forUserId: forId) { (complimentsDict, success) in
invitesAndCompliments.removeAll()
compliments = complimentsDict
invitesAndCompliments = invites + compliments
handler(invitesAndCompliments, true)
}
getInvitesReceived(forUserId: forId) { (invitesDict, success) in
invitesAndCompliments.removeAll()
invites = invitesDict
invitesAndCompliments = compliments + invites
//sort array by timeStamp value
let sorted_invitesAndCompliments = invitesAndCompliments.sorted(by: { $0["timeStamp"] < $1["timeStamp"] })
handler(sorted_invitesAndCompliments, true)
}
}//end func
Update your code as follows:
let sorted_invitesAndCompliments = invitesAndCompliments.sorted { ($0["timeStamp"] as! Int) < ($1["timeStamp"] as! Int)}
I hope this will work for you.
The result of getting a value from a dictionary is always Any?, you have to downcast the types
let sorted_invitesAndCompliments = invitesAndCompliments.sorted(by: { ($0["timeStamp"] as! Int) < $1["timeStamp"] as! Int })
To avoid that use a custom struct.
I suggest don't use force cast it may be crash if value is empty. Check below code:
let sorted_invitesAndCompliments = invitesAndCompliments.sorted(by: {
guard let timestamp1 = $0["timeStamp"] as? Int else {return false}
guard let timestamp2 = $1["timeStamp"] as? Int else {return false}
return timestamp1 < timestamp2
})
As vadian suggested use custom struct in this case.

Firebase array became nil after loop in Swift

I'm trying to append element to array but it returns empty array.
What I'm trying to do is put elements that match up with tags user selected.
To achieve that, first I created model.
//Model
import UIKit
import Firebase
struct Team {
var key: String
var teamName: String
var league: String
var lat: Double
var lng: Double
init(snapshot: DataSnapshot) {
self.key = snapshot.key
self.teamName = (snapshot.value as! NSDictionary)["teamName"] as? String
self.league = (snapshot.value as! NSDictionary)["league"] as? String ?? ""
self.lat = (snapshot.value as! NSDictionary)["lat"] as? Double ?? 0
self.lng = (snapshot.value as! NSDictionary)["lng"] as? Double ?? 0
}
}
firebase structure looks like this.
{
"tags": {
"NewYork": {
uid1: 1
uid2: 1
uid3: 1
},
"baseball": {
uid1: 1
}
}
}
First, I fetched uids that match up with the tags. Then retrieve team's info based on uids that matched up with. If the same uid comes in like uid1 above, I want Xcode to print something. To do that I implemented like so(is there a better way?) but it's never been executed because results.count is always 0 even if after appended. However right after append method(I commented //here) it returns team's info properly.
func filterTeam(filterTag: FilteredTags) {
let tag = filterTag.selectedTag.components(separatedBy: "/") //this returns "NewYork" and "baseball"
var results = [Team]()
tag.forEach { (key) in
ref.child("tags").child(key).observe(.value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String:Any] else { return }
dictionary.forEach({ (uid,_) in
if results.count == 0 {
ref.child("teams").child(uid).observe(.value, with: { (snapshots) in
let team = Team(snapshot: snapshots)
results.append(team)
//here
print(results)
}, withCancel: nil)
} else {
for result in results {
if result.key == uid {
print("same uid!!")
} else {
ref.child("teams").child(uid).observe(.value, with: { (snapshots) in
let team = Team(snapshot: snapshots)
results.append(team)
}, withCancel: nil)
}
}
}
})
}, withCancel: nil)
}
}//func
How can I append properly and achieve what I want to do.
Thank you in advance!

Convert Array of Dictionaries into Array of Values from NSFetchRequest

I have a dictionary that is being returned from a NSFetchRequest using the fetchRequest.resultType = .dictionaryResultType the dictionary returned is [Any] and looks like the folowing:
teams [{
team = Canadiens;
}, {
team = "Maple Leafs";
}, {
team = Penguins;
}]
I would like just an array of the values, like this [Canadiens, "Maple Leafs", Penguins"], how can I convert the array of dictionaries into an array only containing the values?
Full fetch
func teamNames(managedContext: NSManagedObjectContext) {
//print("\(self) -> \(#function)")
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Players")
fetchRequest.fetchBatchSize = 8
fetchRequest.propertiesToGroupBy = [#keyPath(Players.team)]
fetchRequest.propertiesToFetch = [#keyPath(Players.team)]
fetchRequest.resultType = .dictionaryResultType
do {
let fetchTeamNamesDictionary = try managedContext.fetch(fetchRequest)
print("fetchTeamNamesDictionary \(fetchTeamNamesDictionary)")
} catch let error as NSError {
print("GoFetch|teamNames: Could not fetch. \(error), \(error.userInfo)")
}
}
Alternate to accepted answer:
do {
let fetchTeamNamesArray = try managedContext.fetch(fetchRequest)
for array in fetchTeamNamesArray {
let teamName = (array as AnyObject).value(forKey: "team") as! String
teamNameArray.append(teamName)
}
As you clearly know that the keys and values of the result are strings force-downcast the result to [[String:String]]
let teamArray = try managedContext.fetch(fetchRequest) as! [[String:String]]
Then map the dictionaries to its value for key team
let teamNames = teamArray.map { $0["team"]! }

Swift 1.2 Filter an Array of Structs by keyword

I need some help filtering an array of Structs.
This is what I am doing currently, it filters the array but not correctly.
For example lets say I search for an item in the array with "Mid" I have one item that should be shown however the item shown starts with "Bad".
var array = breweries.filter() { $0.name?.lowercaseString.rangeOfString(searchController.searchBar.text.lowercaseString) != nil }
results = array
here is my Struct
struct Breweries {
let name: String?
let breweryId: String?
let distance: Double?
let largeIconURL: String?
let streetAddress: String?
let locality: String?
let region: String?
let phone: String?
let website: String?
init(brewDictionary: [String: AnyObject]) {
name = brewDictionary["brewery"]?["name"] as? String
breweryId = brewDictionary["breweryId"] as? String
distance = brewDictionary["distance"] as? Double
largeIconURL = brewDictionary["brewery"]?["images"]??.objectForKey("large") as? String
streetAddress = brewDictionary["streetAddress"] as? String
locality = brewDictionary["locality"] as? String
region = brewDictionary["region"] as? String
phone = brewDictionary["phone"] as? String
website = brewDictionary["website"] as? String
}
}
Please point in the right direction!
Note: I am using Swift 1.2
Update:
I thought a video would be of help to better explain what I am trying to do.
Demo Of issue
What I want is to find the filter the array so only the item with a similar name is shown.
Update 2: As it turns out I forgot to handle the case when my UISearchController was active.
Assuming your Struct name is Breweries and it has a name property, try this:
let array = breweries.filter() {
($0.name!.lowercaseString as NSString).containsString(searchController.searchBar.text.lowercaseString)
}
Your usage of filter is correct, but your closure seem to be complicated with no clear goal. I suggest you to write an extension (or possibly use what I am using):
extension String {
func contains(search: String, ignoreCase: Bool = false, ignoreDiacritic: Bool = false) -> Bool {
var options = NSStringCompareOptions.allZeros
if ignoreCase { options |= NSStringCompareOptions.CaseInsensitiveSearch }
if ignoreDiacritic { options |= NSStringCompareOptions.DiacriticInsensitiveSearch }
return self.rangeOfString(search, options: options) != nil
}
}
This way you can use closure like this to search:
breweries.filter() {
$0.name?.contains("x") // Precise search
$0.name?.contains("x", ignoreCase: true, ignoreDiacritics: true) // Ignores diacritics and lower / upper case
}
of course, you can use | or & to search for multiple parameters
breweries.filter() {
$0.name?.contains("x") || $0.streetAddress?.contains("x")
}
Hope it helps!
Here is an example from an investing app with struct:
import Foundation
public struct SNStock {
public let ticker:NSString
public let name:NSString
init(ticker:NSString, name:NSString) {
self.ticker = ticker
self.name = name
}
}
Search on Main Thread:
public func searchStocksByKeyword(keyword:String) -> [SNStock] {
let lowercaseKeyword = keyword.lowercaseString
var searchResults:[SNStock] = []
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.ticker.lowercaseString.hasPrefix(lowercaseKeyword)
})
if (searchResults.count == 0) {
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.name.lowercaseString.hasPrefix(lowercaseKeyword)
})
}
searchResults.sortInPlace {
($0.ticker as String) < ($1.ticker as String)
}
return searchResults;
}
Search on Background Thread:
public func searchStocksByKeyword(keyword:String, completion:(stocks:[SNStock])->()) {
let qualityOfServiceClass = QOS_CLASS_USER_INTERACTIVE
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
let stocks:[SNStock] = self.searchStocksByKeyword(keyword)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(stocks: stocks)
})
})
}

Resources