Cara terbaik untuk menghapus nilai duplikat dari NSMutableArray di Objective-C?


147

Cara terbaik untuk menghapus nilai duplikat ( NSString) dari NSMutableArraydi Objective-C?

Apakah ini cara termudah dan tepat untuk melakukannya?

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

5
Anda mungkin ingin mengklarifikasi apakah Anda ingin menghilangkan referensi ke objek yang sama persis, atau juga yang merupakan objek yang berbeda tetapi memiliki nilai yang sama untuk setiap bidang.
Amagrammer

Apakah tidak ada cara untuk melakukan ini tanpa membuat salinan array?
hfossli

Cara ini cukup mudah dan mungkin yang terbaik. Tapi misalnya itu tidak akan berfungsi untuk kasus saya - item array bukan duplikat penuh dan harus dibandingkan dengan satu properti.
Vyachaslav Gerchicov

Jawaban:


242

NSSetPendekatan Anda adalah yang terbaik jika Anda tidak khawatir tentang urutan objek, tetapi sekali lagi, jika Anda tidak khawatir tentang urutannya, lalu mengapa Anda tidak menyimpannya di NSSetawal?

Saya menulis jawaban di bawah ini pada tahun 2009; pada tahun 2011, Apple ditambahkan NSOrderedSetke iOS 5 dan Mac OS X 10.7. Apa yang tadinya merupakan algoritma sekarang adalah dua baris kode:

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];
NSArray *arrayWithoutDuplicates = [orderedSet array];

Jika Anda khawatir tentang pesanan dan Anda menjalankan iOS 4 atau lebih lama, lewati salinan array:

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
Jika Anda membutuhkan keunikan DAN pesanan, cukup gunakan [NSOrderedSet orderedSetWithArray:array];Anda kemudian dapat kembali array melalui array = [orderedSet allObjects];atau hanya menggunakan NSOrderedSets bukan NSArraydi tempat pertama.
Regexident

10
Solusi @ Regexident sangat ideal. Hanya perlu diganti [orderedSet allObjects]dengan [orderedSet array]!
inket

Nice One;) Saya suka jawaban yang membuat pengembang menyalin & menempel tanpa banyak modifikasi, ini adalah jawaban yang disukai setiap pengembang iOS;) @ abo3atef
Abo3atef

Terima kasih, tetapi Anda harus memperbaiki contoh Anda. Alasan - kami biasanya memiliki NSArraydan harus membuat temp NSMutableArray. Dalam contoh Anda, Anda bekerja sebaliknya
Vyachaslav Gerchicov

NSSetAdakah yang tahu pandangan terbaik untuk menghapus duplikat apakah metode ini (menggunakan ) atau tautan @Simon Whitaker mencegah sebelum menambahkan nilai duplikat yang merupakan cara efisien?
Mathi Arasan

78

Saya tahu ini adalah pertanyaan lama, tetapi ada cara yang lebih elegan untuk menghapus duplikat di NSArray jika Anda tidak peduli dengan pesanan .

Jika kita menggunakan Object Operator dari Key Value Coding kita bisa melakukan ini:

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

Sebagai AnthoPak juga mencatat adalah mungkin untuk menghapus duplikat berdasarkan properti. Contohnya adalah:@distinctUnionOfObjects.name


3
Ya, ini juga yang saya gunakan! Ini adalah pendekatan yang sangat kuat, yang tidak diketahui oleh banyak pengembang iOS!
Lefteris

1
Saya terkejut ketika saya mengetahui bahwa ini bisa terjadi. Saya pikir banyak pengembang iOS tidak dapat mengetahui hal ini, itulah mengapa saya memutuskan untuk menambahkan jawaban ini :)
Tiago Almeida

12
Ini tidak menjaga urutan objek.
Rudolf Adamkovič

2
Ya, itu melanggar perintah.
Rostyslav Druzhchenko

Perhatikan bahwa ini juga dapat digunakan @distinctUnionOfObjects.propertyuntuk menghapus duplikat berdasarkan properti dari array objek kustom. Misalnya@distinctUnionOfObjects.name
AnthoPak

47

