【问题标题】:How to Achieve Expressive Dynamic Array Sorting in Swift如何在 Swift 中实现富有表现力的动态数组排序
【发布时间】:2014-11-25 20:17:06
【问题描述】:

作为一个只学习 Swift 做事方式的学科,我尽量避免对 NSWhatevers 进行类型转换,但是,有时这样做会非常令人沮丧。

我有一个表格视图控制器,带有可排序的数据列标题。我使用的数据是一个对象数组。我想按这些对象中的字段名称对数组进行排序。如果用户再次点击相同的列标题按钮,排序顺序会颠倒,这意味着我必须跟踪每个字段/列的当前排序方向。

在 Objective-C/NSArray API 中,动态执行此操作非常简单。您只需在NSArray 上使用NSSortDescriptorsortedArrayUsingDescriptors,将字段/列名称和排序顺序传递给它,您就可以得到所需的内容。

然而,在 Swift 中,您不能将字段名称作为字符串动态传递。相反,您必须明确指定要使用的字段,这意味着您必须设置详细开关或 if/else 构造,或者您必须定义一个字典,将排序字段作为键,闭包作为值。然后闭包将传递给您的排序函数。我在操场上设置它只是为了看看我能用字典/闭包查找路线找出什么。这是我想出的使用一些虚拟对象的方法:

class Order {
    var orderNumber:String
    var invoiceNumber:String
    var orderDate:NSDate

    init(orderNumber:String, invoiceNumber:String, orderDate:NSDate) {
        self.orderNumber = orderNumber
        self.invoiceNumber = invoiceNumber
        self.orderDate = orderDate
    }
}

我在这样的数组中实例化其中的一些:

var orders = [Order(orderNumber: "123456", invoiceNumber: "2342342", orderDate: NSDate()),
    Order(orderNumber: "332422", invoiceNumber: "848484", orderDate: NSDate()),
    Order(orderNumber: "423425", invoiceNumber: "488244", orderDate: NSDate())]

然后我创建我的查找字典,它提供了我想要排序的闭包:

var sorters = [
    "orderNumber" :
        [true : { (o1: Order, o2: Order) -> Bool in return o1.orderNumber < o2.orderNumber },
        false : { (o1: Order, o2: Order) -> Bool in return o1.orderNumber > o2.orderNumber }],
    "invoiceNumber" :
        [true : { (o1: Order, o2: Order) -> Bool in return o1.invoiceNumber < o2.invoiceNumber },
            false : { (o1: Order, o2: Order) -> Bool in return o1.invoiceNumber > o2.invoiceNumber }],
    "orderDate" :
        [true : { (o1: Order, o2: Order) -> Bool in return o1.orderDate < o2.orderDate },
            false : { (o1: Order, o2: Order) -> Bool in return o1.orderDate > o2.orderDate }]
]

您可能想知道truefalse 键的作用。这是为了确定是升序(true)还是降序(false)排序。当我实际运行排序时,您可以看到这是如何进行的:

var currentSortField = "invoiceNumber"
var ascending = false

orders.sort(sorters[currentSortField]![ascending]!)

注意:我还必须为 NSDate 重载大于和小于运算符,才能在“orderDate”字段中进行比较:

func >(lhs:NSDate, rhs:NSDate) -> Bool {
    return lhs.compare(rhs) == NSComparisonResult.OrderedAscending
}

func <(lhs:NSDate, rhs:NSDate) -> Bool {
    return lhs.compare(rhs) == NSComparisonResult.OrderedDescending
}

我意识到这个问题的背景有点长,但是,我只是有点困惑,Swift 似乎没有更优雅的解决方案来解决这个问题,并希望提供我来的完整解决方案与时俱进。请记住,在这种情况下,等效的 NSWhatever 是一个单行:

var sorted = (orders as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: currentSortField, ascending: ascending)])

那么问题来了。在 Swift 中是否有更简洁、更具表现力和惯用的方式来实现这一点,而无需借助 Foundation API?

如果您想试用我创建的用于测试的 Playground,可以在此处获取:https://www.dropbox.com/s/8m53bprzui50muq/Sorting.playground.zip?dl=0

更新 在与 cmets 中的 Matt 讨论之后,我继续实施了一个排序闭包,在使我的 Order 对象继承自 NSObject 之后,使用valueForKey: 进行了妥协。这是一个折衷方案,但它使我无法完全放弃 Swift 排序功能,而且我不必输入一大堆代码。它是这样的:

var currentSortField = "invoiceNumber"
var ascending = false

var sorter = { (o1: Order, o2: Order) -> Bool in
    if ascending {
        return (o1.valueForKey(currentSortField) as String) < (o2.valueForKey(currentSortField) as String)

    } else {
        return (o1.valueForKey(currentSortField) as String) > (o2.valueForKey(currentSortField) as String)

    }
}

orders.sort(sorter)

这不是单行的,而是一种有趣的折衷方案。

更新到更新

这实际上是行不通的,除非所有的变量都是字符串——它们不是。当相关字段是 NSDate 而不是字符串时,这会在 o1.valueForKey(currentSortField) as String 上消失。

【问题讨论】:

  • "然而,在 Swift 中,你不能将字段名称作为字符串动态传递" 对不起,你把我弄丢了——为什么不能呢?
  • @matt 也许我只是措辞错误。在排序闭包中,例如,当您使用 $0.invoiceNumber 时,您不能说 $0."invoiceNumber" 以这种方式传递了字段名称。
  • 对,我明白了。而且我想你禁止我说valueForKey(),即使这个类继承自NSObject?
  • 确实如此。禁止! ;-) 我在必要时会使用它,但我非常愿意尝试采用 Swift 方法处理所有事情。鉴于它的根源仍然存在并且可行,使用 valueForKey 之类的东西并没有错,但是,我希望有一个我还没有发现的小魔法,那就是纯 Swift。
  • 是的,但 Swift 的“魔力”是带走 内省、动态消息形成和类型不可知论。因此,当您举起 Objective-C 并说“在这种情况下 NSWhatever 等价物是单行”时,您似乎是在说:“现在这样做,但没有任何危险但令人上瘾的强大 NSWhatever 功能 一开始就把它做成了单线。”这公平吗?我的意思是,让我们看看在没有使用任何这些特性的情况下在Objective-C中做到这一点。你的单线是脆脆的,具有 Swift 旨在防止的那种崩溃性。

标签: objective-c arrays sorting swift


【解决方案1】:

我不清楚什么样的答案会让你满意。我个人认为您的解决方案是一个很好的解决方案。对于那些在做事时遇到困难的人来说,一个更简单的版本是这样的:

struct Thing {
    var first:String
    var last:String
}
var arr = [Thing]()
arr.append(Thing(first:"Al", last:"Zork"))
arr.append(Thing(first:"Zelda", last:"Aardvark"))

var howToSort: ((Thing, Thing) -> Bool)

func sorter(thing1:Thing, thing2:Thing) -> Bool {
    return thing1.first < thing2.first
}
howToSort = sorter
let arr2 = sorted(arr, howToSort)

func sorter2(thing1:Thing, thing2:Thing) -> Bool {
    return thing1.last < thing2.last
}
howToSort = sorter2
let arr3 = sorted(arr, howToSort)

关键是sortersorter2 之类的东西是可以存储和检索的。这就是保存排序条件所依赖的。

现在,sortersorter2 确实不能像 NSSortDescriptor 那样动态地形成,或者动态地和类型无关地应用,当您使用排序描述符进行排序时,KVC 正在执行的方式。但是,正如我在评论中所说,Swift 是一种故意缺少 eval(“不要 eval!”)、内省、动态消息形成(没有 performSelector)和类型不可知论的语言。我想,你可以使用泛型来概括你的解决方案,但在我看来,真正的问题是你现在沉迷于这些东西的“力量”,你想在没有实际给予它们的情况下对它们冷淡向上。在我看来,我的工作是说服你离开壁架。

当然,我对您的问题的真实回答是:嘿,Objective-C 和 NSSortDescriptor 不会消失,那么有什么大不了的?

【讨论】:

    【解决方案2】:

    试试这个解决方案。它的工作原理和最短的方式

    var array = [6,3,2,1,5,4]
    

    对其元素进行排序(升序)

    array = array.sort { $0 < $1 }
    print(array) 
    result is  [1, 2, 3, 4, 5, 6]
    

    【讨论】:

      猜你喜欢
      • 2018-10-04
      • 2012-01-09
      • 1970-01-01
      • 1970-01-01
      • 2020-06-24
      • 2021-04-02
      • 1970-01-01
      • 2014-04-20
      • 1970-01-01
      相关资源
      最近更新 更多