filtrare NSArray in un nuovo NSArray in Objective-C


121

Ho un NSArraye vorrei crearne uno nuovo NSArraycon oggetti dall'array originale che soddisfano determinati criteri. Il criterio viene deciso da una funzione che restituisce a BOOL.

Posso creare un NSMutableArray, iterare attraverso l'array di origine e copiare sugli oggetti che la funzione di filtro accetta e quindi crearne una versione immutabile.

C'è un modo migliore?

Risposte:


140

NSArraye NSMutableArrayfornire metodi per filtrare il contenuto dell'array. NSArrayfornisce filteredArrayUsingPredicate: che restituisce un nuovo array contenente oggetti nel ricevitore che corrispondono al predicato specificato. NSMutableArrayaggiunge filterUsingPredicate: che valuta il contenuto del destinatario rispetto al predicato specificato e lascia solo gli oggetti che corrispondono. Questi metodi sono illustrati nel seguente esempio.

NSMutableArray *array =
    [NSMutableArray arrayWithObjects:@"Bill", @"Ben", @"Chris", @"Melissa", nil];

NSPredicate *bPredicate =
    [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
NSArray *beginWithB =
    [array filteredArrayUsingPredicate:bPredicate];
// beginWithB contains { @"Bill", @"Ben" }.

NSPredicate *sPredicate =
    [NSPredicate predicateWithFormat:@"SELF contains[c] 's'"];
[array filteredArrayUsingPredicate:sPredicate];
// array now contains { @"Chris", @"Melissa" }

84
Ho ascoltato il podcast di Grande Puffo e Grande Puffo ha detto che le risposte dovrebbero risiedere in StackOverflow in modo che la comunità possa valutarle e migliorarle.
willc2

10
@mmalc - Forse più appropriato, ma sicuramente più comodo da visualizzare qui.
Bryan

5
NSPredicate è morto, lunga vita ai blocchi! cf. la mia risposta di seguito.
Clay Bridges

Cosa significa contiene [c]? Vedo sempre la [c] ma non capisco cosa fa?
user1007522

3
@ user1007522, la [c] rende la corrispondenza senza distinzione tra maiuscole e minuscole
jonbauer

104

Ci sono molti modi per farlo, ma di gran lunga il più accurato è sicuramente usare [NSPredicate predicateWithBlock:]:

NSArray *filteredArray = [array filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
    return [object shouldIKeepYou];  // Return YES for each object you want in filteredArray.
}]];

Penso che sia il più conciso possibile.


Swift:

Per coloro che lavorano con NSArrays in Swift, potresti preferire questa versione ancora più concisa:

nsArray = nsArray.filter { $0.shouldIKeepYou() }

filterè solo un metodo su Array( NSArrayè implicitamente collegato a Swift Array). Richiede un argomento: una chiusura che accetta un oggetto nell'array e restituisce un file Bool. Nella tua chiusura, torna trueper qualsiasi oggetto desideri nell'array filtrato.


1
Che ruolo giocano i binding NSDictionary * qui?
Kaitain

Le associazioni @Kaitain sono richieste NSPredicate predicateWithBlock:dall'API.
ThomasW,

@Kaitain Il bindingsdizionario può contenere associazioni di variabili per i modelli.
Amin Negm-Awad

Risposta molto più utile di quella accettata quando si tratta di oggetti complessi :)
Ben Leggiero

47

