A melhor maneira de remover valores duplicados do NSMutableArray no Objective-C?


147

A melhor maneira de remover valores duplicados ( NSString) do NSMutableArrayObjective-C?

Essa é a maneira mais fácil e correta de fazer isso?

uniquearray = [[NSSet setWithArray:yourarray] allObjects];

5
Você pode esclarecer se deseja eliminar as referências exatamente ao mesmo objeto ou também aquelas que são objetos distintos, mas que têm os mesmos valores para cada campo.
Amagrammer

Não existe uma maneira de fazer isso sem criar nenhuma cópia da matriz?
Hfossli

Desta forma, é bastante fácil e talvez melhor. Mas, por exemplo, não funcionará no meu caso - os itens da matriz não são duplicatas completas e devem ser comparados por uma propriedade.
Vyachaslav Gerchicov

Respostas:


242

Sua NSSetabordagem é a melhor se você não está preocupado com a ordem dos objetos, mas, novamente, se você não está preocupado com a ordem, por que não está armazenando-os em umNSSet para começar?

Eu escrevi a resposta abaixo em 2009; em 2011, a Apple adicionou NSOrderedSetao iOS 5 e Mac OS X 10.7. O que havia sido um algoritmo agora são duas linhas de código:

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];
NSArray *arrayWithoutDuplicates = [orderedSet array];

Se você estiver preocupado com o pedido e estiver executando o iOS 4 ou anterior, faça um loop sobre uma cópia da matriz:

NSArray *copy = [mutableArray copy];
NSInteger index = [copy count] - 1;
for (id object in [copy reverseObjectEnumerator]) {
    if ([mutableArray indexOfObject:object inRange:NSMakeRange(0, index)] != NSNotFound) {
        [mutableArray removeObjectAtIndex:index];
    }
    index--;
}
[copy release];

53
Se você precisar de exclusividade e ordem, basta usar. [NSOrderedSet orderedSetWithArray:array];Você pode recuperar uma matriz via array = [orderedSet allObjects];ou apenas usar NSOrderedSets em vez de NSArrayem primeiro lugar.
Regexident

10
A solução da @ Regexident é ideal. Só precisa substituir [orderedSet allObjects]com [orderedSet array]!
inket

Nice one;) Eu como a resposta que fazem o desenvolvedor copiar e colar, sem um monte de modificações, esta é a resposta que cada desenvolvedor iOS vai gostar;) @ abo3atef
Abo3atef

Obrigado, mas você deve dar um exemplo. Razão - geralmente temos NSArraye devemos criar temp NSMutableArray. No seu exemplo você trabalha vice-versa
Vyachaslav Gerchicov

Alguém sabe qual é a melhor maneira de remover duplicado, esse método (usando NSSet) ou o link @Simon Whitaker impede antes de adicionar valor duplicado, o que é uma maneira eficiente?
Mathi Arasan

78

Sei que essa é uma pergunta antiga, mas existe uma maneira mais elegante de remover duplicatas em um NSArray caso você não se importe com o pedido .

Se usarmos Operadores de Objetos da Key Value Coding , podemos fazer o seguinte:

uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];

Como a AnthoPak também observou, é possível remover duplicatas com base em uma propriedade. Um exemplo seria:@distinctUnionOfObjects.name


3
Sim, é isso que eu também uso! Essa é uma abordagem muito poderosa, que muitos desenvolvedores do iOS não conhecem!
Lefteris

1
Fiquei surpreso quando soube que isso era possível. Pensei que muitos desenvolvedores iOS não podia saber este é por isso que eu decidi adicionar esta resposta :)
Tiago Almeida

12
Isso não mantém a ordem dos objetos.
Rudolf Adamkovič

2
Sim, isso quebra a ordem.
Rostyslav Druzhchenko

Observe que também pode ser usado como @distinctUnionOfObjects.propertypara remover duplicatas por propriedade de uma matriz de objetos personalizados. Por exemplo@distinctUnionOfObjects.name
AnthoPak 28/03

47

Sim, o uso do NSSet é uma abordagem sensata.

Para adicionar à resposta de Jim Puls, aqui está uma abordagem alternativa para remover duplicatas, mantendo a ordem:

// Initialise a new, empty mutable array 
NSMutableArray *unique = [NSMutableArray array];

for (id obj in originalArray) {
    if (![unique containsObject:obj]) {
        [unique addObject:obj];
    }
}

É essencialmente a mesma abordagem que a de Jim, mas copia itens exclusivos para uma nova matriz mutável, em vez de excluir duplicatas do original. Isso o torna um pouco mais eficiente de memória no caso de uma matriz grande com muitas duplicatas (não é necessário fazer uma cópia de toda a matriz) e, na minha opinião, é um pouco mais legível.

