Le meilleur moyen de supprimer les valeurs en double de NSMutableArray dans Objective-C?

147

La meilleure façon de supprimer les valeurs en double ( NSString) NSMutableArraydans Objective-C?

Est-ce la manière la plus simple et la plus appropriée de le faire?

uniquearray = [[NSSet setWithArray:yourarray] allObjects];
Teo Choong Ping
la source
5
Vous souhaiterez peut-être préciser si vous souhaitez éliminer les références au même objet, ou également celles qui sont des objets distincts mais qui ont les mêmes valeurs pour chaque champ.
Amagrammer
N'y a-t-il pas un moyen de faire cela sans créer de copie du tableau?
hfossli
Cette façon est assez simple et peut-être la meilleure. Mais par exemple, cela ne fonctionnera pas pour mon cas - les éléments du tableau ne sont pas des doublons complets et doivent être comparés par une propriété.
Vyachaslav Gerchicov
Essayez ceci pour une fois .. stackoverflow.com/a/38007095/3908884
Rencontrez Doshi

Réponses:

242

Votre NSSetapproche est la meilleure si vous ne vous inquiétez pas de l'ordre des objets, mais encore une fois, si vous ne vous inquiétez pas de l'ordre, alors pourquoi ne les stockez-vous pas dans un NSSetpour commencer?

J'ai écrit la réponse ci-dessous en 2009; en 2011, Apple a ajouté NSOrderedSetiOS 5 et Mac OS X 10.7. Ce qui avait été un algorithme est maintenant composé de deux lignes de code:

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

Si vous vous inquiétez de la commande et que vous utilisez iOS 4 ou une version antérieure, parcourez une copie du tableau:

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];
Jim Puls
la source
53
Si vous avez besoin d'unicité ET d'ordre, utilisez simplement [NSOrderedSet orderedSetWithArray:array];Vous pouvez ensuite récupérer un tableau via array = [orderedSet allObjects];ou simplement utiliser NSOrderedSets au NSArraylieu de.
Regexident
10
La solution de @ Regexident est idéale. Juste besoin de remplacer [orderedSet allObjects]par [orderedSet array]!
entrée
Nice One;) J'aime la réponse qui oblige le développeur à copier et coller sans beaucoup de modifications, c'est la réponse que tout développeur iOS aimera;) @ abo3atef
Abo3atef
Merci mais vous devriez corriger votre exemple. Raison - nous avons généralement NSArrayet devrions créer des temp NSMutableArray. Dans votre exemple, vous travaillez vice versa
Vyachaslav Gerchicov
Tout le monde sait qui est le mieux en vue de supprimer les doublons est cette méthode ( en utilisant NSSet) ou @Simon Whitaker lien empêchent ajouter avant doublon qui est de manière efficace?
Mathi Arasan
78

Je sais que c'est une vieille question, mais il existe un moyen plus élégant de supprimer les doublons NSArray si vous ne vous souciez pas de la commande .

Si nous utilisons des opérateurs d'objets du codage de valeurs clés, nous pouvons le faire:

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

Comme AnthoPak l'a également noté, il est possible de supprimer les doublons en fonction d'une propriété. Un exemple serait:@distinctUnionOfObjects.name

Tiago Almeida
la source
3
Oui, c'est ce que j'utilise aussi! C'est une approche très puissante, que beaucoup de développeurs iOS ne connaissent pas!
Lefteris
1
J'ai été surpris d'apprendre que cela pouvait être possible. Je pensais que de nombreux développeurs iOS ne pouvaient pas savoir cela, c'est pourquoi j'ai décidé d'ajouter cette réponse :)
Tiago Almeida
12
Cela ne maintient pas l'ordre des objets.
Rudolf Adamkovič
2
Ouais, ça casse l'ordre.
Rostyslav Druzhchenko
Notez qu'il peut également être utilisé comme @distinctUnionOfObjects.propertypour supprimer les doublons par propriété d'un tableau d'objets personnalisés. Par exemple@distinctUnionOfObjects.name
AnthoPak
47

Oui, l'utilisation de NSSet est une approche judicieuse.

Pour ajouter à la réponse de Jim Puls, voici une approche alternative pour supprimer les doublons tout en conservant l'ordre:

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

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

C'est essentiellement la même approche que celle de Jim, mais copie les éléments uniques dans un nouveau tableau mutable plutôt que de supprimer les doublons de l'original. Cela le rend légèrement plus efficace dans le cas d'un grand tableau avec beaucoup de doublons (pas besoin de faire une copie de l'ensemble du tableau), et est à mon avis un peu plus lisible.

Notez que dans les deux cas, vérifier si un élément est déjà inclus dans le tableau cible (en utilisant containsObject:dans mon exemple, ou indexOfObject:inRange:dans celui de Jim) ne s'adapte pas bien aux grands tableaux. Ces vérifications s'exécutent en temps O (N), ce qui signifie que si vous doublez la taille du tableau d'origine, chaque vérification prendra deux fois plus de temps à s'exécuter. Puisque vous effectuez la vérification pour chaque objet du tableau, vous exécuterez également plus de ces vérifications plus coûteuses. L'algorithme global (le mien et celui de Jim) s'exécute en temps O (N 2 ), ce qui devient vite coûteux à mesure que le tableau d'origine se développe.

Pour ramener cela au temps O (N), vous pouvez utiliser a NSMutableSetpour stocker un enregistrement des éléments déjà ajoutés au nouveau tableau, car les recherches NSSet sont O (1) plutôt que O (N). En d'autres termes, vérifier si un élément est membre d'un NSSet prend le même temps, quel que soit le nombre d'éléments dans l'ensemble.

