Swift sort array de dictionarys por key donde el valor es opcional AnyObject

Estoy sacando una variedad de dictionarys directamente de Parse y mostrándolos en una tabla. Así que realmente me gustaría trabajar con la estructura de datos que tengo (los dictionarys extrañamente estructurados a continuación).

Un object PFObject es [String : AnyObject?] Y quiero poder orderar por cualquier tecla, por lo que no conozco el tipo de object Y la key podría faltar en algunos de los dictionarys. Porque en Parse, si no le das un valor a una propiedad, simplemente no existe. Por ejemplo:

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

]

Quiero que los dictionarys con keys perdidas siempre se orderen después de los dictionarys orderados. Un ejemplo:

Tabla original:

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

Pedido por nombre:

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

Como no tengo mucho control sobre el model de datos, y su uso es limitado en toda la aplicación, prefiero enfocarme en una solución algorítmica en lugar de estructural.

Se me ocurrió una manera de hacer esto, pero parece ineficiente y lento, estoy seguro de que hay alguien que puede hacer esto mejor.

 //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 filtenetworkingFirstHalf = dataModel.filter({ $0[propertyName] != nil }) let filtenetworkingSecondHalf = dataModel.filter({ $0[propertyName] == nil }) //sort the dictionaries that have the key let sortedAndFiltenetworking = filtenetworkingFirstHalf { 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("filtenetworkingFirstHalf shouldn't be here") } } //this will always put the blanks behind the sorted dataModel = sortedAndFiltenetworking + filtenetworkingSecondHalf 

¡Gracias!

Swift no puede comparar dos objects. Primero debes enviarlos a un tipo específico:

 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) 

Si hay varios dictionarys que no tienen valor para la key especificada, se colocarán al final de la matriz de result pero sus órdenes relativas son inciertas.

Requisitos

Entonces tienes una gran variedad de dictionarys.

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

Desea orderar la matriz:

  • con el valor del Name ascendente
  • Los dictionarys sin una String Name deben estar al final.

Solución

Aquí está el código ( en el estilo de 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 } 

Salida

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

Haga un tipo de datos para representar sus datos:

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

Haga una rutina de extracción:

 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) } } 

Su tipo de datos se puede orderar por sus campos, porque tienen types reales.

 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 compara como "antes" con cualquier otro valor; Puedes escribir tu propio comparador si quieres cambiar eso.

Esto es lo que haría. Si eres capaz, haría la estructura más específica dándole un nombre y una edad distintos de la key y el valor. ¡Esto debería darle un resumen de cómo lograrlo!

 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}) } } 

para hacerlo más fácil, aquí:

 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.OrdenetworkingAscending}) } }