Лучший способ удалить дубликаты значений из NSMutableArray в Objective-C?

147

Лучший способ удалить дубликаты значений ( NSString) NSMutableArrayв Objective-C?

Это самый простой и правильный способ сделать это?

uniquearray = [[NSSet setWithArray:yourarray] allObjects];
Тео Чунг Пинг
источник
5
Возможно, вы захотите уточнить, хотите ли вы удалить ссылки на один и тот же объект, а также ссылки, которые являются разными объектами, но имеют одинаковые значения для каждого поля.
Amagrammer
Разве нет способа сделать это без создания какой-либо копии массива?
hfossli
Этот способ достаточно легкий и, возможно, лучший. Но, например, в моем случае это не сработает - элементы массива не являются полными дубликатами и должны сравниваться по одному свойству.
Вячеслав Герчиков
Попробуйте на этот раз ... stackoverflow.com/a/38007095/3908884
Встречайте Доши

Ответы:

242

Ваш NSSetподход является лучшим, если вы не беспокоитесь о порядке объектов, но опять же, если вы не беспокоитесь о порядке, то почему вы не храните их в NSSetначале?

Я написал ответ ниже в 2009 году; В 2011 году Apple добавила NSOrderedSetiOS 5 и Mac OS X 10.7. То, что раньше было алгоритмом, теперь представляет собой две строки кода:

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

Если вас беспокоит порядок, и вы работаете на iOS 4 или более ранней версии, переберите копию массива:

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
Если вам нужна уникальность и порядок, просто используйте [NSOrderedSet orderedSetWithArray:array];Вы можете затем вернуть массив через array = [orderedSet allObjects];или просто использовать NSOrderedSets вместо NSArrayпервого.
Regexident
10
Решение @ Regexident идеально. Просто нужно заменить [orderedSet allObjects]на [orderedSet array]!
Inket
Nice One;) Мне нравится ответ, который заставляет разработчика копировать и вставлять без большого количества модификаций, это ответ, который понравится каждому разработчику iOS;) @ abo3atef
Abo3atef
Спасибо, но вы должны исправить пример. Причина - мы обычно имеем NSArrayи должны создавать временные NSMutableArray. В вашем примере вы работаете наоборот
Вячеслав Герчиков
Кто-нибудь знает, что является лучшим видом для удаления дубликатов, этот метод (использование NSSet) или ссылка @Simon Whitaker предотвращают перед добавлением дубликатов, что является эффективным способом?
Мати Арасан
78

Я знаю, что это старый вопрос, но есть более элегантный способ удалить дубликаты, NSArray если вы не заботитесь о порядке .

Если мы используем объектные операторы из Key Value Coding, мы можем сделать это:

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

Как отметил AnthoPak, можно удалить дубликаты на основе свойства. Примером может быть:@distinctUnionOfObjects.name

Тиаго Алмейда
источник
3
Да, это то, что я тоже использую! Это очень мощный подход, о котором многие разработчики iOS не знают!
Лефтерис
1
Я был удивлен, когда узнал, что это возможно. Я думал, что многие разработчики iOS не могли знать об этом, поэтому я решил добавить этот ответ :)
Tiago Almeida
12
Это не поддерживает порядок объектов.
Рудольф Адамкович
2
Да, это нарушает порядок.
Ростислав Дружченко
Обратите внимание, что его также можно использовать @distinctUnionOfObjects.propertyдля удаления дубликатов по свойству массива пользовательских объектов. Например@distinctUnionOfObjects.name
AnthoPak
47

Да, использование NSSet - разумный подход.

Чтобы добавить к ответу Джима Пулса, вот альтернативный подход к удалению дубликатов при сохранении порядка:

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

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

По сути, это тот же подход, что и у Джима, но он копирует уникальные элементы в новый изменяемый массив, а не удаляет дубликаты из оригинала. Это делает его немного более эффективным в отношении памяти в случае большого массива с большим количеством дубликатов (не нужно делать копию всего массива) и, на мой взгляд, немного более читабельным.

