代码之家  ›  专栏  ›  技术社区  ›  Jaanus

帮助跨两个属性排序nsarray(使用nssortdescriptor?)

  •  8
  • Jaanus  · 技术社区  · 15 年前

    我有点像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和内存方面,不一定是代码方面)我如何构造NSSortDescriptor,或者使用其他方法?我不想自己滚,除非这是最有效的方法。

    6 回复  |  直到 15 年前
        1
  •  17
  •   Benedict Cohen    15 年前

    这个 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
  •  20
  •   RyanG    12 年前

    我的解决方案是:

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

    你可以试试

        3
  •  3
  •   James Eichele Bernard Igiri    15 年前

    我将创建一个名为 ItemGroup ,然后添加一个名为 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.time 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; }
    }
    

    如果你真的想成为 炽烈的 很快,您甚至可以修改 时间 属性设置动态分组时间:

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

    请注意,您必须确保 在你设定时间之前就已经设定好了。有了这个,你就根本不需要排序循环了。排序描述器就足够了。

        4
  •  1
  •   mjdth    15 年前

    我做了一些代码(没有试着运行它,也没有真正检查它,所以可能会有一些错误,但它有一个总的想法)来做你想要的事情。从性能上看,如果你开始运行大量的数据,它可能不是最好的。我相信有更好的方法可以做到这一点,但我想用最基本的方法来解决这个问题。

    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 .

        5
  •  1
  •   guPra    13 年前

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

        6
  •  0
  •   james_womack    14 年前

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