Sulla base di una risposta di Clay Bridges, ecco un esempio di filtraggio utilizzando i blocchi (cambia yourArrayil nome della variabile dell'array e testFuncil nome della funzione di test):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];

Infine una risposta non solo menzionando il filtraggio con i blocchi, ma fornendo anche un buon esempio su come farlo. Grazie.
Krystian

Mi piace questa risposta; anche se filteredArrayUsingPredicateè più snello, il fatto che non usi alcun predicato ne oscura lo scopo.
James Perih

Questo è il modo più moderno per farlo. Personalmente desidero ardentemente la vecchia stenografia "objectsPassingTest" che a un certo punto è scomparsa dall'API. Tuttavia questo funziona velocemente e bene. Mi piace NSPredicate - ma per altre cose, dove serve il martello più pesante
Motti Shneor

Riceverai un errore ora Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... si aspetta NSIndexSets
anoop4real il

@ anoop4real, penso che la causa dell'avvertimento che hai citato sia che hai erroneamente usato al indexOfObjectPassingTestposto di indexesOfObjectsPassingTest. Facile da perdere, ma grande differenza :)
pckill

46

Se sei OS X 10.6 / iOS 4.0 o versioni successive, probabilmente stai meglio con i blocchi rispetto a NSPredicate. Visualizza -[NSArray indexesOfObjectsPassingTest:]o scrivi la tua categoria per aggiungere un pratico -select:o un -filter:metodo ( esempio ).

Vuoi che qualcun altro scriva quella categoria, la provi, ecc.? Dai un'occhiata a BlocksKit ( array docs ). E ci sono molti altri esempi che possono essere trovati, ad esempio, cercando "nsarray block category select" .


5
Ti dispiacerebbe espandere la tua risposta con un esempio? I siti web di blog hanno la tendenza a morire quando ne hai più bisogno.
Dan Abramov

8
La protezione contro la decomposizione dei collegamenti consiste nell'estrarre il codice pertinente e quant'altro dagli articoli collegati, non aggiungere altri collegamenti. Mantieni i link, ma aggiungi del codice di esempio.
toolbear

3
@ClayBridges Ho trovato questa domanda cercando modi per filtrare nel cacao. Dal momento che la tua risposta non contiene esempi di codice, mi ci sono voluti circa 5 minuti per esaminare i tuoi collegamenti per scoprire che i blocchi non raggiungono ciò di cui ho bisogno. Se il codice fosse stato nella risposta, ci sarebbero voluti forse 30 secondi. Questa risposta è MOLTO meno utile senza il codice effettivo.
N_A

1
@mydogisbox et alia: ci sono solo 4 risposte qui, e quindi molto spazio per una tua risposta campionata dal codice brillante e superiore. Sono felice del mio, quindi per favore lascialo stare.
Clay Bridges

2
mydogisbox ha ragione su questo punto; se tutto ciò che stai facendo è fornire un link, non è realmente una risposta, anche se aggiungi più link. Vedi meta.stackexchange.com/questions/8231 . La cartina di tornasole: la tua risposta può reggersi da sola o è necessario fare clic sui collegamenti per avere un valore? In particolare, dichiari "probabilmente stai meglio con i blocchi che con NSPredicate", ma non spieghi davvero il motivo.
Robert Harvey

16

Supponendo che i tuoi oggetti siano tutti di un tipo simile, potresti aggiungere un metodo come categoria della loro classe base che chiama la funzione che stai utilizzando per i tuoi criteri. Quindi creare un oggetto NSPredicate che faccia riferimento a quel metodo.

In alcune categorie definisci il tuo metodo che usa la tua funzione

@implementation BaseClass (SomeCategory)
- (BOOL)myMethod {
    return someComparisonFunction(self, whatever);
}
@end

Quindi ovunque filtrerai:

- (NSArray *)myFilteredObjects {
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"myMethod = TRUE"];
    return [myArray filteredArrayUsingPredicate:pred];
}

Ovviamente, se la tua funzione si confronta solo con proprietà raggiungibili dall'interno della tua classe, potrebbe essere più semplice convertire le condizioni della funzione in una stringa di predicato.


Mi è piaciuta questa risposta, perché è graziosa e breve, e scorciatoie la necessità di andare a imparare di nuovo come formalizzare un NSPredicate per la cosa più semplice: accedere alle proprietà e al metodo booleano. Penso persino che l'involucro non fosse necessario. Un semplice [myArray filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; sarebbe sufficiente. Grazie! (Adoro anche le alternative, ma questa è carina).
Motti Shneor

3

NSPredicateè il modo di nextstep di costruire condizioni di filtro di una collezione ( NSArray, NSSet, NSDictionary).

Ad esempio, considera due array arre filteredarr:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

filteredarr = [NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

il filteredarr avrà sicuramente gli elementi che contengono solo il carattere c.

per rendere più facile ricordare chi è il piccolo background di sql

*--select * from tbl where column1 like '%a%'--*

1) seleziona * da tbl -> collection

2) colonna1 come '% a%' ->NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

3) seleziona * da tbl dove colonna1 come '% a%' ->

[NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

Spero che aiuti


2

NSArray + Xh

@interface NSArray (X)
/**
 *  @return new NSArray with objects, that passing test block
 */
- (NSArray *)filteredArrayPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate;
@end

NSArray + Xm

@implementation NSArray (X)

- (NSArray *)filteredArrayPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
{
    return [self objectsAtIndexes:[self indexesOfObjectsPassingTest:predicate]];
}

@end

Puoi scaricare questa categoria qui


0

Controlla questa libreria

https://github.com/BadChoice/Collection

Viene fornito con molte semplici funzioni di array per non scrivere mai più un ciclo

Quindi puoi semplicemente fare:

NSArray* youngHeroes = [self.heroes filter:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];

o

NSArray* oldHeroes = [self.heroes reject:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];

0

Il modo migliore e più semplice è creare questo metodo e passare array e valore:

- (NSArray *) filter:(NSArray *)array where:(NSString *)key is:(id)value{
    NSMutableArray *temArr=[[NSMutableArray alloc] init];
    for(NSDictionary *dic in self)
        if([dic[key] isEqual:value])
            [temArr addObject:dic];
    return temArr;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.