2010-02-03 8 views
8

Ich bin ein bisschen ein NSSortDescriptor n00b. Ich denke jedoch, es ist das richtige Werkzeug für das, was ich tun muss:Hilfe Sortierung eines NSArray über zwei Eigenschaften (mit NSSortDescriptor?)

Ich habe ein NSArray bestehend aus Objekten mit Schlüsseln, sagen wir, "Name" und "Zeit". Anstatt es zu verbalisieren, hier ein Beispiel:

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 

So sind die Werte von „Zeit“ sortiert werden und durch „name“ gruppiert. A kommt zuerst, weil er den kleinsten Zeitwert hatte und alle Werte für A aufeinander folgen. Dann kommt C, er hat den zweitkleinsten Zeitwert aus all seinen Werten. Ich habe die Werte angegeben, die bestimmen, wie die Namen sortiert werden. Innerhalb jeder Namensgruppe erfolgt die Sortierung nach der Zeit.

Wie bekomme ich vom Eingang zum Ausgang NSArray auf die effizienteste Weise? (cpu-und memory-weise, nicht unbedingt code-weise.) Wie würde ich die NSSortDescriptors dafür konstruieren oder eine andere Methode verwenden? Ich möchte nicht meine eigenen rollen, außer es ist der effizienteste Weg.

Antwort

17

Die sortedArrayUsingDescriptors:NSArray Methode macht das meiste, was Sie brauchen:

Der erste Descriptor den Primärschlüssel Pfad gibt beim Sortieren des Empfängers Inhalte verwendet werden. Alle nachfolgenden Deskriptoren werden verwendet, um das Sortieren von Objekten mit doppelten Werten weiter zu verfeinern. Weitere Informationen finden Sie unter NSSortDescriptor.

Einige Filterung mit NSPredicate ist auch erforderlich:

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

Ich habe keine Ahnung, ob dies die effizienteste Methode ist, aber bis Sie Grund zu der Annahme, dass es Probleme gibt es keinen Grund zur Sorge verursacht die Auswirkungen auf die Leistung. Es ist eine vorzeitige Optimierung. Ich hätte keine Bedenken hinsichtlich der Durchführung dieser Methode. Sie müssen dem Framework vertrauen, sonst werden Sie es am Ende aufgrund einer unbegründeten Paranoia neu schreiben (und damit den Rahmen des Frameworks untergraben).

+0

Das beantwortet die Frage nicht: Meine Situation ist komplizierter als einfach nach Namen zu sortieren. – Jaanus

+0

@Jaanus. Ohhh ich sehe. Ich habe nicht bemerkt, dass die Reihenfolge der Gruppen von der Zeit abhängt. –

+0

@Jaanus. Ich habe den Code aktualisiert, damit er die Frage beantwortet! –

3

Ich würde eine neue Klasse ItemGroup, und fügen Sie dann eine zusätzliche Ivar genannt group zu Ihrem Artikel Klasse namens erstellen:

@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 

Dann könnten Sie Folgendes tun:

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

Dieser Code Durchläuft das unsortierte Array und protokolliert die Mindestzeit für jede Gruppe sowie die Gruppe für jeden Eintrag. Mit diesen komplett Sie einfach Art auf group.time und 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]; 

Und das sollte es tun!

UPDATE: Beachten Sie, dass Sie viel bessere Leistung erhalten können, wenn Sie die Gruppen direkt aus dem Tor zuordnen konnten. Etwas wie folgt aus:

@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 

Nun, wenn Sie eine Liste der Gruppen irgendwo halten (sie sogar irgendwo in einem Array gehen könnte, wenn es sein muss):

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

und stattdessen die Namen der Einstellung Ihrer Datenelemente, stellen Sie ihre Gruppe:

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

Dies würde die Schleife erheblich vereinfachen verwendet Gruppe Zeiten einzustellen:

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

Und wenn Sie wirklich darüber sein lodernden schnell wollte, man konnte sogar die Eigenschaft Setter für Ihre time Eigenschaft ändern, um die Gruppe mal on the fly zu setzen:

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

Beachten Sie, dass Sie müssten sicher sein, dass group eingestellt wurde, bevor Sie die Uhrzeit einstellen. Damit wäre diese Sortierschleife überhaupt nicht nötig. Die sortDescriptors wären ausreichend.

+0

Ich verstehe das, aber ich denke nicht, dass dies die Frage beantwortet. Ich sortiere nicht nach Namen, ich muss die Namen sortieren und gruppieren, basierend auf dem kleinsten Zeitwert für einen bestimmten Namen. – Jaanus

+0

Ah. Jetzt sehe ich. Das ist viel interessanter. –

+0

Definieren Sie den Objekttyp, der im ursprünglichen Array gespeichert wird? d. h. ist es eine benutzerdefinierte Klasse, der Sie Ivars hinzufügen können? –

1

Ich ging durch, um einen kleinen Code zu machen (habe nicht versucht, ihn laufen zu lassen oder wirklich darüber zu gehen, also könnte es ein paar Fehler geben, aber es hat die allgemeine Idee), zu tun, wonach du suchst. Leistungsmäßig wird es wahrscheinlich nicht das Beste sein, wenn Sie anfangen, riesige Datenmengen zu verarbeiten. Ich bin mir sicher, dass es einen besseren Weg gibt, dies zu tun, aber ich hatte das Gefühl, es auf die einfachste Art und Weise als "vorläufige Lösung" zu versuchen.

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; 

} 

Am Ende landen Sie mit dem, was Sie suchen in sortedarray.

+0

Ich denke, das macht fast das selbe wie Benedikt Cohens Antwort, aber allein statt Sortierung und Prädikate. – Jaanus

20

Meine Lösung ist:

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

Sie können versuchen, es

+0

Das hat meine Probleme gelöst, flexibel, mehr sortDescriptor hinzuzufügen. –

0

Wenn Sie die komplizierteren Sortier nur „aufsteigend“ zu tun haben, können kümmern (sagen Art NSString, als ob sie schwimmt waren) , möchten Sie möglicherweise Folgendes tun:

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; 
1

Sie können NSSortDescriptor verwenden. Diese Deskriptoren sind sehr nützlich, da sie sowohl die Sortierung nach mehreren Schlüsseln als auch nach einzelnen Schlüsseln ermöglichen. Die Groß-/Kleinschreibung und Unempfindlichkeit ist ebenfalls leicht zu erreichen. Ich fand ein detailliertes Beispiel HERE

Verwandte Themen