2010-02-03 7 views
8

Sono un po 'un NSSortDescriptor n00b. Penso, però, che sia lo strumento giusto per ciò che devo fare:Aiutare l'ordinamento di un NSArray attraverso due proprietà (con NSSortDescriptor?)

Ho un NSArray costituito da oggetti con i tasti, diciamo "nome" e "ora". Invece di verbalizzare esso, ecco un esempio:

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 

Così i valori sono ordinati per "tempo" e raggruppati per "nome". A viene prima perché ha il valore temporale più piccolo e tutti i valori di A vengono dopo l'altro. Poi arriva C, ha il secondo valore temporale più piccolo tra tutti i suoi valori. Ho indicato i valori che determinano come vengono ordinati i nomi; all'interno di ciascun gruppo di nomi, l'ordinamento avviene in base al tempo.

Come si ottiene dall'ingresso all'uscita NSArray nel modo più efficiente? (CPU e memoria, non necessariamente in base al codice.) Come posso costruire NSSortDescriptors per questo, o usare un altro metodo? Non voglio giocare da solo a meno che non sia il modo più efficiente.

risposta

17

Il metodo sortedArrayUsingDescriptors:NSArray fa la maggior parte di quello che vi serve:

Il primo descrittore specifica il percorso della chiave primaria da utilizzare nella selezione contenuti del ricevitore. Qualsiasi descrittore successivo viene utilizzato per perfezionare ulteriormente l'ordinamento degli oggetti con valori duplicati. Vedi NSSortDescriptor per ulteriori informazioni.

è necessario Alcuni filtraggio con NSPredicate troppo:

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

Non ho idea se questo è il metodo più efficiente, ma fino a quando si ha ragione di credere che sta causando problemi non c'è bisogno di preoccuparsi le implicazioni sulla performance. È l'ottimizzazione prematura. Non mi preoccuperei delle prestazioni di questo metodo. Devi fidarti del framework altrimenti finirai per riscriverlo (quindi minare il punto del framework) a causa di una paranoia infondata.

+0

Questo non risponde alla domanda: la mia situazione è più complicata del semplice ordinamento per nome. – Jaanus

+0

@ Jaanus. Ohhh, vedo. Non ho notato che l'ordine dei gruppi dipende dal tempo. –

+0

@ Jaanus. Ho aggiornato il codice in modo che risponda effettivamente alla domanda! –

3

vorrei creare una nuova classe chiamata ItemGroup, e quindi aggiungere un Ivar aggiuntivo chiamato group alla classe articolo:

@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 

Poi, si potrebbe procedere come segue:

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

Questo codice esegue il ciclo attraverso la matrice non ordinata, tenendo traccia del tempo minimo per ciascun gruppo e anche impostando il gruppo per ogni articolo. Con questo completo, è sufficiente ordinamento group.time e 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]; 

E che dovrebbe fare il trucco!

UPDATE: Si noti che si potrebbe ottenere molto prestazioni migliori se tu fossi in grado di assegnare i gruppi dritto fuori dal cancello. Qualcosa di simile a questo:

@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 

Ora, se si mantiene un elenco di gruppi da qualche parte (si potrebbe anche andare in un array da qualche parte, se necessario):

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

E invece di impostare i nomi dei vostri elementi di dati, è possibile impostare il loro gruppo:

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

Questo semplificherebbe notevolmente il ciclo utilizzato per impostare gli orari di gruppo:

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

E, se si voleva davvero essere ardente veloce su di esso, si potrebbe anche modificare il setter di proprietà per la vostra proprietà time per impostare i tempi di gruppo al volo:

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

Si noti che dovresti assicurarti che group sia stato impostato prima di impostare l'ora. Con questo in atto, non avresti bisogno di quel ciclo di smistamento. Gli ordinatoriDescriptors sarebbero sufficienti.

+0

Capisco questo, ma non penso che questo risponda alla domanda. Non sto smistando per nome, ho bisogno di classificare e raggruppare i nomi in base al valore temporale più piccolo per un determinato nome. – Jaanus

+0

Ah. Ora vedo. Questo è molto più interessante. –

+0

Definite il tipo di oggetto che viene memorizzato nell'array originale? È una classe personalizzata a cui puoi aggiungere gli ivar? –

1

Ho passato un po 'di codice (non ho provato a eseguirlo o ci ho passato sopra, quindi potrebbero esserci un paio di errori, ma ha l'idea generale) di fare quello che stai cercando. Per quanto riguarda le prestazioni, probabilmente non sarà il massimo se inizi a correre enormi quantità di dati. Sono sicuro che c'è un modo migliore per farlo, ma mi è sembrato di farlo nel modo più semplice come una risposta "provvisoria".

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; 

} 

Alla fine si ritroverà con quello che stai cercando in sortedarray.

+0

Penso che questo stia facendo più o meno lo stesso della risposta di Benedict Cohen, ma da solo invece di ordinare e predicati. – Jaanus

20

La mia soluzione è:

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

Si può provare

+0

Questo è risolto i miei problemi, flessibile per aggiungere più sortDescriptor. –

0

Se si deve fare di più complicato di smistamento del giusto "ascendente" può prendersi cura di (diciamo tipo NSString come se fossero carri) , si potrebbe voler fare qualcosa di simile:

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

È possibile utilizzare NSSortDescriptor. Questi descrittori sono molto utili in quanto consentono di eseguire più ordinamenti di chiavi e anche l'ordinamento a chiave singola. La case sensitive e l'insensibilità sono facilmente ottenibili. Ho trovato un esempio dettagliato HERE

Problemi correlati