Найкращий спосіб видалити повторювані значення з 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.