简体   繁体   English

Swift按键排序字典数组,其中value是可选的AnyObject

[英]Swift sort array of dictionaries by key where value is optional AnyObject

I'm pulling an array of dictionaries straight from Parse and displaying them in a table. 我正在直接从Parse中提取一系列字典并将它们显示在表格中。 So I'd really like to work with the data structure I'm handed (the oddly 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 object type AND the key might be missing from some of the dictionaries. PFObject[String : AnyObject?] ,我希望能够按任意键排序,所以我不知道对象类型,并且某些字典可能缺少密钥。 Because in Parse, if you don't give a property a value, it is simply nonexistent. 因为在Parse中,如果不给属性赋值,则它根本就不存在。 For example: 例如:

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

] ]

I want the dictionaries with missing keys to always be ordered after the sorted dictionaries. 我希望在排序的词典之后始终对缺少键的词典进行排序。 An example: 一个例子:

Original Table: 原表:

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

Ordered By Name: 按名称排序:

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

As I don't have a lot of control over the data model, and it's usage is limited throughout the application, I'd prefer to focus on an algorithmic solution rather than structural. 由于我对数据模型没有太多控制权,并且它的使用在整个应用程序中受到限制,我更倾向于关注算法解决方案而不是结构化解决方案。

I came up with a way to do this but it just seems inefficient and slow, I'm certain there's someone who can do this 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! 谢谢!

Swift can't compare any two objects. Swift无法比较任何两个对象。 You have to cast them to a specific type first: 您必须先将它们转换为特定类型:

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 multiple dictionaries that have no value for the specified key , they will be placed at the end of the result array but their relative orders are uncertain. 如果有多个字典没有指定key值,它们将被放置在result数组的末尾,但它们的相对顺序是不确定的。

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 the Name value ascending Name值升序
  • dictionaries without a Name String should be at the end 没有Name String字典应该在最后

Solution

Here's the code ( in functional programming style ) 这是代码( 在函数式编程风格中

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
}

Output 产量

print(sorted)

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

Make a datatype to represent your data: 创建一个数据类型来表示您的数据:

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

Make an extraction routine: 提取程序:

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 datatype 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 s compare as "before" any other value; nil s比较“之前”任何其他值; you can write your own comparator if you'd like to change that. 你可以编写自己的比较器,如果你想改变它。

Here is what I would do. 这就是我要做的。 If you are able to, I would make the struct more specific by giving it a Name and Age other than just key and value. 如果你能够,我会通过给它一个名称和年龄而不仅仅是键和值来使结构更具体。 This should give you an outline for how to achieve that 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, here: 为了方便起见,这里:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM