【问题标题】:NSSortDescriptor: Custom comparison on multiple keys simultaneouslyNSSortDescriptor:同时对多个键进行自定义比较
【发布时间】:2018-09-18 02:30:45
【问题描述】:

我有一个自定义对象,其中包含基于时间段的信息,例如属性 endCalYear、endMonth 和 periodLength,它们指示每个时间段的结束及其长度。

我想创建一个基于NSSortDescriptor 或其他排序方法,它结合了这三个属性并允许同时在所有三个键上对该对象进行排序。

例子:

EndCalYear  endMonth  periodLength (months) sortOrder
2012        6         6                     1
2012        6         3                     2
2012        3         3                     3
2011        12        12                    4

排序算法将完全取决于我自己的算法。

我如何编写这样的算法?

基于块的sortDescriptorWithKey:ascending:comparator: 方法在我看来不起作用,因为它只允许我指定一个排序键。但是,我需要同时对所有三个键进行排序。

关于如何解决这个问题的任何想法或想法?

谢谢!

【问题讨论】:

  • 你能举一个输入和预期输出的例子吗?另外,我假设您将这些对象存储在 NSArray 中是否正确?
  • @Clever 错误:我已使用预期的排序顺序/输出更新了我的问题中的表格。你是对的,这些对象存储在NSArray 中,这是由NSFetchedResultsController 请求产生的。如果排序可以与NSFetchedResultsController(例如NSSortDescriptor)结合使用,那就完美了。

标签: objective-c ios sorting comparison nssortdescriptor


【解决方案1】:

您可以改为使用块进行排序:

NSArray *sortedArray;
sortedArray = [myArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    MyObject *first = (MyObject*)a;
    MyObject *second = (MyObject*)b;

    if (first.endCalYear < second.endCalYear) {
        return NSOrderedAscending;
    }
    else if (first.endCalYear > second.endCalYear) {
        return NSOrderedDescending;
    }
    // endCalYear is the same

    if (first.endMonth < second.endMonth) {
        return NSOrderedAscending;
    }
    else if (first.endMonth > second.endMonth) {
        return NSOrderedDescending;
    }    
    // endMonth is the same

    if (first.periodLength < second.periodLength) {
        return NSOrderedAscending;
    }
    else if (first.periodLength > second.periodLength) {
        return NSOrderedDescending;
    }
    // periodLength is the same

    return NSOrderedSame;
}]

endCalYear 升序排序,然后endMonth 升序,最后periodLength 升序。您可以修改它以更改顺序或切换 if 语句中的符号以使其降序。

对于 NSFetchedResultsController,您可能想尝试其他方法:

看起来您可以pass it a list of descriptors,为您想要排序的每一列提供一个:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSSortDescriptor *descriptor1 = [[NSSortDescriptor alloc] initWithKey:@"endCalYear" ascending:YES];
NSSortDescriptor *descriptor2 = [[NSSortDescriptor alloc] initWithKey:@"endMonth" ascending:YES];
NSSortDescriptor *descriptor3 = [[NSSortDescriptor alloc] initWithKey:@"periodLength" ascending:YES];
NSArray *sortDescriptors = @[descriptor1, descriptor2, descriptor3];
[fetchRequest setSortDescriptors:sortDescriptors];

【讨论】:

  • 太棒了,内森!非常感谢!是否可以将相同的排序逻辑应用于NSFetchedResultsController
  • 不幸的是,我不能将 NSSortDescriptors 数组用于多个键,因为我的专有排序算法需要同时评估所有三个键,例如 if ((first.endCalYear &lt; second.endCalYear) &amp;&amp; (first.periodLength &lt; second.periodLength)) {...}。这不是关于先排序 endCalYear,然后是 endMonth,然后是 periodLength。
  • 在这种情况下,我认为没有办法可以在 NSFetchedResultsController 中进行所需的排序类型。该类用于管理 Core Data,排序在数据库级别完成。
【解决方案2】:

用于排序的 API 通常能够使用 NSSortDescriptors 的数组,而不仅仅是一个,那么为什么不使用它们呢?

例如,NSArray 有一个名为 sortedArrayUsingDescriptors:(注意复数形式)的方法,它采用 NSSortDescriptor 对象的数组。

所以你可以简单地写这个:

NSSortDescriptor *endCalYearSD = [NSSortDescriptor sortDescriptorWithKey:@"endCalYear" ascending:YES];
NSSortDescriptor *endMonthSD = [NSSortDescriptor sortDescriptorWithKey:@"endMonth" ascending:YES];
NSSortDescriptor *periodLenSD = [NSSortDescriptor sortDescriptorWithKey:@"periodLength" ascending:YES];

NSArray *sortedArray = [originalArray sortedArrayUsingDescriptors:@[endCalYearSD, endMonthSD, periodLenSD]];

这样,您的originalArray 将首先按endCalYear 排序,每个具有相同endCalYear 的条目将按endMonth 排序,然后每个具有相同endCalYear 和endMonth 的条目将按periodLendth 排序。

对于大多数提议排序的 API(包括 CoreData 等),您的 API 使用 sortDescriptor 数组,因此原则始终相同。


如果您真的只需要使用一个 NSSortDescriptor(并且您的排序算法不够灵活,无法使用基于块的比较器或 NSSortDescriptors 数组),您可以简单地为您的自定义提供一个属性计算一些值的对象,您可以将这些值作为排序算法的基础。

例如将这样的方法添加到您的自定义类中:

-(NSUInteger)sortingIndex {
    return endCalYear*10000 + endCalMonth*100 + periodLength;
}

然后按此键/属性排序。这不是很干净阅读和一个非常漂亮的设计模式,但更好的方法是改变你的排序算法 API 以允许一次对多个键进行排序,所以......


[EDIT](回答您关于基于块的 API 的 [EDIT])

我不明白为什么基于块的 API sortDescriptorWithKey:ascending:comparator: 不适合您。您可以在那里指定您需要的任何自定义 NSComparator 块,因此这个块可以告诉,给定两个对象,一个在另一个之前。确定哪个在哪个之前取决于您的方式,您可以仅比较endCalYear,或endCalYearendMonth等,因此这里对使用多个键进行排序没有限制。

【讨论】:

  • 不幸的是,我不能将 NSSortDescriptors 数组用于多个键,因为我的专有排序算法需要同时评估所有三个键,例如 if ((first.endCalYear
  • 如果你有专有的排序算法,我真的不明白你为什么需要NSSortDescriptor?你确定使用它合适吗?为什么不提供一个块 API(使用 NSComparator 块)(并且您的专有排序算法只需在两个对象上调用块即可知道哪个在另一个之前,所以无论您的算法是什么,添加这样的 API 应该是可能)?
  • 无论如何,如果你真的只需要坚持一个NSSortDescriptor(而且你的排序算法不够灵活,无法使用基于块的比较器或NSSortDescriptors数组),你可以只需为您的自定义对象提供一个属性,该属性会计算一些值,您可以在该值上建立您的排序算法。例如添加一个方法-(NSUInteger)sortingIndex { return endCalYear*10000 + endCalMonth*100 + periodLength; } 并按此键/属性排序。
  • 我已经编辑了我的答案以添加一些关于使用基于块的 API 以及它与使用多个键兼容的方式的注意事项
猜你喜欢
  • 2015-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
  • 2020-07-11
  • 1970-01-01
相关资源
最近更新 更多