9

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

4

6 回答 6

21

我的解决方案是:

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

你可以试试看

于 2010-12-24T11:07:23.143 回答
18

该方法可以完成您需要的大部分工作: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];
}

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

于 2010-02-03T12:12:43.620 回答
3

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

@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.timeand进行排序time

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 就足够了。

于 2010-02-03T07:24:53.833 回答
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

于 2010-02-03T09:23:36.423 回答
1

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

于 2011-08-23T19:10:22.260 回答
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;
于 2011-06-16T18:29:56.117 回答