filtrando o NSArray em um novo NSArray no Objective-C

121

Eu tenho um NSArraye gostaria de criar um novo NSArraycom objetos da matriz original que atendam a certos critérios. O critério é decidido por uma função que retorna a BOOL.

Posso criar NSMutableArray, percorrer a matriz de origem e copiar os objetos que a função de filtro aceita e, em seguida, criar uma versão imutável dela.

Existe uma maneira melhor?

lajos
fonte

Respostas:

140

NSArraye NSMutableArrayforneça métodos para filtrar o conteúdo da matriz. NSArrayfornece o método filterArrayUsingPredicate: que retorna uma nova matriz que contém objetos no receptor que correspondem ao predicado especificado. NSMutableArrayadiciona filterUsingPredicate: que avalia o conteúdo do destinatário em relação ao predicado especificado e deixa apenas os objetos correspondentes. Esses métodos são ilustrados no exemplo a seguir.

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
fonte
84
Ouvi o podcast do Papa Smurf e o Papa Smurf disse que as respostas devem estar no StackOverflow para que a comunidade possa classificá-las e melhorá-las.
willc2 17/09/09
10
@mmalc - Talvez mais adequado, mas certamente mais conveniente para vê-lo aqui.
Bryan
5
NSPredicate está morto, viva blocos! cf. minha resposta abaixo.
Clay Bridges
O que significa contains [c]? Eu sempre vejo o [c], mas não entendo o que ele faz?
user1007522
3
@ user1007522, o [c] faz com que o jogo de maiúsculas e minúsculas
jonbauer
104

Existem várias maneiras de fazer isso, mas, de longe, o mais puro é certamente 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.
}]];

Eu acho que é o mais conciso possível.


Rápido:

Para quem trabalha com NSArrays no Swift, você pode preferir esta versão ainda mais concisa:

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

filteré apenas um método ativado Array( NSArrayé implicitamente vinculado ao de Swift Array). É preciso um argumento: um fechamento que pega um objeto na matriz e retorna a Bool. No seu encerramento, basta retornar truepara os objetos que você deseja na matriz filtrada.

Stuart
fonte
1
Qual o papel das ligações do NSDictionary * aqui?
precisa saber é
As ligações @Kaitain são necessárias pela NSPredicate predicateWithBlock:API.
Thomasw
@ Kaitain O bindingsdicionário pode conter ligações variáveis ​​para modelos.
Amin Negm-Awad
Muito mais útil do que resposta aceita quando se lida com objetos complexos :)
Ben leggiero
47

Com base em uma resposta de Clay Bridges, aqui está um exemplo de filtragem usando blocos (mude yourArraypara o nome da variável da matriz e testFuncpara o nome da sua função de teste):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];
pckill
fonte
Finalmente, uma resposta não apenas mencionando a filtragem com blocos, mas também dando um bom exemplo de como fazê-lo. Obrigado.
Krystian
Eu gosto desta resposta; mesmo sendo filteredArrayUsingPredicatemais enxuto, o fato de você não usar nenhum predicado obscurece o objetivo.
precisa saber é o seguinte
Essa é a maneira mais moderna de fazer isso. Pessoalmente, anseio pela abreviação antiga "objectsPassingTest" que desapareceu da API em determinado momento. Ainda assim, isso é rápido e bom. Eu gosto NSPredicate - mas para outras coisas, onde é necessário o martelo mais pesado
Motti Shneor
Agora você receberá um erro Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... ele espera NSIndexSets
anoop4real
@ anoop4real, acho que a causa do aviso que você mencionou é que você usou por engano em indexOfObjectPassingTestvez de indexesOfObjectsPassingTest. Fácil de perder, mas grande diferença :)
pckill
46

Se você possui o OS X 10.6 / iOS 4.0 ou posterior, provavelmente está melhor com blocos do que o NSPredicate. Veja -[NSArray indexesOfObjectsPassingTest:]ou escreva sua própria categoria para adicionar um método -select:ou -filter:método útil ( exemplo ).

Quer que outra pessoa escreva essa categoria, teste-a etc.? Confira o BlocksKit ( documentos da matriz ). E há muitos outros exemplos a serem encontrados, digamos, pesquisando, por exemplo, "nsarray block category select" .

Pontes de argila
fonte
5
Você se importaria de expandir sua resposta com um exemplo? Os sites de blog tendem a morrer quando você mais precisa deles.
Dan Abramov
8
A proteção contra a podridão do link é extrair o código relevante e outros enfeites dos artigos vinculados, não adicionar mais links. Mantenha os links, mas adicione algum código de exemplo.
toolbear
3
@ClayBridges Encontrei esta pergunta procurando maneiras de filtrar o cacau. Como sua resposta não possui exemplos de código, demorei cerca de 5 minutos para pesquisar seus links para descobrir que os blocos não atingirão o que eu preciso. Se o código estivesse na resposta, levaria talvez 30 segundos. Esta resposta é MUITO menos útil sem o código real.
N_A
1
@mydogisbox et alia: existem apenas 4 respostas aqui e, portanto, muito espaço para uma resposta brilhante e superior, com amostra de código de sua preferência. Estou feliz com o meu, então por favor, deixe em paz.
argila Pontes
2
mydogisbox está certo neste ponto; se tudo o que você está fazendo é fornecer um link, não é realmente uma resposta, mesmo se você adicionar mais links. Consulte meta.stackexchange.com/questions/8231 . O teste decisivo: sua resposta pode se sustentar por si própria ou exige que os cliques nos links tenham algum valor? Em particular, você declara "provavelmente está melhor com blocos que o NSPredicate", mas na verdade não explica o porquê.
21412 Robert Harvey
16

Supondo que seus objetos sejam todos de um tipo semelhante, você pode adicionar um método como uma categoria de sua classe base que chama a função que você está usando para seus critérios. Em seguida, crie um objeto NSPredicate que se refere a esse método.

Em alguma categoria, defina seu método que usa sua função

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

Então, onde quer que você esteja filtrando:

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

Obviamente, se sua função for comparada apenas às propriedades alcançáveis ​​de sua classe, pode ser mais fácil converter as condições da função em uma sequência de predicados.

Ashley Clark
fonte
Gostei dessa resposta, porque é graciosa e curta, e diminui a necessidade de aprender novamente como formalizar um NSPredicate para a coisa mais básica - acessar propriedades e método booleano. Até acho que o invólucro não era necessário. Um simples [myArray filterArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; seria suficiente. Obrigado! (Eu amo as alternativas também, mas essa é legal).
Motti Shneor 18/02/19
3

NSPredicateé o caminho de nextstep de construir condições para filtrar uma coleção ( NSArray, NSSet, NSDictionary).

Por exemplo, considere duas matrizes arre filteredarr:

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

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

a área filtrada certamente terá os itens que contêm o caractere c sozinho.

para facilitar a lembrança daqueles que têm pouco background sql

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

1) selecione * de tbl -> coleção

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

3) selecione * de tbl onde coluna1 como '% a%' ->

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

Eu espero que isso ajude

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

Você pode baixar esta categoria aqui

skywinder
fonte
0

Fazer check-out desta biblioteca

https://github.com/BadChoice/Collection

Ele vem com muitas funções fáceis de array para nunca escrever um loop novamente

Então você pode fazer:

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
fonte
0

A maneira melhor e mais fácil é criar esse método And Pass Array And Value:

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