【问题标题】:Help sorting an NSArray across two properties (with NSSortDescriptor?)帮助跨两个属性对 NSArray 进行排序(使用 NSSortDescriptor?)
【发布时间】:2011-01-12 12:20:10
【问题描述】:

我有点 NSSortDescriptor n00b。不过,我认为它是我需要做的正确工具:

我有一个由带有键的对象组成的 NSArray,比如“名称”和“时间”。与其说出来,不如举个例子:

input:

name: time
B: 4
C: 8
B: 5
C: 4
A: 3
C: 2
A: 1
A: 7
B: 6


desired output:

name: time
A: 1 <---
A: 3
A: 7
C: 2 <---
C: 4
C: 8
B: 4 <---
B: 5
B: 6

因此这些值按“时间”排序并按“名称”分组。 A 排在第一位,因为他的时间价值最小,而 A 的所有价值都一个接一个。然后是 C,在他的所有值中,他的时间值第二小。我已经指出了决定名称排序方式的值;在每个名称组中,按时间排序。

如何以最有效的方式从输入到输出 NSArray? (CPU 和内存方面,不一定是代码方面。)我将如何为此构造 NSSortDescriptors,或使用任何其他方法?我不想自己动手,除非这是最有效的方式。

【问题讨论】:

    标签: cocoa sorting nsarray nssortdescriptor


    【解决方案1】:

    我的解决办法是:

        NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
        NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"time" ascending:YES];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
    

    你可以试试

    【讨论】:

    • 这解决了我的问题,可以灵活添加更多的 sortDescriptor。
    【解决方案2】:

    sortedArrayUsingDescriptors:NSArray 方法可以满足您的大部分需求:

    第一个描述符指定用于对接收者的内容进行排序的主键路径。任何后续描述符都用于进一步细化具有重复值的对象的排序。有关更多信息,请参阅 NSSortDescriptor。

    还需要使用NSPredicate 进行一些过滤:

    NSSortDescriptor *timeSD = [NSSortDescriptor sortDescriptorWithKey: @"time" ascending: YES];
    
    NSMutableArray *sortedByTime = [UnsortedArray sortedArrayUsingDescriptors: timeSD];
    NSMutableArray *sortedArray = [NSMutableArray arrayWithCapacity:[sortedByTime count]];
    
    while([sortedByTime count]) 
    {
            id groupLead = [sortedByTime objectAtIndex:0];  
            NSPredicate *groupPredicate = [NSPredicate predicateWithFormat:@"name = %@", [groupLead name]];
    
            NSArray *group = [sortedByTime filteredArrayUsingPredicate: groupPredicate];
    
            [sortedArray addObjectsFromArray:group];
            [sortedByTime removeObjectsInArray:group];
    }
    

    我不知道这是否是最有效的方法,但在您有理由相信它会导致问题之前,无需担心性能影响。这是过早的优化。我不会担心这种方法的性能。你必须信任这个框架,否则你最终会因为毫无根据的偏执狂而重写它(从而破坏框架的意义)。

    【讨论】:

    • 这并不能回答问题:我的情况比简单地按名称排序更复杂。
    • @Jaanus。哦,我明白了。我没有注意到组的顺序取决于时间。
    • @Jaanus。我已经更新了代码,让它真正回答了这个问题!
    • 谢谢,这看起来像我需要的。我将从答案中尝试几种方法并进行报告。
    • 赞成答案。希望我可以为性能影响评论扣除四分之一分。考虑算法的效率并不是为时过早的优化。这句话被越来越多地折腾,这不是它的意思。花时间考虑算法的复杂性,以及是否有更好的方法,就是好的工程。
    【解决方案3】:

    我将创建一个名为 ItemGroup 的新类,然后将一个名为 group 的额外 ivar 添加到您的项目类中:

    @interface ItemGroup : NSObject
    {
        NSNumber * time;
    }
    @property (nonatomic, copy) time;
    @end
    
    @interface ItemClass : NSobject
    {
        NSString * name;
        NSNumber * time;
        ItemGroup * group;
    }
    @property (nonatomic, copy) NSString * name;
    @property (nonatomic, copy) NSNumber * time;
    @property (nonatomic, assign) ItemClass * group; // note: must be assign
    @end
    

    然后,您可以执行以下操作:

    NSMutableDictionary * groups = [NSMutableDictionary dictionaryWithCapacity:0];
    for (ItemClass * item in sourceData)
    {
        ItemGroup * group = [groups objectForKey:item.name];
        if (group == nil)
        {
            group = [[ItemGroup alloc] init];
            [groups setObject:group forKey:item.name];
            [group release];
    
            group.time = item.time;
        }
        else if (item.time < group.time)
        {
            group.time = item.time;
        }
        item.group = group;
    }
    

    此代码循环遍历未排序的数组,跟踪每个组的最短时间,并设置每个项目的组。完成后,您只需对group.timetime 进行排序:

    NSSortDescriptor * groupSorter;
    groupSort = [NSSortDescriptor sortDescriptorWithKey:@"group.time" ascending:YES];
    
    NSSortDescriptor * timeSorter;
    timeSort = [NSSortDescriptor sortDescriptorWithKey:@"time" ascending:YES];
    
    NSArray * sortDescriptors = [NSArray arrayWithObjects:groupSort, timeSort, nil];
    
    NSArray * sorted = [sourceData sortedArrayUsingDescriptors:sortDescriptors];
    

    这应该可以解决问题!

    更新:请注意,如果您能够直接分配组,您可以获得更多更好的性能。像这样的:

    @interface ItemGroup : NSObject
    {
        NSString * name;
        NSNumber * time;
    }
    @property (nonatomic, copy) NSString * name;
    @property (nonatomic, copy) NSSNumber * time;
    @end
    
    @interface ItemClass : NSObject
    {
        ItemGroup * group;
        NSNumber * time;
    }
    @property (nonatomic, retain) ItemGroup * group;
    @property (nonatomic, copy) NSNumber * time;
    @end
    

    现在,如果您在某处维护一个组列表(如果需要,它们甚至可以放入某个数组中):

    ItemGroup * group_A = [[ItemGroup alloc] init];
    group_A.name = @"A";
    ItemGroup * group_B = [[ItemGroup alloc] init];
    group_B.name = @"B";
    ...
    

    您无需设置数据项的名称,而是设置它们的组:

    someItem.group = group_A;
    someItem.time = GetSomeRandomTimeValue();
    [sourceData addObject:someItem];
    ....
    

    这将大大简化用于设置分组时间的循环:

    for (ItemClass * item in sourceData)
    {
        if (item.time < group.time) { group.time = item.time; }
    }
    

    而且,如果你真的想快速地,你甚至可以修改 time 属性的属性设置器来即时设置分组时间:

    @implementation ItemClass
    - (void)setTime:(NSNumber *)newTime
    {
        if (newTime < group.time) { group.time = newTime; }
        time = [newTime copy];
    }
    @end
    

    请注意,在设置时间之前,您必须确保已设置 group。有了这个,你根本不需要那个排序循环。 sortDescriptors 就足够了。

    【讨论】:

    • 我明白这一点,但我认为这不能回答问题。我没有按名称排序,我需要根据给定名称的最小时间值对名称进行排名和分组。
    • 啊。现在我明白了。这更有趣。
    • 您是否定义了存储在原始数组中的对象类型?即它是一个可以添加 ivars 的自定义类吗?
    • 是的,这是我的自定义类,我可以添加 ivars。我看不出自己有什么帮助。数据是易变的,新值可能会在运行时到达。在任何给定时间,我只需要按这种方式排序的数据的快照。
    • 到目前为止的所有答案中,我最喜欢这些,尤其是小组方法,将报告回来。我认为这具有 O(n) 复杂性,比我目前的天真 O(n*n) 好得多。门外分配会很好,但实际情况更复杂,取决于保存时不知道的环境。
    【解决方案4】:

    我做了一个小代码(没有尝试运行它或真正检查它,所以可能会有一些错误,但它具有一般的想法)来做你正在寻找的东西。性能方面,如果您开始遇到大量数据,它可能不会是最好的。我确信有更好的方法可以做到这一点,但我觉得这样做是作为“临时修复”答案的最基本方式。

    NSMutableArray *copiedarray = [YourFirstArray mutableCopy];
    NSMutableArray *sortedarray = [[NSMutableArray alloc] init];
    NSMutableArray *tempgroup = nil;
    NSSortDescriptor * groupSorter = [NSSortDescriptor sortDescriptorWithKey:@"time" ascending:YES];
    
    NSInteger i;
    NSInteger savedlowest = -1;
    NSString *savedname = @"";
    
    
    while ([copiedarray count] > 0) {
        ///reset lowest time and group
        savedlowest = -1;
        savedname = @"";
    
        ///grab the lowest time and group name
        for (ii = 0;ii < [copiedarray count]; ii++) {
            if (savedlowest==-1 || ((YourClass *)([copiedarray objectAtIndex:ii])).time<savedlowest)) {
                savedname = ((YourClass *)([copiedarray objectAtIndex:ii])).name;
                savedlowest = ((YourClass *)([copiedarray objectAtIndex:ii])).time;
            }
        }
    
        //we have the lowest time and the type so we grab all those items from the group
        tempgroup = [[NSMutableArray alloc] init];
        for (ii = [copiedarray count]-1;ii > -1; ii--) {
            if ([((YourClass *)([copiedarray objectAtIndex:ii])).name isEqualToString:savedname]) {
                ///the item matches the saved group so we'll add it to our temporary array
                [tempgroup addObject:[copiedarray objectAtIndex:ii]];
                ///remove it from the main copied array for "better performance"
                [copiedarray removeObjectAtIndex:ii];
            }
        }
    
        [tempgroup sortUsingDescriptors:[NSArray arrayWithObject:groupSorter]];
        [sortedarray addObjectsFromArray:tempgroup];
    
        [tempgroup release];
        tempgroup = nil;
    
    }
    

    最后你会在sortedarray找到你想要的东西。

    【讨论】:

    • 我认为这与 Benedict Cohen 的回应基本相同,但您自己而不是排序和谓词。
    【解决方案5】:

    您可以使用 NSSortDescriptor。这些描述符非常有用,因为它们让您可以进行多键排序以及单键排序。区分大小写和不区分大小写也很容易实现。我找到了一个详细的例子HERE

    【讨论】:

      【解决方案6】:

      如果您必须进行更复杂的排序,只需“升序”即可处理(比如将 NSString 排序为浮点数),您可能需要执行以下操作:

          NSDictionary *d = [self dictionaryFromURL:[NSURL URLWithString:urlStringValue]];    
      
          NSSortDescriptor *distanceSort = [[NSSortDescriptor alloc] initWithKey:@"distance" ascending:YES comparator:^(id left, id right) {
              float v1 = [left floatValue];
              float v2 = [right floatValue];
              if (v1 < v2)
                  return NSOrderedAscending;
              else if (v1 > v2)
                  return NSOrderedDescending;
              else
                  return NSOrderedSame;
          }];
          NSSortDescriptor *nameSort = [NSSortDescriptor sortDescriptorWithKey:@"company_name" ascending:YES];
      
          NSArray *sortDescriptors = [NSArray arrayWithObjects:distanceSort, nameSort, nil];
      
          [distanceSort release];
      
          NSArray *sortedObjects = [[d allValues] sortedArrayUsingDescriptors:sortDescriptors];
      
          ILog();
          return sortedObjects;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多