Le code utilisant cette approche ressemblerait à ceci:

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

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

Cela semble néanmoins un peu inutile; nous sommes toujours en train de générer un nouveau tableau lorsque la question a clairement indiqué que le tableau d'origine est mutable, nous devrions donc être en mesure de le déduper en place et d'économiser de la mémoire. Quelque chose comme ça:

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++;
    }
}

MISE À JOUR : Yuri Niyazov a souligné que ma dernière réponse s'exécute en fait en O (N 2 ) parce que removeObjectAtIndex:probablement en O (N).

(Il dit "probablement" parce que nous ne savons pas avec certitude comment il est implémenté; mais une implémentation possible est qu'après avoir supprimé l'objet à l'index X, la méthode boucle ensuite sur chaque élément de l'index X + 1 au dernier objet du tableau , en les déplaçant vers l'index précédent. Si tel est le cas, il s'agit bien de la performance O (N).)

Alors que faire? Ça dépend de la situation. Si vous avez un grand tableau et que vous ne vous attendez qu'à un petit nombre de doublons, la déduplication sur place fonctionnera très bien et vous évitera d'avoir à créer un tableau en double. Si vous avez un tableau dans lequel vous vous attendez à beaucoup de doublons, la création d'un tableau séparé et déduppé est probablement la meilleure approche. Ce qu'il faut retenir ici, c'est que la notation big-O ne décrit que les caractéristiques d'un algorithme, elle ne vous dira pas définitivement ce qui est le mieux pour une circonstance donnée.

Simon Whitaker
la source
20

Si vous ciblez iOS 5+ (ce qui couvre tout le monde iOS), meilleure utilisation NSOrderedSet. Il supprime les doublons et conserve l'ordre de votre NSArray.

Fais juste

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Vous pouvez maintenant le reconvertir en un NSArray unique

NSArray *uniqueArray = orderedSet.array;

Ou utilisez simplement le orderSet car il a les mêmes méthodes qu'un NSArray comme objectAtIndex:, firstObjectet ainsi de suite.

Un chèque d'adhésion avec containsest encore plus rapide sur le NSOrderedSetque ce serait sur uneNSArray

Pour plus d'informations, consultez la référence NSOrderedSet

Lukaswelte
la source
Cela a eu mon vote, je les ai tous lus et c'est la meilleure réponse. Je ne peux pas croire que la meilleure réponse soit une boucle manuelle. Oh, ils ont maintenant copié cette réponse.
malhal le
19

Disponible dans OS X v10.7 et versions ultérieures.

Si vous êtes inquiet pour la commande, bonne façon de faire

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

Voici le code de suppression des valeurs en double de NSArray dans l'ordre.

Sultanie
la source
1
allObjects should be array
malhal
7

besoin d'ordre

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

ou pas besoin de commande

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

Ici, j'ai supprimé les valeurs de nom en double de mainArray et stocké le résultat dans 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]];
    }
}
Bibin Joseph
la source
1

Notez que si vous avez un tableau trié, vous n'avez pas besoin de vérifier tous les autres éléments du tableau, juste le dernier élément. Cela devrait être beaucoup plus rapide que de vérifier tous les éléments.

// 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]];
    }
}

Il semble que les NSOrderedSetréponses qui sont également suggérées nécessitent beaucoup moins de code, mais si vous ne pouvez pas utiliser un NSOrderedSetpour une raison quelconque et que vous avez un tableau trié, je pense que ma solution serait la plus rapide. Je ne sais pas comment cela se compare à la vitesse des NSOrderedSetsolutions. Notez également que mon code est en cours de vérification avec isEqualToString:, donc la même série de lettres n'apparaîtra pas plus d'une fois newArray. Je ne sais pas si les NSOrderedSetsolutions supprimeront les doublons en fonction de la valeur ou de l'emplacement de la mémoire.

Mon exemple suppose qu'il sortedSourceArraycontient juste NSStrings, juste NSMutableStrings ou un mélange des deux. Si à la sortedSourceArrayplace contient juste NSNumbers ou juste NSDates, vous pouvez remplacer

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

avec

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

et cela devrait fonctionner parfaitement. Si sortedSourceArraycontient un mélange de NSStrings, NSNumbers et / ou NSDates, il plantera probablement.

GénéralMike
la source
1

Il existe un opérateur d'objets KVC qui offre une solution plus élégante. uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Voici une catégorie NSArray .

Peter
la source
1

Une autre façon simple d'essayer qui n'ajoutera pas de valeur en double avant d'ajouter un objet dans le tableau: -

// Supposons que mutableArray est alloué et initialisé et contient une valeur

if (![yourMutableArray containsObject:someValue])
{
   [yourMutableArray addObject:someValue];
}
Hussain Shabbir
la source
1

Supprimer les valeurs en double de NSMutableArray dans Objective-C

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

Voici le code de suppression des valeurs en double de NSMutable Array. .il fonctionnera pour vous. myArray est votre matrice mutable dont vous souhaitez supprimer les valeurs en double.

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
IHSAN KHAN
la source
0

L'utilisation Orderedsetfera l'affaire. Cela gardera les doublons supprimés du tableau et maintiendra l'ordre ce que les ensembles ne font normalement pas

abhi
la source
-3

utilisez simplement ce code simple:

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

puisque nsset n'autorise pas les valeurs dupliquées et que tous les objets retournent un tableau

Dinesh619
la source
A travaillé pour moi. Tout ce que vous avez à faire est de trier à nouveau votre NSArray car NSSet renvoie un NSArray non trié.
lindinax
Ou utilisez simplement NSOrderedSetinsteed of NSSet.
lindinax