filtrer NSArray dans un nouveau NSArray dans Objective-C

121

J'ai un NSArrayet je voudrais créer un nouveau NSArrayavec des objets du tableau d'origine qui répondent à certains critères. Le critère est décidé par une fonction qui renvoie un BOOL.

Je peux créer un NSMutableArray, parcourir le tableau source et copier sur les objets que la fonction de filtre accepte, puis en créer une version immuable.

Y a-t-il un meilleur moyen?

lajos
la source

Réponses:

140

NSArrayet NSMutableArrayfournir des méthodes pour filtrer le contenu du tableau. NSArrayfournit filteredArrayUsingPredicate: qui renvoie un nouveau tableau contenant des objets dans le récepteur qui correspondent au prédicat spécifié. NSMutableArrayajoute filterUsingPredicate: qui évalue le contenu du récepteur par rapport au prédicat spécifié et ne laisse que les objets qui correspondent. Ces méthodes sont illustrées dans l'exemple suivant.

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" }
lajos
la source
84
J'ai écouté le podcast de Papa Smurf et Papa Smurf a dit que les réponses devraient vivre dans StackOverflow afin que la communauté puisse les évaluer et les améliorer.
willc2
10
@mmalc - Peut-être plus approprié, mais certainement plus pratique pour le voir ici.
Bryan
5
NSPredicate est mort, vive les blocs! cf. ma réponse ci-dessous.
Clay Bridges
Que signifie contient [c]? Je vois toujours le [c] mais je ne comprends pas ce qu'il fait?
user1007522
3
@ user1007522, le [c] rend le match insensible à la casse
jonbauer
104

Il existe de nombreuses façons de le faire, mais de loin le plus simple est sûrement d'utiliser [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.
}]];

Je pense que c'est à peu près aussi concis que possible.


Rapide:

Pour ceux qui travaillent avec NSArrays dans Swift, vous préférerez peut-être cette version encore plus concise:

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

filterest juste une méthode sur Array( NSArrayest implicitement reliée à celle de Swift Array). Il prend un argument: une fermeture qui prend un objet dans le tableau et renvoie un Bool. Dans votre conclusion, revenez simplement truepour tous les objets que vous voulez dans le tableau filtré.

Stuart
la source
1
Quel est le rôle des liaisons NSDictionary * ici?
Kaitain
Les liaisons @Kaitain sont requises par l' NSPredicate predicateWithBlock:API.
ThomasW
@Kaitain Le bindingsdictionnaire peut contenir des liaisons de variables pour les modèles.
Amin Negm-Awad
Réponse beaucoup plus utile que la réponse acceptée lorsqu'il s'agit d'objets complexes :)
Ben Leggiero
47

Basé sur une réponse de Clay Bridges, voici un exemple de filtrage à l'aide de blocs (changez le nom de votre yourArrayvariable de tableau et testFuncle nom de votre fonction de test):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];
pckill
la source
Enfin une réponse ne mentionnant pas seulement le filtrage avec des blocs, mais donnant également un bon exemple sur la façon de le faire. Merci.
Krystian
J'aime cette réponse; même s'il filteredArrayUsingPredicateest plus léger, le fait que vous n'utilisiez aucun prédicat en obscurcit le but.
James Perih
C'est la manière la plus moderne de le faire. Personnellement, j'aspire au vieux raccourci "objectsPassingTest" qui a disparu de l'API à un moment donné. Pourtant, cela fonctionne vite et bien. J'aime NSPredicate - mais pour d'autres choses, où le marteau le plus lourd est nécessaire
Motti Shneor
Vous obtiendrez une erreur maintenant Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... il attend NSIndexSets
anoop4real le
@ anoop4real, je pense que la cause de l'avertissement que vous avez mentionné est que vous avez utilisé par erreur à la indexOfObjectPassingTestplace de indexesOfObjectsPassingTest. Facile à manquer, mais grosse différence :)
pckill
46

Si vous êtes OS X 10.6 / iOS 4.0 ou version ultérieure, vous êtes probablement mieux avec des blocs que NSPredicate. Voir -[NSArray indexesOfObjectsPassingTest:]ou écrire votre propre catégorie pour ajouter une pratique -select:ou une -filter:méthode ( exemple ).