Обратите внимание, что в любом случае проверка того, включен ли элемент в целевой массив (используется containsObject:в моем примере или indexOfObject:inRange:в Jim's), плохо масштабируется для больших массивов. Эти проверки выполняются за время O (N), что означает, что если вы удвоите размер исходного массива, то для каждой проверки потребуется вдвое больше времени. Так как вы делаете проверку для каждого объекта в массиве, вы также будете выполнять больше этих более дорогих проверок. Общий алгоритм (как мой, так и Джима) выполняется за время O (N 2 ), что быстро растет с ростом исходного массива.

Чтобы получить это время O (N), вы можете использовать a NSMutableSetдля хранения записи элементов, уже добавленных в новый массив, так как NSSet ищет O (1), а не O (N). Другими словами, проверка того, является ли элемент членом NSSet, занимает одно и то же время, независимо от того, сколько элементов в наборе.

Код, использующий этот подход, будет выглядеть примерно так:

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

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

Это все еще кажется немного расточительным; мы все еще генерируем новый массив, когда вопрос прояснил, что исходный массив является изменяемым, поэтому мы должны иметь возможность его дедупликации и сэкономить память. Что-то вроде этого:

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

ОБНОВЛЕНИЕ : Юрий Ниязов указал, что мой последний ответ на самом деле работает в O (N 2 ), потому что, removeObjectAtIndex:вероятно, работает в O (N) времени.

(Он говорит «вероятно», потому что мы не знаем наверняка, как это реализовано; но одна из возможных реализаций состоит в том, что после удаления объекта с индексом X метод затем проходит по каждому элементу от индекса X + 1 до последнего объекта в массиве , перемещая их в предыдущий индекс. Если это так, то это действительно производительность O (N).)

Так что делать? Это зависит от ситуации. Если у вас большой массив и вы ожидаете только небольшое количество дубликатов, то дедупликация на месте будет работать нормально и избавит вас от необходимости создавать дублирующий массив. Если у вас есть массив, в котором вы ожидаете много дубликатов, то, вероятно, лучше всего создать отдельный дедуплицированный массив. Вывод здесь заключается в том, что нотация big-O описывает только характеристики алгоритма, он не будет однозначно сообщать вам, какой вариант лучше всего подходит для любого конкретного обстоятельства.

Саймон Уитакер
источник
20

Если вы нацелены на iOS 5+ (что охватывает весь мир iOS), лучше всего использовать NSOrderedSet. Он удаляет дубликаты и сохраняет ваш порядок NSArray.

Просто сделать

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Теперь вы можете преобразовать его обратно в уникальный NSArray

NSArray *uniqueArray = orderedSet.array;

Или просто используйте orderSet, потому что он имеет те же методы, что и NSArray objectAtIndex:, firstObjectи так далее.

Проверка членства с помощью containsеще быстрее, NSOrderedSetчем наNSArray

Для дополнительной проверки NSOrderedSet Ссылка

lukaswelte
источник
Это получил мой голос, я прочитал их все, и это лучший ответ. Не могу поверить, что главный ответ - это ручной цикл. О, они сейчас скопировали этот ответ.
Малхал
19

Доступный в OS X v10.7 и позже.

Если вы беспокоитесь о заказе, правильный способ сделать

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

Вот код удаления значений дубликатов из NSArray в порядке.

Sultania
источник
1
allObjects должен быть массивом
malhal
7

нужен заказ

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

или не нужен заказ

NSSet *set = [NSSet setWithArray:yourarray];
NSArray *arrayWithoutOrder = [set allObjects];
NSLog(@"%@",arrayWithoutOrder);
Майк
источник
3

Здесь я удалил повторяющиеся значения имени из mainArray и сохранил результат в 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

Обратите внимание, что если у вас есть отсортированный массив, вам не нужно проверять все остальные элементы в массиве, только последний элемент. Это должно быть намного быстрее, чем проверка по всем пунктам.

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

Похоже, что для NSOrderedSetответов, которые также предлагаются, требуется гораздо меньше кода, но если NSOrderedSetпо какой-то причине вы не можете использовать его , и у вас есть отсортированный массив, я считаю, что мое решение будет самым быстрым. Я не уверен, как это сравнивается со скоростью NSOrderedSetрешений. Также обратите внимание, что мой код проверяется isEqualToString:, поэтому одна и та же серия букв не будет появляться более одного раза newArray. Я не уверен, что NSOrderedSetрешения удалят дубликаты на основе значения или в зависимости от места в памяти.

В моем примере предполагается, что sortedSourceArrayсодержит только NSStrings, просто NSMutableStrings или их комбинацию. Если sortedSourceArrayвместо этого содержит только NSNumbers или просто NSDates, вы можете заменить

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

с участием

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

и это должно работать отлично. Если sortedSourceArrayсодержит смесь NSStrings, NSNumbers и / или NSDates, он, вероятно, потерпит крах.

GeneralMike
источник
1

Есть оператор объектов KVC, который предлагает более элегантное решение. uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Вот категория NSArray .

Питер
источник
1

Еще один простой способ, который вы можете попробовать, который не добавит дубликат Value перед добавлением объекта в массив:

// Предположим, что mutableArray выделен и инициализирован и содержит некоторое значение

if (![yourMutableArray containsObject:someValue])
{
   [yourMutableArray addObject:someValue];
}
Хуссейн Шаббир
источник
1

Удалите повторяющиеся значения из NSMutableArray в Objective-C

NSMutableArray *datelistArray = [[NSMutableArray alloc]init];
for (Student * data in fetchStudentDateArray)
{
    if([datelistArray indexOfObject:data.date] == NSNotFound)
    [datelistArray addObject:data.date];
}
Арвинд Патель
источник
0

Вот код удаления значений дубликатов из NSMutable Array. Это будет работать для вас. myArray - это ваш изменяемый массив, который вы хотите удалить дублирующиеся значения.

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

Использование Orderedsetсделает свое дело. Это сохранит удаление дубликатов из массива и будет поддерживать порядок, который обычно не выполняется

абхи
источник
-3

просто используйте этот простой код:

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

так как nsset не допускает повторяющихся значений и все объекты возвращают массив

Dinesh619
источник
Работал на меня. Все, что вам нужно сделать, это снова отсортировать NSArray, поскольку NSSet возвращает несортированный NSArray.
Линдинакс
Или просто используйте NSOrderedSetзастрахованный NSSet.
Линдинакс