Помогите отсортировать NSArray по двум свойствам (с NSSortDescriptor?) - PullRequest
8 голосов
/ 03 февраля 2010

Я немного 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? (для процессора и памяти, не обязательно для кода.) Как бы я сконструировал для этого NSSortDescriptors или использовал какой-либо другой метод? Я не хочу катиться самостоятельно, если это не самый эффективный способ.

Ответы [ 6 ]

20 голосов
/ 24 декабря 2010

Мое решение:

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

Вы можете попробовать это

18 голосов
/ 03 февраля 2010

Метод 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];
}

Я понятия не имею, является ли это наиболее эффективным методом, но пока у вас нетпричина полагать, что это вызывает проблемы, нет необходимости беспокоиться о влиянии на производительность.Это преждевременная оптимизация.Я не буду беспокоиться о производительности этого метода.Вы должны доверять фреймворку, иначе вы в конечном итоге переписаете его (тем самым подорвав суть фреймворка) из-за необоснованной паранойи.

3 голосов
/ 03 февраля 2010

Я бы создал новый класс с именем 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; }
}

И, если вы действительно хотите быть сверкающим быстро, вы можете даже изменить установщик свойства для своего свойства time, чтобы установить время группы на лету:

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

Обратите внимание, что вы должны быть уверены, что group было установлено, прежде чем установить время. С этим на месте, вам вообще не понадобится этот цикл сортировки. Сортировка дескрипторов будет достаточно.

1 голос
/ 23 августа 2011

Вы можете использовать NSSortDescriptor.Эти дескрипторы очень полезны, так как позволяют вам выполнять сортировку по нескольким ключам, а также сортировку по одному ключу.Чувствительность к регистру и нечувствительность также легко достижимы.Я нашел подробный пример ЗДЕСЬ

1 голос
/ 03 февраля 2010

Я сделал небольшой код (не пробовал запускать его или перебирать, так что может быть пара ошибок, но у него есть общая идея) сделать то, что вы ищете.С точки зрения производительности, это, вероятно, не будет лучшим, если вы начнете сталкиваться с огромными объемами данных.Я уверен, что есть лучший способ сделать это, но я чувствовал, что делать это самым простым способом как «временное исправление» ответа.

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.

0 голосов
/ 16 июня 2011

Если вам нужно выполнить более сложную сортировку, о которой может позаботиться только «восходящая» (скажем, сортировка 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;
...