Observe que, em ambos os casos, verificar se um item já está incluído na matriz de destino (usando containsObject:no meu exemplo ouindexOfObject:inRange: no de Jim) não é adequado para grandes matrizes. Essas verificações são executadas no tempo O (N), o que significa que, se você dobrar o tamanho da matriz original, cada verificação levará o dobro do tempo para ser executada. Como você está fazendo a verificação de cada objeto na matriz, também estará executando mais dessas verificações mais caras. O algoritmo geral (tanto o meu quanto o de Jim) é executado no tempo O (N 2 ), que fica caro rapidamente à medida que a matriz original cresce.

Para reduzir o tempo O (N), você pode usar um NSMutableSet para armazenar um registro de itens já adicionados à nova matriz, pois as pesquisas do NSSet são O (1) e não O (N). Em outras palavras, verificar se um elemento é membro de um NSSet leva o mesmo tempo, independentemente de quantos elementos estiverem no conjunto.

Código usando essa abordagem seria algo como isto:

NSMutableArray *unique = [NSMutableArray array];
NSMutableSet *seen = [NSMutableSet set];

for (id obj in originalArray) {
    if (![seen containsObject:obj]) {
        [unique addObject:obj];
        [seen addObject:obj];
    }
}

Isso ainda parece um pouco inútil; ainda estamos gerando uma nova matriz quando a pergunta deixou claro que a matriz original é mutável, para que possamos redimensioná-la no local e economizar memória. Algo assim:

NSMutableSet *seen = [NSMutableSet set];
NSUInteger i = 0;

while (i < [originalArray count]) {
    id obj = [originalArray objectAtIndex:i];

    if ([seen containsObject:obj]) {
        [originalArray removeObjectAtIndex:i];
        // NB: we *don't* increment i here; since
        // we've removed the object previously at
        // index i, [originalArray objectAtIndex:i]
        // now points to the next object in the array.
    } else {
        [seen addObject:obj];
        i++;
    }
}

ATUALIZAÇÃO : Yuri Niyazov apontou que a minha última resposta na verdade é executado em O (N 2 ), porque removeObjectAtIndex:provavelmente é executado em O (N) tempo.

(Ele diz "provavelmente" porque não sabemos ao certo como é implementado; mas uma implementação possível é que, após excluir o objeto no índice X, o método percorre todos os elementos do índice X + 1 até o último objeto na matriz , movendo-os para o índice anterior. Se for esse o caso, esse é realmente o desempenho de O (N).)

Então o que fazer? Depende da situação. Se você possui uma matriz grande e espera apenas um pequeno número de duplicatas, a desduplicação no local funcionará perfeitamente e poupará a criação de uma matriz duplicada. Se você tem uma matriz em que espera muitas duplicatas, a criação de uma matriz separada e com dupagem é provavelmente a melhor abordagem. A conclusão aqui é que a notação big-O apenas descreve as características de um algoritmo, não lhe dirá definitivamente qual é o melhor para qualquer circunstância.


20

Se você está segmentando o iOS 5 ou superior (que abrange todo o mundo do iOS), use melhor NSOrderedSet. Ele remove duplicatas e mantém a ordem do seu NSArray.

Apenas faça

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Agora você pode convertê-lo novamente em um NSArray exclusivo

NSArray *uniqueArray = orderedSet.array;

Ou apenas use o ordersSet porque ele possui os mesmos métodos, como um NSArray objectAtIndex:, firstObjecte assim por diante.

Uma verificação de associação containsé ainda mais rápida no NSOrderedSetque seria em umNSArray

Para mais informações, consulte a Referência NSOrderedSet


Este foi o meu voto, li todos e é a melhor resposta. Não posso acreditar que a resposta principal é um loop manual. Oh, eles já copiaram esta resposta.
malhal

19

Disponível no OS X v10.7 e posterior.

Se você está preocupado com o pedido, a maneira certa de fazer

NSArray *no = [[NSOrderedSet orderedSetWithArray:originalArray]allObjects];

Aqui está o código para remover valores duplicados do NSArray no pedido.


1
allObjects deve ser array
malhal 14/01

7

precisa de ordem

NSArray *yourarray = @[@"a",@"b",@"c"];
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourarray];
NSArray *arrayWithoutDuplicates = [orderedSet array];
NSLog(@"%@",arrayWithoutDuplicates);

ou não precisa de ordem

