Pergunta

Em Cocoa, se eu quiser percorrer um NSMutableArray e remover vários objetos que cabem a determinados critérios, qual é a melhor maneira de fazer isso sem reiniciar o ciclo cada vez que eu remover um objeto?

Obrigado,

Edit: Só para esclarecer - eu estava procurando a melhor maneira, por exemplo, algo mais elegante do que atualizar manualmente o índice eu estou. Por exemplo, em C ++ eu posso fazer;

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}
Foi útil?

Solução

Para maior clareza Eu gosto de fazer um loop inicial onde eu recolher os itens para excluir. Então eu excluí-los. Aqui está um exemplo usando Objective-C 2.0 sintaxe:

NSMutableArray *discardedItems = [NSMutableArray array];

for (SomeObjectClass *item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addObject:item];
}

[originalArrayOfItems removeObjectsInArray:discardedItems];

Então não há nenhuma pergunta sobre se os índices estão sendo atualizados corretamente, ou outros detalhes pouco contabilidade.

Editado para adicionar:

Tem sido observado em outras respostas que a formulação inversa deve ser mais rápido. ou seja Se você percorrer a matriz e compor uma nova matriz de objetos para manter, em vez de objetos para descarte. Isso pode ser verdade (embora o que acontece com a memória e processamento custo de alocar uma nova matriz, e descartando o antigo?), Mas mesmo que seja mais rápido ele pode não ser tão negócio um grande quanto seria para uma implementação ingênua, porque NSArrays não se comportam como matrizes "normais". Eles falam a conversa, mas eles andam um passeio diferente. Veja uma boa análise aqui:

A formulação inversa pode ser mais rápido, mas eu nunca precisei de se preocupar se ele é, porque a formulação acima sempre foi rápido o suficiente para minhas necessidades.

Para mim, a mensagem para levar para casa é para usar qualquer formulação é mais clara para você. Otimizar somente se necessário. Eu pessoalmente acho a mais clara formulação acima, o que é por isso que eu usá-lo. Mas se a formulação inversa é mais claro para você, vá para ele.

Outras dicas

Uma mais variação. Então você começa a legibilidade e bom desempenho:

NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;

for (item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addIndex:index];
    index++;
}

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];

Este é um problema muito simples. Você trás só ITERATE:

for (NSInteger i = array.count - 1; i >= 0; i--) {
   ElementType* element = array[i];
   if ([element shouldBeRemoved]) {
       [array removeObjectAtIndex:i];
   }
}

Este é um padrão muito comum.

Algumas das outras respostas teriam mau desempenho em matrizes muito grandes, porque os métodos como removeObject: e removeObjectsInArray: envolvem fazer uma busca linear do receptor, o que é um desperdício porque você já sabe onde o objeto é. Além disso, qualquer chamada para removeObjectAtIndex: terá que copiar os valores do índice para o final da matriz por um slot de cada vez.

Mais eficiente seria o seguinte:

NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
    if (! shouldRemove(object)) {
        [itemsToKeep addObject:object];
    }
}
[array setArray:itemsToKeep];

Porque nós definir a capacidade de itemsToKeep, não perca tempo copiando valores durante um redimensionamento. Não modificar a matriz no local, por isso estamos livres para usar enumeração rápida. Usando setArray: para substituir o conteúdo da array com itemsToKeep será eficiente. Dependendo do seu código, você pode até mesmo substituir a última linha com:

[array release];
array = [itemsToKeep retain];

Assim, não há nem mesmo uma necessidade para copiar valores, apenas a trocar um ponteiro.

Você pode usar NSPredicate para remover itens da sua matriz mutável. Isso não requer para loops.

Por exemplo, se você tem um NSMutableArray de nomes, você pode criar um predicado como este:

NSPredicate *caseInsensitiveBNames = 
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];

A linha a seguir vai deixar você com uma matriz que contém apenas nomes começando com b.

[namesArray filterUsingPredicate:caseInsensitiveBNames];

Se você tem problemas para criar os predicados que você precisa, usar este desenvolvedor de maçã ligação .

Eu fiz um teste de desempenho usando 4 métodos diferentes. Cada teste iterado todos os elementos de uma matriz 100,000 elemento, e removeu cada item 5. Os resultados não variam muito com / sem otimização. Estes foram feitos em um iPad 4:

(1) removeObjectAtIndex: - 271 ms

(2) removeObjectsAtIndexes: - 1010 ms (porque construir o conjunto índice leva ~ 700 ms, caso contrário este é basicamente o mesmo que chamar removeObjectAtIndex: para cada item)

(3) removeObjects: - 326 ms

(4) fazer uma nova matriz com objetos que passam o teste - 17 ms

Assim, a criação de uma nova matriz é de longe o mais rápido. Os outros métodos são comparáveis, a não ser que o uso removeObjectsAtIndexes: será pior com mais itens para remover, por causa do tempo necessário para construir o conjunto de índice

.

De qualquer utilização de loop contando sobre índices:

for (NSInteger i = array.count - 1; i >= 0; --i) {

ou fazer uma cópia com os objetos que você deseja manter.

Em particular, não use um loop for (id object in array) ou NSEnumerator.

Para iOS 4+ ou OS X 10.6+, a Apple adicionou série passingTest de APIs em NSMutableArray, como – indexesOfObjectsPassingTest:. Uma solução com tal API seria:

NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
    ^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];