Ya, menggunakan NSSet adalah pendekatan yang masuk akal.

Untuk menambah jawaban Jim Puls, berikut adalah pendekatan alternatif untuk menghapus duplikat sambil mempertahankan pesanan:

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

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

Ini pada dasarnya pendekatan yang sama dengan Jim tetapi menyalin item unik ke array yang bisa berubah baru daripada menghapus duplikat dari aslinya. Ini membuatnya sedikit lebih efisien dalam hal array besar dengan banyak duplikat (tidak perlu membuat salinan seluruh array), dan menurut saya sedikit lebih mudah dibaca.

Perhatikan bahwa dalam kedua kasus, memeriksa untuk melihat apakah item sudah termasuk dalam array target (menggunakan containsObject:dalam contoh saya, atau indexOfObject:inRange:dalam Jim) tidak skala dengan baik untuk array besar. Pemeriksaan tersebut berjalan dalam waktu O (N), yang berarti bahwa jika Anda menggandakan ukuran array asli maka setiap pemeriksaan akan memakan waktu dua kali lebih lama untuk dijalankan. Karena Anda melakukan pemeriksaan untuk setiap objek dalam array, Anda juga akan menjalankan lebih banyak dari pemeriksaan yang lebih mahal itu. Algoritma keseluruhan (baik milik saya maupun Jim) berjalan dalam waktu O (N 2 ), yang menjadi cepat mahal seiring dengan tumbuhnya array asli.

Untuk menurunkannya ke waktu O (N), Anda bisa menggunakan a NSMutableSetuntuk menyimpan catatan item yang sudah ditambahkan ke array baru, karena pencarian NSSet adalah O (1) daripada O (N). Dengan kata lain, memeriksa untuk melihat apakah suatu elemen adalah anggota NSSet membutuhkan waktu yang sama terlepas dari berapa banyak elemen dalam set.

Kode yang menggunakan pendekatan ini akan terlihat seperti ini:

NSMutableArray *unique = [NSMutableArray array];
NSMutableSet *seen = [NSMutableSet set];

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

Ini tampaknya masih sedikit boros; kami masih membuat array baru ketika pertanyaan menjelaskan bahwa array asli bisa berubah, jadi kita harus dapat menghapusnya di tempat dan menghemat memori. Sesuatu seperti ini:

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 : Yuri Niyazov menunjukkan bahwa jawaban terakhir saya benar-benar berjalan di O (N 2 ) karena removeObjectAtIndex:mungkin berjalan dalam waktu O (N).

(Dia mengatakan "mungkin" karena kita tidak tahu pasti bagaimana itu diterapkan; tetapi satu kemungkinan implementasi adalah bahwa setelah menghapus objek pada indeks X metode kemudian loop melalui setiap elemen dari indeks X + 1 ke objek terakhir dalam array , memindahkan mereka ke indeks sebelumnya. Jika itu masalahnya maka itu memang kinerja O (N).)

Jadi, apa yang harus dilakukan? Itu tergantung situasi. Jika Anda memiliki array besar dan Anda hanya mengharapkan sejumlah kecil duplikat maka de-duplikasi di tempat akan bekerja dengan baik dan menghemat Anda harus membangun array duplikat. Jika Anda memiliki array di mana Anda mengharapkan banyak duplikat kemudian membangun array terpisah, de-duped mungkin merupakan pendekatan terbaik. Yang dibawa pulang di sini adalah bahwa notasi-O besar hanya menggambarkan karakteristik suatu algoritma, itu tidak akan memberi tahu Anda secara definitif mana yang terbaik untuk keadaan apa pun.


20

Jika Anda menargetkan iOS 5+ (yang mencakup seluruh dunia iOS), gunakan yang terbaik NSOrderedSet. Ini menghapus duplikat dan mempertahankan urutan NSArray.

Kerjakan saja

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Anda sekarang dapat mengubahnya kembali ke NSArray unik

NSArray *uniqueArray = orderedSet.array;

Atau cukup gunakan orderedetet karena memiliki metode yang sama seperti NSArray objectAtIndex:, firstObjectdan sebagainya.

Pemeriksaan keanggotaan dengan containsbahkan lebih cepat NSOrderedSetdaripada padaNSArray