NSSet *set = [NSSet setWithArray:yourarray];
NSArray *arrayWithoutOrder = [set allObjects];
NSLog(@"%@",arrayWithoutOrder);

3

Aqui eu removi valores de nome duplicados do mainArray e armazene o resultado no NSMutableArray (listOfUsers)

for (int i=0; i<mainArray.count; i++) {
    if (listOfUsers.count==0) {
        [listOfUsers addObject:[mainArray objectAtIndex:i]];

    }
   else if ([[listOfUsers valueForKey:@"name" ] containsObject:[[mainArray objectAtIndex:i] valueForKey:@"name"]])
    {  
       NSLog(@"Same object");
    }
    else
    {
        [listOfUsers addObject:[mainArray objectAtIndex:i]];
    }
}

1

Observe que, se você tiver uma matriz classificada, não precisará verificar todos os outros itens da matriz, apenas o último item. Isso deve ser muito mais rápido do que comparar com todos os itens.

// sortedSourceArray is the source array, already sorted
NSMutableArray *newArray = [[NSMutableArray alloc] initWithObjects:[sortedSourceArray objectAtIndex:0]];
for (int i = 1; i < [sortedSourceArray count]; i++)
{
    if (![[sortedSourceArray objectAtIndex:i] isEqualToString:[sortedSourceArray objectAtIndex:(i-1)]])
    {
        [newArray addObject:[tempArray objectAtIndex:i]];
    }
}

Parece que as NSOrderedSetrespostas sugeridas também exigem muito menos código, mas se você não puder usar um NSOrderedSetpor algum motivo e tiver uma matriz classificada, acredito que minha solução seria a mais rápida. Não tenho certeza de como ele se compara à velocidade das NSOrderedSetsoluções. Observe também que meu código está sendo verificado isEqualToString:, para que a mesma série de letras não apareça mais de uma vez newArray. Não tenho certeza se oNSOrderedSet soluções removerão duplicatas com base no valor ou no local da memória.

Meu exemplo assume que sortedSourceArraycontém apenas NSStrings, apenasNSMutableString s ou uma mistura dos dois. Se, em sortedSourceArrayvez disso, contiver apenas NSNumbers ou apenas NSDates, você poderá substituir

if (![[sortedSourceArray objectAtIndex:i] isEqualToString:[sortedSourceArray objectAtIndex:(i-1)]])

com

if ([[sortedSourceArray objectAtIndex:i] compare:[sortedSourceArray objectAtIndex:(i-1)]] != NSOrderedSame)

e deve funcionar perfeitamente. Se sortedSourceArraycontiver uma mistura de NSStrings, se NSNumber/ ou NSDates, provavelmente ocorrerá um erro fatal.


1

Há um operador de objeto KVC que oferece uma solução mais elegante. uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Aqui está uma categoria do NSArray .


1

Mais uma maneira simples de testar, que não adicionará Valor duplicado antes de adicionar objeto na matriz: -

// Suponha que mutableArray esteja alocado e inicialize e contenha algum valor

if (![yourMutableArray containsObject:someValue])
{
   [yourMutableArray addObject:someValue];
}

1

Remover valores duplicados do NSMutableArray no Objective-C

NSMutableArray *datelistArray = [[NSMutableArray alloc]init];
for (Student * data in fetchStudentDateArray)
{
    if([datelistArray indexOfObject:data.date] == NSNotFound)
    [datelistArray addObject:data.date];
}

0

Aqui está o código para remover valores duplicados do NSMutable Array. . funcionará para você. myArray é sua matriz mutável que você deseja remover valores duplicados.

for(int j = 0; j < [myMutableArray count]; j++){
    for( k = j+1;k < [myMutableArray count];k++){
    NSString *str1 = [myMutableArray objectAtIndex:j];
    NSString *str2 = [myMutableArray objectAtIndex:k];
    if([str1 isEqualToString:str2])
        [myMutableArray removeObjectAtIndex:k];
    }
 } // Now print your array and will see there is no repeated value

0

Usar Orderedsetfará o truque. Isso manterá as duplicatas removidas da matriz e manterá a ordem que os conjuntos normalmente não fazem


-3

basta usar este código simples:

NSArray *hasDuplicates = /* (...) */;
NSArray *noDuplicates = [[NSSet setWithArray: hasDuplicates] allObjects];

como o nsset não permite valores duplicados e todos os objetos retornam uma matriz


Trabalhou para mim. Tudo o que você precisa fazer é classificar o NSArray novamente, pois o NSSet retorna um NSArray não classificado.
Lindinax

Ou simplesmente use NSOrderedSetinsteed de NSSet.
Lindinax
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.