Найкращий спосіб видалити повторювані значення з NSMutableArray в Objective-C?


147

Найкращий спосіб видалити повторювані значення ( NSString) з NSMutableArrayоб'єктива-C?

Це найпростіший і правильний спосіб зробити це?

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

5
Ви можете уточнити, чи потрібно виключати посилання на той самий об’єкт, або також на ті, що є окремими об'єктами, але мають однакові значення для кожного поля.
— Amagrammer

Чи не існує способу це зробити, не створивши жодної копії масиву?
— hfossli

Цей спосіб досить простий і, можливо, найкращий. Але, наприклад, це не спрацює для мого випадку - елементи масиву не є повними дублікатами і їх слід порівнювати однією властивістю.
— Вячаслав Герчич

Відповіді:


242

Ваш 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];

53
Якщо вам потрібна унікальність І замовлення, просто використовуйте. [NSOrderedSet orderedSetWithArray:array];Потім ви можете отримати масив через array = [orderedSet allObjects];або просто використовувати NSOrderedSets, а не NSArrayв першу чергу.
— Regexident

10
@ Рішення Regexident ідеально. Просто потрібно замінити [orderedSet allObjects]на [orderedSet array]!
— інкет

Nice One;) Мені подобається відповідь, яка змушує розробника копіювати та вставляти без великої кількості модифікацій. Це відповідь, яка сподобається кожному розробнику iOS;) @ abo3atef
— Abo3atef

Дякую, але вам слід налагодити приклад. Причина - ми зазвичай маємо NSArrayі повинні створювати темп NSMutableArray. У вашому прикладі ви працюєте навпаки
— Вячаслав Герчичов

Хтось знає, який найкращий вид видалити дублікат, це метод (за допомогою NSSet) або посилання @Simon Whitaker запобігти, перш ніж додати значення дубліката, що є ефективним способом?
— Маті Арасан

78

Я знаю, що це давнє питання, але є більш елегантний спосіб видалення дублікатів, NSArray якщо ви не дбаєте про замовлення .

Якщо ми використовуємо Оператори об’єктів з кодування ключових значень, ми можемо це зробити:

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:в Джимі), не підходить для великих масивів. Ці перевірки виконуються в 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 має бути масив
— маль

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

Ще один простий спосіб ви можете спробувати, який не додасть значення дубліката перед додаванням об'єкта в масив: -

// Припустимо, 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. .це буде працювати для вас. 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.
— ліндінакс

Або просто використовувати NSOrderedSetinsteed з NSSet.
— ліндінакс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.