Untuk checkout lebih lanjut, Referensi NSOrderedSet


Ini mendapat suara saya, saya membaca semuanya dan itu adalah jawaban terbaik. Tidak percaya jawaban teratas adalah loop manual. Oh, mereka sekarang menyalin jawaban ini.
Malhal

19

Tersedia dalam OS X v10.7 dan yang lebih baru.

Jika Anda khawatir tentang pesanan, cara yang benar untuk dilakukan

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

Berikut adalah kode untuk menghapus nilai duplikat dari NSArray dalam Pesanan.


1
allObjects harus berupa array
malhal

7

butuh pesanan

NSArray *yourarray = @[@"a",@"b",@"c"];
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourarray];
NSArray *arrayWithoutDuplicates = [orderedSet array];
NSLog(@"%@",arrayWithoutDuplicates);

atau tidak perlu dipesan

NSSet *set = [NSSet setWithArray:yourarray];
NSArray *arrayWithoutOrder = [set allObjects];
NSLog(@"%@",arrayWithoutOrder);

3

Di sini saya menghapus nilai nama duplikat dari mainArray dan menyimpan hasil di 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

Perhatikan bahwa jika Anda memiliki array yang diurutkan, Anda tidak perlu memeriksa setiap item dalam array, hanya item terakhir. Ini harus jauh lebih cepat daripada memeriksa semua item.

// 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]];
    }
}

Sepertinya NSOrderedSetjawaban yang juga disarankan memerlukan kode jauh lebih sedikit, tetapi jika Anda tidak dapat menggunakan NSOrderedSetkarena suatu alasan, dan Anda memiliki array yang diurutkan, saya percaya solusi saya akan menjadi yang tercepat. Saya tidak yakin bagaimana membandingkannya dengan kecepatan NSOrderedSetsolusi. Perhatikan juga bahwa kode saya sedang diperiksa isEqualToString:, sehingga seri huruf yang sama tidak akan muncul lebih dari satu kali newArray. Saya tidak yakin apakah NSOrderedSetsolusi akan menghapus duplikat berdasarkan nilai atau berdasarkan lokasi memori.

Contoh saya anggap sortedSourceArrayberisi hanya NSStrings, hanya NSMutableStrings, atau campuran keduanya. Jika sortedSourceArraysebaliknya hanya berisi NSNumbers atau hanya NSDates, Anda dapat mengganti

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

dengan

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

dan itu harus bekerja dengan sempurna. Jika sortedSourceArrayberisi campuran NSStrings, NSNumbers, dan / atau NSDates, itu mungkin akan macet.


1

Ada Operator Objek KVC yang menawarkan solusi yang lebih elegan. uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Ini adalah kategori NSArray .


1

Satu lagi cara sederhana yang dapat Anda coba yang tidak akan menambah Nilai rangkap sebelum menambahkan objek dalam array: -

// Asumsikan mutableArray dialokasikan dan diinisialisasi dan berisi beberapa nilai

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

1

Hapus nilai duplikat dari NSMutableArray di Objective-C

NSMutableArray *datelistArray = [[NSMutableArray alloc]init];
for (Student * data in fetchStudentDateArray)
{
    if([datelistArray indexOfObject:data.date] == NSNotFound)
    [datelistArray addObject:data.date];
}

0

Berikut adalah kode untuk menghapus nilai duplikat dari NSMutable Array. .itu akan bekerja untuk Anda. myArray adalah Array Mutable Anda yang ingin Anda hapus nilai duplikat ..

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

Menggunakan Orderedsetakan melakukan trik. Ini akan menjaga agar duplikat yang dihapus dari array dan mempertahankan urutan yang biasanya tidak dilakukan


-3

cukup gunakan kode sederhana ini:

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

karena nsset tidak mengizinkan nilai duplikat dan semua objek mengembalikan array


Bekerja untukku. Yang harus Anda lakukan adalah menyortir NSArray Anda lagi saat NSSet mengembalikan NSArray yang tidak disortir.
lindinax

Atau cukup gunakan NSOrderedSetinsteed of NSSet.
lindinax
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.