filtrar NSArray en un nuevo NSArray en Objective-C

121

Tengo una NSArrayy me gustaría crear una nueva NSArraycon objetos de la matriz original que cumplan ciertos criterios. El criterio lo decide una función que devuelve a BOOL.

Puedo crear un NSMutableArray, iterar a través de la matriz de origen y copiar sobre los objetos que acepta la función de filtro y luego crear una versión inmutable del mismo.

¿Hay una mejor manera?

lajos
fuente

Respuestas:

140

NSArrayy NSMutableArrayproporcionar métodos para filtrar el contenido de la matriz. NSArrayproporciona filterArrayUsingPredicate: que devuelve una nueva matriz que contiene objetos en el receptor que coinciden con el predicado especificado. NSMutableArrayagrega filterUsingPredicate: que evalúa el contenido del receptor contra el predicado especificado y deja solo los objetos que coinciden. Estos métodos se ilustran en el siguiente ejemplo.

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
fuente
84
Escuché el podcast de Papa Smurf y Papa Smurf dijo que las respuestas deberían estar en StackOverflow para que la comunidad pueda calificarlas y mejorarlas.
willc2
10
@mmalc - Quizás más apropiado, pero ciertamente más conveniente para verlo aquí mismo.
Bryan el
55
NSPredicate está muerto, ¡larga vida a los bloques! cf. Mi respuesta a continuación.
Clay Bridges
¿Qué significa que contiene [c]? Siempre veo el [c] pero no entiendo lo que hace.
user1007522
3
@ user1007522, el [c] hace que el partido no
distinga
104

Hay muchas formas de hacer esto, pero el más seguro es usar [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.
}]];

Creo que es lo más conciso posible.


Rápido:

Para aquellos que trabajan con NSArrays en Swift, puede preferir esta versión aún más concisa:

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

filteres solo un método activado Array( NSArrayestá implícitamente unido a Swift Array). Se necesita un argumento: un cierre que toma un objeto en la matriz y devuelve a Bool. En su cierre, simplemente regrese truepara cualquier objeto que desee en la matriz filtrada.

Stuart
fuente
1
¿Qué papel juegan los enlaces NSDictionary * aquí?
Kaitain
La API requiere enlaces de @Kaitain NSPredicate predicateWithBlock:.
ThomasW
@Kaitain El bindingsdiccionario puede contener enlaces variables para plantillas.
Amin Negm-Awad
Mucho más útil que la respuesta aceptada cuando se trata de objetos complejos :)
Ben Leggiero
47

Basado en una respuesta de Clay Bridges, aquí hay un ejemplo de filtrado usando bloques (cambie el nombre de su yourArrayvariable de matriz y testFuncel nombre de su función de prueba):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];
pckill
fuente
Finalmente, una respuesta no solo menciona el filtrado con bloques, sino que también da un buen ejemplo de cómo hacerlo. Gracias.
Krystian
Me gusta esta respuesta aunque filteredArrayUsingPredicatees más delgado, el hecho de que no uses ningún tipo de predicado oscurece el propósito.
James Perih el
Esta es la forma más moderna de hacer esto. Personalmente, anhelo la antigua taquigrafía "objectsPassingTest" que desapareció de la API en cierto momento. Aún así, esto corre rápido y bien. Me gusta NSPredicate, pero para otras cosas, donde se necesita el martillo más pesado
Motti Shneor
Recibirá un error ahora Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... espera NSIndexSets
anoop4real
@ anoop4real, creo que la causa de la advertencia que mencionaste es que la usaste por error en indexOfObjectPassingTestlugar de hacerlo indexesOfObjectsPassingTest. Fácil de perder, pero gran diferencia :)
pckill
46

Si tiene OS X 10.6 / iOS 4.0 o posterior, probablemente esté mejor con bloques que NSPredicate. Vea -[NSArray indexesOfObjectsPassingTest:]o escriba su propia categoría para agregar un práctico -select:o -filter:método ( ejemplo ).

¿Quieres que alguien más escriba esa categoría, la pruebe, etc.? Echa un vistazo a BlocksKit ( matriz de documentos ). Y hay muchos más ejemplos que se pueden encontrar, por ejemplo, buscando, por ejemplo, "selección de categoría de bloque nsarray" .

Puentes de arcilla
fuente
55
¿Te importaría expandir tu respuesta con un ejemplo? Los sitios web de blogs tienden a morir cuando más los necesita.
Dan Abramov
8
La protección contra la pudrición de enlaces es extraer el código relevante y otras cosas de los artículos vinculados, no agregar más enlaces. Mantenga los enlaces, pero agregue un código de ejemplo.
toolbear
3
@ClayBridges Encontré esta pregunta buscando formas de filtrar el cacao. Dado que su respuesta no contiene ningún código de muestra, me llevó unos 5 minutos buscar en sus enlaces para descubrir que los bloques no lograrán lo que necesito. Si el código hubiera estado en la respuesta, habría tardado unos 30 segundos. Esta respuesta es MUCHO menos útil sin el código real.
N_A
1
@mydogisbox et alia: aquí solo hay 4 respuestas y, por lo tanto, mucho espacio para una respuesta brillante y superior con código de muestra propia. Estoy contento con el mío, así que déjalo en paz.
Clay Bridges
2
mydogisbox tiene razón en este punto; Si todo lo que está haciendo es proporcionar un enlace, no es realmente una respuesta, incluso si agrega más enlaces. Ver meta.stackexchange.com/questions/8231 . La prueba de fuego: ¿Puede su respuesta ser independiente, o requiere hacer clic en los enlaces para tener algún valor? En particular, usted dice "probablemente esté mejor con bloques que NSPredicate", pero realmente no explica por qué.
Robert Harvey
16

Suponiendo que todos sus objetos son del mismo tipo, podría agregar un método como categoría de su clase base que llame a la función que está utilizando para sus criterios. Luego cree un objeto NSPredicate que se refiera a ese método.

En alguna categoría, defina su método que usa su función

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

Luego, donde sea que estés filtrando:

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

Por supuesto, si su función solo se compara con las propiedades accesibles desde su clase, puede ser más fácil convertir las condiciones de la función en una cadena de predicados.

Ashley Clark
fuente
Me gustó esta respuesta, porque es elegante y corta, y atajos la necesidad de volver a aprender cómo formalizar un NSPredicate para lo más básico: acceder a propiedades y método booleano. Incluso creo que el envoltorio no era necesario. Un simple [myArray filterArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; bastaría. ¡Gracias! (También me encantan las alternativas, pero esta es buena).
Motti Shneor
3

NSPredicatees la forma de nextstep de construir condiciones para filtrar una colección ( NSArray, NSSet, NSDictionary).

Por ejemplo, considere dos matrices arry filteredarr:

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

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

el filteredarr seguramente tendrá los elementos que contienen el carácter c solo.

para que sea fácil recordar a aquellos que poco fondo de sql es

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

1) seleccione * de tbl -> colección

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

3) seleccione * de tbl donde column1 como '% a%' ->

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

espero que esto ayude

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

Puedes descargar esta categoría aquí

skywinder
fuente
0

Mira esta biblioteca

https://github.com/BadChoice/Collection

Viene con muchas funciones de matriz fáciles para nunca volver a escribir un bucle

Entonces solo puedes hacer:

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;
}];
Jordi Puigdellívol
fuente
0

La mejor y más fácil manera es crear este método y pasar matriz y valor:

- (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
fuente