Swift sort an array of dictionaries by key, where value is optional AnyObject

I pull out an array of dictionaries directly from Parse and display them in a table. Therefore, I would really like to work with the data structure that I passed (these are strangely structured dictionaries below).

A PFObject is [String : AnyObject?] , And I want to be able to sort by any key, so I don’t know the type of object, and the key may not be in some dictionaries. Because in Parse, if you do not give a property a value, it simply does not exist. For instance:

 [ { "ObjectId" : "1", "Name" : "Frank", "Age" : 32 }, { "ObjectId" : "2", "Name" : "Bill" }, { "ObjectId" : "3", "Age" : 18 } { "ObjectId" : "4", "Name" : "Susan", "Age" : 47 } 

]

I want dictionaries with missing keys always to be ordered after sorted dictionaries. Example:

Original table:

 ObjectId Name Age 1 Frank 32 2 Bill 3 18 4 Susan 47 

Sorted by name:

 ObjectId Name Age 2 Bill 1 Frank 32 4 Susan 47 3 18 

Since I do not have much control over the data model, and its use is limited throughout the application, I would prefer to focus on the algorithmic solution rather than the structural one.

I came up with a way to do this, but it seems inefficient and slow, I'm sure someone can do it better.

 //dataModel is an array of dictionary objects used as my table source //sort mode is NSComparisonResult ascending or descending //propertyName is the dictionary key //first filter out any objects that dont have this key let filteredFirstHalf = dataModel.filter({ $0[propertyName] != nil }) let filteredSecondHalf = dataModel.filter({ $0[propertyName] == nil }) //sort the dictionaries that have the key let sortedAndFiltered = filteredFirstHalf { some1, some2 in if let one = some1[propertyName] as? NSDate, two = some2[propertyName] as? NSDate { return one.compare(two) == sortMode } else if let one = some1[propertyName] as? String, two = some2[propertyName] as? String { return one.compare(two) == sortMode } else if let one = some1[propertyName] as? NSNumber, two = some2[propertyName] as? NSNumber { return one.compare(two) == sortMode } else { fatalError("filteredFirstHalf shouldn't be here") } } //this will always put the blanks behind the sorted dataModel = sortedAndFiltered + filteredSecondHalf 

Thanks!

+6
source share
4 answers

Swift cannot compare any two objects. First you must direct them to a specific type:

 let arr: [[String: Any]] = [ ["Name" : "Frank", "Age" : 32], ["Name" : "Bill"], ["Age" : 18], ["Name" : "Susan", "Age" : 47] ] let key = "Name" // The key you want to sort by let result = arr.sort { switch ($0[key], $1[key]) { case (nil, nil), (_, nil): return true case (nil, _): return false case let (lhs as String, rhs as String): return lhs < rhs case let (lhs as Int, rhs as Int): return lhs < rhs // Add more for Double, Date, etc. default: return true } } print(result) 

If there are several dictionaries that do not matter for the specified key , they will be placed at the end of the result array, but their relative orders are not defined.

+6
source

Requirements

So you have an array of dictionaries.

 let dictionaries: [[String:AnyObject?]] = [ ["Name" : "Frank", "Age" : 32], ["Name" : "Bill"], ["Age" : 18], ["Name" : "Susan", "Age" : 47] ] 

You want to sort the array:

  • with Name value ascending
  • dictionaries without a Name String should be at the end

Decision

Here's the code (in the style of functional programming)

 let sorted = dictionaries.sort { left, right -> Bool in guard let rightKey = right["Name"] as? String else { return true } guard let leftKey = left["Name"] as? String else { return false } return leftKey < rightKey } 

Exit

 print(sorted) [ ["Name": Optional(Bill)], ["Name": Optional(Frank), "Age": Optional(32)], ["Name": Optional(Susan), "Age": Optional(47)], ["Age": Optional(18)] ] 
+4
source

Create a data type to represent your data:

 struct Person { let identifier: String let name: String? let age: Int? } 

Make the extraction procedure:

 func unpack(objects: [[String : Any]]) -> [Person] { return objects.flatMap { object in guard let identifier = object["ObjectID"] as? String else { // Invalid object return nil } let name = object["Name"] as? String let age = object["Age"] as? Int return Person(identifier: identifier, name: name, age: age) } } 

Your data type can be sorted by its fields, because they have real types.

 let objects: [[String : Any]] = [["ObjectID" : "1", "Name" : "Frank", "Age" : 32], ["ObjectID" : "2", "Name" : "Bill"], ["ObjectID" : "3", "Age" : 18], ["ObjectID" : "4", "Name" : "Susan", "Age" : 47]] let persons = unpack(objects) let byName = persons.sort { $0.name < $1.name } 

nil compare as "before" any other value; you can write your own comparator if you want to change this.

0
source

Here is what I will do. If you are able, I would make the structure more specific by giving it a name and age, not just a key and value. This should give you a diagram of how to achieve this, though!

 struct PersonInfo { var key: String! var value: AnyObject? init(key key: String, value: AnyObject?) { self.key = key self.value = value } } class ViewController: UIViewController { var possibleKeys: [String] = ["Name", "Age", "ObjectId"] var personInfos: [PersonInfo] = [] override func viewDidLoad() { super.viewDidLoad() for infos in json { for key in possibleKeys { if let value = infos[key] { personInfos.append(PersonInfo(key: key, value: value)) } } } personInfos.sortInPlace({$0.value as? Int > $1.value as? Int}) } } 

To make it easier:

 struct PersonInfo { var key: String! var objectId: Int! var name: String? var age: Int? init(key key: String, objectId: Int, name: String?, age: Int?) { self.key = key self.objectId = objectId self.name = name self.age = age } } class ViewController: UIViewController { var possibleKeys: [String] = ["Name", "Age", "ObjectId"] var personInfos: [PersonInfo] = [] override func viewDidLoad() { super.viewDidLoad() for infos in json { var objectId: String! var name: String? = nil var age: Int? = nil for key in possibleKeys { if let value = infos[key] { if key == "ObjectId" { objectId = value as? String } if key == "Name" { name = value as? String } if key == "Age" { age = value as? Int } } } personInfos.append(PersonInfo(key: key, objectId: objectId, name: String?, age: Int?)) } //by objectId personInfos.sortInPlace({$0.objectId? > $1.objectId?}) //by age personInfos.sortInPlace({$0.age? > $1.age?}) //byName personInfos.sortInPlace({$0.name?.compare($1.name?) == NSComparisonResult.OrderedAscending}) } } 
-one
source

All Articles