Vous voulez que quelqu'un d'autre écrive cette catégorie, la teste, etc.? Découvrez BlocksKit ( documentation sur le tableau ). Et il y a beaucoup plus d'exemples à trouver, par exemple, en recherchant par exemple "nsarray block category select" .

Ponts d'argile
la source
5
Pourriez-vous développer votre réponse avec un exemple? Les sites Web de blogs ont tendance à mourir lorsque vous en avez le plus besoin.
Dan Abramov
8
La protection contre la pourriture des liens consiste à extraire le code pertinent et ainsi de suite des articles liés, et non à ajouter plus de liens. Conservez les liens, mais ajoutez un exemple de code.
toolbear
3
@ClayBridges J'ai trouvé cette question à la recherche de moyens de filtrer le cacao. Étant donné que votre réponse ne contient aucun exemple de code, il m'a fallu environ 5 minutes pour fouiller dans vos liens afin de découvrir que les blocs ne répondront pas à mes besoins. Si le code avait été dans la réponse, cela aurait pris peut-être 30 secondes. Cette réponse est BEAUCOUP moins utile sans le code réel.
N_A
1
@mydogisbox et alia: il n'y a que 4 réponses ici, et donc beaucoup de place pour une réponse brillante et supérieure échantillonnée par code de votre choix. Je suis content du mien, alors laissez-le tranquille.
Clay Bridges
2
mydogisbox a raison sur ce point; si vous ne faites que fournir un lien, ce n'est pas vraiment une réponse, même si vous ajoutez plus de liens. Voir meta.stackexchange.com/questions/8231 . Le test décisif: votre réponse peut-elle être autonome ou nécessite-t-elle de cliquer sur les liens pour avoir une quelconque valeur? En particulier, vous dites "vous êtes probablement mieux avec des blocs que NSPredicate", mais vous n'expliquez pas vraiment pourquoi.
Robert Harvey
16

En supposant que vos objets sont tous d'un type similaire, vous pouvez ajouter une méthode en tant que catégorie de leur classe de base qui appelle la fonction que vous utilisez pour vos critères. Créez ensuite un objet NSPredicate qui fait référence à cette méthode.

Dans certaines catégories, définissez votre méthode qui utilise votre fonction

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

Ensuite, partout où vous filtrerez:

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

Bien sûr, si votre fonction se compare uniquement aux propriétés accessibles à partir de votre classe, il peut être plus simple de convertir les conditions de la fonction en une chaîne de prédicat.

Ashley Clark
la source
J'ai aimé cette réponse, car elle est gracieuse et courte, et raccourcit la nécessité de réapprendre à formaliser un NSPredicate pour la chose la plus élémentaire - accéder aux propriétés et à la méthode booléenne. Je pense même que l'emballage n'était pas nécessaire. Un simple [myArray filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; suffirait. Merci! (J'adore aussi les alternatives, mais celle-ci est sympa).
Motti Shneor
3

NSPredicateest la façon de construire nextstep état pour filtrer une collection ( NSArray, NSSet, NSDictionary).

Par exemple, considérons deux tableaux arret filteredarr:

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

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

le filteredarr aura sûrement les éléments qui contiennent le caractère c seul.

pour se souvenir facilement de ceux qui ont peu d'arrière-plan SQL

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

1) sélectionnez * de tbl -> collection

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

3) sélectionnez * de tbl où column1 comme '% a%' ->

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

J'espère que ça aide

Durai Amuthan.H
la source
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

Vous pouvez télécharger cette catégorie ici

Skywinder
la source
0

Découvrez cette bibliothèque

https://github.com/BadChoice/Collection

Il est livré avec de nombreuses fonctions de tableau faciles pour ne plus jamais écrire une boucle

Vous pouvez donc simplement faire:

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

ou

NSArray* oldHeroes = [self.heroes reject:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];
Jordi Puigdellívol
la source
0

Le meilleur moyen est de créer cette méthode et de transmettre le tableau et la valeur:

- (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;
}
jalmatari
la source