帮助跨两个属性对NSArray进行排序(使用NSSortDescriptor?)

时间:2010-02-03 07:16:37

标签: cocoa sorting nsarray nssortdescriptor

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

我有一个由带有键的对象组成的NSArray,比如“name”和“time”。这是一个例子,而不是用语言表达:

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,或使用任何其他方法?我不想自己动手,除非这是最有效的方式。

6 个答案:

答案 0 :(得分:20)

我的解决方案是:

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

你可以尝试一下

答案 1 :(得分:17)

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

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

答案 2 :(得分: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就足够了。

答案 3 :(得分:1)

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

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找到您想要的内容。

答案 4 :(得分:1)

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

答案 5 :(得分:0)

如果你必须进行更复杂的排序,只需要“升序”就可以处理(比如将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;