如何使用/通过动态属性对结构数组进行排序

时间:2016-07-01 17:50:45

标签: swift macos

给定NSTableView具有结构数组作为其数据源。用户可以单击任何列标题以按该列排序。列标识符与结构中属性的属性名称匹配。

给定结构

struct MyStructure {       
     var col0data = ""  //name matches the column identifier
     var col1data = ""
}

和一组结构

var myArray = [MyStructure]()

目标是在单击列标题时,使用该列的标识符按该列标识符/属性对结构数组进行排序

有一系列词典,很容易......

self.myArrayOfDictionaries.sortInPlace {
     (dictOne, dictTwo) -> Bool in
     let d1 = dictOne[colIdentifier]! as String;
     let d2 = dictTwo[colIdentifier]! as String;

     return d1 < d2 //or return d1 > d2 for reverse sort
}

问题是如何动态访问Structure的属性,如

let struct = myArray[10] as! MyStructure //get the 10th structure in the array
let value = struct["col0data"] as! String //get the value of the col0data property

如果有更好的方法,建议将不胜感激。

我还应该注意,该结构可能有50个属性,因此这是为了减少任何一个属性对数组进行排序所需的代码量。

编辑:

一种解决方案是将结构更改为从NSObject派生的类。然后可以通过.valueForKey(“some key”)访问属性。但是,我试图保持这个Swifty。

4 个答案:

答案 0 :(得分:2)

也许我有解决问题的方法。此代码优于您的解决方案的优点是,您无需在结构中添加下标方法,以通过代码创建硬编码的String-Property-Value映射。

这是我的扩展

extension _ArrayType {
    func sortedBy(propertyName propertyName: String) -> [Self.Generator.Element] {
        let mirrors = self.map { Mirror(reflecting: $0) }
        let propertyValues = mirrors.map { $0.children.filter { $0.label == propertyName }.first?.value }
        let castedValues = propertyValues.map { $0 as? String }

        let sortedArray = zip(self, castedValues).sort { (left, right) -> Bool in
            return left.1 < right.1
        }.map { $0.0 }
        return sortedArray
    }
}

用法

struct Animal {
    var name: String
    var type: String
}

let animals = [
    Animal(name: "Jerry", type: "Mouse"),
    Animal(name: "Tom", type: "Cat"),
    Animal(name: "Sylvester", type: "Cat")
]

animals.sortedBy(propertyName: "name")
// [{name "Jerry", type "Mouse"}, {name "Sylvester", type "Cat"}, {name "Tom", type "Cat"}]

animals.sortedBy(propertyName: "type")
// [{name "Tom", type "Cat"}, {name "Sylvester", type "Cat"}, {name "Jerry", type "Mouse"}]

限制

此解决方案的最大限制是它仅适用于String属性。它必须在编译时才能更改为适用于任何类型的属性。现在我还没有一个解决方案,可以让它适用于任何属性类型的王而不改变代码。

我已经就问题的核心here寻求帮助。

答案 1 :(得分:1)

这是一个答案(但不是最好的答案);使用下标返回正确的属性,并在array.sort中设置要排序的属性:

struct MyStructure {       
     var col0data = ""  //name matches the column identifier
     var col1data = ""

     subscript(key: String) -> String? { //the key will be the col identifier
        get {
            if key == "col0data" {
                return col0data
            } else if key == "col1data" {
                return col1data
            }

            return nil
        }
    }
}

然后这是排序的工作方式:

let identifier = the column identifier string,say col0data in this case

myArray.sortInPlace ({

    let my0 = $0[identifier]!  //the identifier from the table col header
    let my1 = $1[identifier]!

    return my0 < my1
})

答案 2 :(得分:1)

我肯定会建议您将字典嵌入到结构中。字典是50个键值对比50个属性更合适的数据结构 - 你已经说过这将是一个可接受的解决方案。

在您的结构中嵌入字典将为您提供两全其美的优势 - 您可以轻松地封装逻辑和放大器。您可以轻松查找每个列ID的值。

您现在可以简单地对结构数组进行排序:

struct MyStructure {

    var dict = [String:String]()

    init(col0Data:String, col1Data:String) {
        dict["col0data"] = col0Data
        dict["col1data"] = col1Data
    }
}


var myArray = [MyStructure(col0Data: "foo", col1Data: "bar"), MyStructure(col0Data: "bar", col1Data: "foo")]

var column = "col0data"
myArray.sort {
    $0.dict[column] < $1.dict[column]
}

print(myArray) // [MyStructure(dict: ["col0data": "bar", "col1data": "foo"]), MyStructure(dict: ["col0data": "foo", "col1data": "bar"])]

column = "col1data"
myArray.sort {
    $0.dict[column] < $1.dict[column]
}

print(myArray) // MyStructure(dict: ["col0data": "foo", "col1data": "bar"])], [MyStructure(dict: ["col0data": "bar", "col1data": "foo"])

答案 3 :(得分:-1)

如果你不知道MyStructure的值是什么类型,你将很难比较它们以对它们进行排序。如果你有一个可以比较你在MyStructure中可以拥有的所有类型的函数,那么这样的东西应该可以工作

struct OtherTypeNotComparable {

}

struct MyStructure {
    var col0data = "cat"  //name matches the column identifier
    var col1data: OtherTypeNotComparable
}

let structures = [MyStructure(), MyStructure()]

let sortBy = "col1data"

func yourCompare(a: Any, b: Any) -> Bool {
    return true
}

var expanded : [[(String, Any, MyStructure)]] 
    = structures.map { s in Mirror(reflecting: s).children.map { ($0!, $1, s) } }

expanded.sortInPlace { (a, b) -> Bool in
    let aMatch = a.filter { $0.0 == sortBy }.first!.1
    let bMatch = b.filter { $0.0 == sortBy }.first!.1
    return yourCompare(aMatch, b: bMatch)
}

来源:https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Mirror_Structure/index.html