Hoje em dia você pode usar a enumeração baseada em blocos inversa. Um código de exemplo simples:

NSMutableArray *array = [@[@{@"name": @"a", @"shouldDelete": @(YES)},
                           @{@"name": @"b", @"shouldDelete": @(NO)},
                           @{@"name": @"c", @"shouldDelete": @(YES)},
                           @{@"name": @"d", @"shouldDelete": @(NO)}] mutableCopy];

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if([obj[@"shouldDelete"] boolValue])
        [array removeObjectAtIndex:idx];
}];

Resultado:

(
    {
        name = b;
        shouldDelete = 0;
    },
    {
        name = d;
        shouldDelete = 0;
    }
)

outra opção com apenas uma linha de código:

[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];

De forma mais declarativa, dependendo dos critérios que correspondem aos itens para remover você poderia usar:

[theArray filterUsingPredicate:aPredicate]

@ Nathan deve ser muito eficiente

Aqui está o caminho mais fácil e limpo. Eu gosto de duplicar minha matriz direita na chamada contagem rápida:

for (LineItem *item in [NSArray arrayWithArray:self.lineItems]) 
{
    if ([item.toBeRemoved boolValue] == YES) 
    {
        [self.lineItems removeObject:item];
    }
}

Desta forma, você enumerar através de uma cópia da matriz sendo excluído, ambos segurando os mesmos objetos. Um NSArray contém ponteiros objeto somente por isso esta é a memória totalmente bem / desempenho sábio.

Adicione os objetos que deseja remover para um segundo array e, após o loop, use -removeObjectsInArray:.

este deve fazê-lo:

    NSMutableArray* myArray = ....;

    int i;
    for(i=0; i<[myArray count]; i++) {
        id element = [myArray objectAtIndex:i];
        if(element == ...) {
            [myArray removeObjectAtIndex:i];
            i--;
        }
    }

espero que isso ajude ...

Por que você não adiciona os objetos a serem removidos para outro NSMutableArray. Quando você está iteração acabado, você pode remover os objetos que você tenha coletado.

Que tal trocar os elementos que deseja excluir com o 'elemento n'th,' elemento n-1'th e assim por diante?

Quando estiver pronto você redimensionar a matriz para 'tamanho anterior - número de swaps'

Se todos os objetos em sua matriz são únicos ou você deseja remover todas as ocorrências de um objeto quando encontrado, você pode rapidamente enumerar em uma cópia da matriz e uso [NSMutableArray removeObject:]. Para remover o objeto do original

NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];

for (NSObject *anObject in myArrayCopy) {
    if (shouldRemove(anObject)) {
        [myArray removeObject:anObject];
    }
}

A resposta de benzado acima é o que você deve fazer para preformace. Em uma das minhas aplicações remover objetos em ordem tomou uma duração de 1 minuto, apenas adicionando a uma nova matriz tomou .023 segundos.

Eu definir uma categoria que me permite filtrar usando um bloco, como este:

@implementation NSMutableArray (Filtering)

- (void)filterUsingTest:(BOOL (^)(id obj, NSUInteger idx))predicate {
    NSMutableIndexSet *indexesFailingTest = [[NSMutableIndexSet alloc] init];

    NSUInteger index = 0;
    for (id object in self) {
        if (!predicate(object, index)) {
            [indexesFailingTest addIndex:index];
        }
        ++index;
    }
    [self removeObjectsAtIndexes:indexesFailingTest];

    [indexesFailingTest release];
}

@end

que pode então ser usado como este:

[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
    return [self doIWantToKeepThisObject:obj atIndex:idx];
}];

Uma implementação mais agradável poderia ser usar o método categoria abaixo em NSMutableArray.

@implementation NSMutableArray(BMCommons)

- (void)removeObjectsWithPredicate:(BOOL (^)(id obj))predicate {
    if (predicate != nil) {
        NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:self.count];
        for (id obj in self) {
            BOOL shouldRemove = predicate(obj);
            if (!shouldRemove) {
                [newArray addObject:obj];
            }
        }
        [self setArray:newArray];
    }
}

@end

O bloco predicado pode ser implementado para fazer o processamento em cada objeto na matriz. Se os retornos de predicados verdade o objeto é removido.

Um exemplo para uma matriz data de remover todas as datas que se encontram no passado:

NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
    NSDate *date = (NSDate *)obj;
    return [date timeIntervalSinceNow] < 0;
}];

Iterating trás-ly era meu favorito por anos, mas por um longo tempo eu nunca encontrei o caso em que o 'mais profundo' (maior número) objeto foi removido primeiro. Momentaneamente antes que o ponteiro se move para o próximo índice que não há nada e ele trava.

forma de benzado é o mais próximo ao que eu faço agora, mas eu nunca percebi que haveria a remodelação pilha após cada remoção.

sob Xcode 6 Isso funciona

NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];

    for (id object in array)
    {
        if ( [object isNotEqualTo:@"whatever"]) {
           [itemsToKeep addObject:object ];
        }
    }
    array = nil;
    array = [[NSMutableArray alloc]initWithArray:itemsToKeep];
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top