【问题标题】:How to sort an array of Structures with/by dynamic property如何使用/按动态属性对结构数组进行排序
【发布时间】:2016-07-01 17:50:44
【问题描述】:

给定一个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
}

问题是如何动态访问结构的属性,比如

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。

【问题讨论】:

  • 你为什么在这里使用结构体?对于 50 个属性,数组或字典似乎是更合适的选择。如果您需要封装额外的逻辑,那么您可以将数组或字典作为结构的属性。
  • @originaluser2 我从一个字典数组开始,它非常适合排序等。但是,随着项目的发展,我需要添加对象逻辑,从而转移到结构或类。无论哪种方式,它们仍然需要通过从 tableView 标题单击派生的属性之一进行排序。我可以用一堆 if..then 语句强制它(如果 headerClick = "col0" 然后按 x 排序,如果 headerClick = "col1" 按 y 排序等),但这似乎很不雅。
  • 那么为什么不简单地将字典作为结构的属性呢?这样,您既可以封装又可以轻松查找值。
  • @originaluser2 这是一个结构数组,用作 tableView 的数据源。因此,它们(结构)需要通过其中一个属性(与单击的列的标题匹配的那个)进行排序。如果字典嵌入在结构中,则无法对结构数组进行排序。但是,如果有办法,这是一个很好的解决方案,所以将其发布为答案!
  • 使用 NSArrayController 和 Cocoa 绑定,然后您就可以免费获得一切。但这需要一个类而不是一个结构作为模型。

标签: swift macos


【解决方案1】:

也许我有办法解决你的问题。与您的解决方案相比,此代码的优势在于您无需向结构添加下标方法即可通过代码创建硬编码的字符串-属性-值映射。

这是我的扩展

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"}]

限制

此解决方案的最严重限制是它仅适用于字符串属性。它必须在编译时才能更改为使用任何类型的属性。现在我还没有一个解决方案可以在不更改代码的情况下使其与任何类型的属性王一起工作。

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

【讨论】:

    【解决方案2】:

    我肯定会建议将您的字典简单地嵌入到您的结构中。字典是 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"])
    

    【讨论】:

    • 辉煌辉煌辉煌。你绝对没有错过任何东西——我误读了你的评论。这是正确的,而且效果很好。非常感谢!
    • @Jay 乐于助人:)
    【解决方案3】:

    这是一个答案(但不是最佳答案);使用下标返回正确的属性,并在 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
    })
    

    【讨论】:

    • 如果您打算采用这种方法 - 您可以通过在 key 上使用 switch 语句来改进它,然后只需将不同的列标识符列为案例。
    • @originaluser2 是的!好电话。
    • @Jay:但是使用这个解决方案,您只需将硬编码部分从闭包移动到下标方法......
    • @appzYourLife 我同意。将该代码移动到单个位置减少了应用程序中的大量重复代码,但正如您所指出的,它仍然不是一个非常优雅的解决方案。它有效,但其他答案要好得多。
    【解决方案4】:

    如果您不知道 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

    【讨论】:

    • 您假设属性的类型为String。但在编译时你并不知道。
    • 这个答案的目的是对结构的属性进行排序吗?如果是这样,那不是最初的问题。问题是如何通过在运行时确定的特定属性对数组中的结构进行排序。即用户单击表格列标题,然后我们知道要根据哪个属性对数组进行排序。属性名称将是从列标识符派生的字符串。我确实喜欢您通过...包含镜像功能...
    • 问题是在运行时比较未知类型,这些类型可能具有可比性,也可能不具有可比性。 similar。我更新了我的答案以使用您可以编写的比较两个“任意”的函数
    猜你喜欢
    • 2011-02-23
    • 2021-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 2021-10-21
    • 1970-01-01
    相关资源
    最近更新 更多