Лучший способ удалить дубликаты значений из NSMutableArray в Objective-C? - PullRequest
143 голосов
/ 22 июня 2009

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

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

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

Ответы [ 14 ]

235 голосов
/ 22 июня 2009

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

Я написал ответ ниже в 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];
77 голосов
/ 15 октября 2013

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

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

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

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

47 голосов
/ 11 июня 2012

Да, использование 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), вы можете использовать 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 описывает только характеристики алгоритма, но не может сказать вам окончательно, что лучше для любого конкретного обстоятельства.

19 голосов
/ 19 июня 2013

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

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

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

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

NSArray *uniqueArray = orderedSet.array;

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

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

Для дополнительной проверки NSOrderedSet Reference

19 голосов
/ 14 мая 2013

Доступно в OS X v10.7 и новее.

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

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

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

6 голосов
/ 02 июня 2015

нужен заказ

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 голосов
/ 12 мая 2016

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

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

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

if (![yourMutableArray containsObject:someValue])
{
   [yourMutableArray addObject:someValue];
}
1 голос
/ 20 марта 2015

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

1 голос
/ 14 августа 2013

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

// 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 с, просто NSMutableString с или их комбинацию. Если вместо sortedSourceArray содержится NSNumber с или NSDate с, вы можете заменить

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

с

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

и должно работать отлично. Если sortedSourceArray содержит смесь NSString с, NSNumber с и / или NSDate с, вероятно, произойдет сбой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...