Лучший способ удалить дубликаты значений из 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 Ссылка


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

19

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

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

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

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


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, он, вероятно, потерпит крах.


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 не допускает повторяющихся значений и все объекты возвращают массив


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

Или просто используйте NSOrderedSetзастрахованный NSSet.
Линдинакс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.