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

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

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

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

Ответ 1

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

Я написал ответ ниже в 2009 году; в 2011 году Apple добавила NSOrderedSet в iOS 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];

Ответ 3

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

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

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

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

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

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

Чтобы получить это до O (N), вы можете использовать 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++;
    }
}

UPDATE: Юрий Ниязов указал, что мой последний ответ на самом деле выполняется в O (N 2), потому что removeObjectAtIndex:, вероятно, работает в O (N) времени.

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

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

Ответ 4

Доступно в OS X v10.7 и более поздних версиях.

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

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

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

Ответ 5

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

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

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

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

NSArray *uniqueArray = orderedSet.array;

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

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

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

Ответ 6

нужен заказ

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);

Ответ 7

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

Ответ 8

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

// 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 содержит только NSString s, просто NSMutableString s или их сочетание. Если sortedSourceArray вместо этого содержит только NSNumber или просто NSDate s, вы можете заменить

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

с

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

и он должен работать отлично. Если sortedSourceArray содержит комбинацию NSString s, NSNumber s и/или NSDate s, это, вероятно, сбой.

Ответ 9

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

Ответ 10

Еще один простой способ попробовать, который не будет добавлять повторяющееся значение перед добавлением объекта в массив: -

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

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

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

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