Cách tốt nhất để xóa các giá trị trùng lặp khỏi NSMutableArray trong Objective-C?


147

Cách tốt nhất để xóa các giá trị trùng lặp ( NSString) khỏi NSMutableArraytrong Objective-C?

Đây có phải là cách dễ nhất và đúng để làm điều đó?

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

5
Bạn có thể muốn làm rõ liệu bạn muốn loại bỏ các tham chiếu đến cùng một đối tượng hay cả những đối tượng là các đối tượng riêng biệt nhưng có cùng giá trị cho mọi trường.
Amagrammer

Không có cách nào để làm điều này mà không tạo ra bất kỳ bản sao nào của mảng?
hfossli

Cách này là đủ dễ dàng và có thể tốt nhất. Nhưng ví dụ, nó sẽ không hoạt động trong trường hợp của tôi - các mục của mảng không phải là bản sao đầy đủ và nên được so sánh bởi một thuộc tính.
Vyachaslav Gerchicov

Hãy thử điều này một lần .. stackoverflow.com/a/38007095/3908884
Gặp Doshi

Câu trả lời:


242

NSSetCách tiếp cận của bạn là tốt nhất nếu bạn không lo lắng về thứ tự của các đối tượng, nhưng một lần nữa, nếu bạn không lo lắng về thứ tự, vậy tại sao bạn không lưu trữ chúng trong mộtNSSet để bắt đầu?

Tôi đã viết câu trả lời dưới đây vào năm 2009; vào năm 2011, Apple đã thêm NSOrderedSetvào iOS 5 và Mac OS X 10.7. Những gì đã là một thuật toán bây giờ là hai dòng mã:

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

Nếu bạn lo lắng về thứ tự và bạn đang chạy trên iOS 4 trở về trước, hãy lặp lại một bản sao của mảng:

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
Nếu bạn cần tính duy nhất VÀ thứ tự, chỉ cần sử dụng [NSOrderedSet orderedSetWithArray:array];Bạn có thể lấy lại một mảng thông qua array = [orderedSet allObjects];hoặc chỉ sử dụng NSOrderedSets thay vì NSArrayở vị trí đầu tiên.
Regexident

10
@ Giải pháp của Regexident là lý tưởng. Chỉ cần thay thế [orderedSet allObjects]bằng [orderedSet array]!
inket

Nice One;) Tôi thích câu trả lời khiến nhà phát triển sao chép và dán mà không cần sửa đổi nhiều, đây là câu trả lời mà mọi nhà phát triển iOS sẽ thích;) @ abo3atef
Abo3atef

Cảm ơn nhưng bạn nên sửa bạn ví dụ. Lý do - chúng ta thường có NSArrayvà nên tạo temp NSMutableArray. Trong ví dụ của bạn, bạn làm việc ngược lại
Vyachaslav Gerchicov

Bất cứ ai cũng biết chế độ xem tốt nhất để loại bỏ trùng lặp là phương pháp này (sử dụng NSSet) hoặc liên kết @Simon Whitaker ngăn chặn trước khi thêm giá trị trùng lặp đó là cách hiệu quả?
Mathi Arasan

78

Tôi biết đây là một câu hỏi cũ, nhưng có một cách thanh lịch hơn để loại bỏ các bản sao trong một NSArray nếu bạn không quan tâm đến thứ tự .

Nếu chúng tôi sử dụng Toán tử đối tượng từ Mã hóa giá trị khóa, chúng tôi có thể thực hiện việc này:

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

Như AnthoPak cũng lưu ý rằng có thể loại bỏ các bản sao dựa trên một tài sản. Một ví dụ sẽ là:@distinctUnionOfObjects.name


3
Vâng, đây là những gì tôi sử dụng quá! Đây là một cách tiếp cận rất mạnh mẽ, điều mà rất nhiều nhà phát triển iOS không biết!
Lefteris

1
Tôi đã rất ngạc nhiên khi biết rằng điều này có thể xảy ra. Tôi nghĩ rằng nhiều nhà phát triển iOS không thể biết đây là lý do tại sao tôi quyết định thêm câu trả lời này :)
Tiago Almeida

12
Điều này không duy trì trật tự của các đối tượng.
Rudolf Adamkovič

2
Vâng, nó phá vỡ trật tự.
Rostyslav Druzhchenko

Lưu ý rằng nó cũng có thể được sử dụng như @distinctUnionOfObjects.propertyđể loại bỏ các bản sao theo thuộc tính của một mảng các đối tượng tùy chỉnh. Ví dụ@distinctUnionOfObjects.name
AnthoPak

47

Có, sử dụng NSSet là một cách tiếp cận hợp lý.

Để thêm vào câu trả lời của Jim Puls, đây là một cách tiếp cận khác để tước bỏ các bản sao trong khi vẫn giữ trật tự:

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

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

Về cơ bản, đó là cách tiếp cận tương tự như của Jim nhưng sao chép các mục độc đáo vào một mảng có thể thay đổi mới thay vì xóa các bản sao khỏi bản gốc. Điều này làm cho bộ nhớ hiệu quả hơn một chút trong trường hợp một mảng lớn có nhiều bản sao (không cần tạo một bản sao của toàn bộ mảng), và theo tôi thì dễ đọc hơn một chút.

Lưu ý rằng trong cả hai trường hợp, kiểm tra xem liệu một mục đã được bao gồm trong mảng mục tiêu (sử dụng containsObject:trong ví dụ của tôi hay indexOfObject:inRange:trong Jim) không mở rộng tốt cho các mảng lớn. Các kiểm tra đó chạy trong thời gian O (N), nghĩa là nếu bạn tăng gấp đôi kích thước của mảng ban đầu thì mỗi kiểm tra sẽ mất gấp đôi thời gian để chạy. Vì bạn đang thực hiện kiểm tra cho từng đối tượng trong mảng, nên bạn cũng sẽ chạy nhiều hơn những kiểm tra đắt tiền hơn. Thuật toán tổng thể (cả của tôi và của Jim) chạy trong thời gian O (N 2 ), sẽ nhanh chóng trở nên đắt đỏ khi mảng ban đầu phát triển.

Để giảm thời gian O (N), bạn có thể sử dụng NSMutableSetđể lưu bản ghi các mục đã được thêm vào mảng mới, vì tra cứu NSSet là O (1) thay vì O (N). Nói cách khác, kiểm tra xem liệu một phần tử có phải là thành viên của NSSet có cùng thời gian hay không bất kể có bao nhiêu phần tử trong tập hợp.

Mã sử ​​dụng phương pháp này sẽ trông giống như thế này:

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

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

Điều này vẫn có vẻ hơi lãng phí mặc dù; chúng tôi vẫn đang tạo ra một mảng mới khi câu hỏi làm rõ rằng mảng ban đầu có thể thay đổi được, vì vậy chúng tôi sẽ có thể khử nó tại chỗ và tiết kiệm một số bộ nhớ. Một cái gì đó như thế này:

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++;
    }
}

CẬP NHẬT : Yuri Niyazov chỉ ra rằng câu trả lời cuối cùng của tôi thực sự chạy trong O (N 2 ) vì removeObjectAtIndex:có lẽ chạy trong thời gian O (N).

. , chuyển chúng sang chỉ mục trước. Nếu đó là trường hợp thì đó thực sự là hiệu suất O (N).)

Vậy lam gi? Nó phụ thuộc vào tình hình. Nếu bạn đã có một mảng lớn và bạn chỉ mong đợi một số lượng nhỏ các bản sao thì việc sao chép tại chỗ sẽ hoạt động tốt và tiết kiệm cho bạn khi phải xây dựng một mảng trùng lặp. Nếu bạn đã có một mảng mà bạn mong đợi nhiều bản sao thì việc xây dựng một mảng tách riêng, tách rời có lẽ là cách tiếp cận tốt nhất. Điều đáng nói ở đây là ký hiệu big-O chỉ mô tả các đặc điểm của thuật toán, nó sẽ không cho bạn biết chắc chắn điều gì là tốt nhất cho bất kỳ trường hợp cụ thể nào.


20

Nếu bạn đang nhắm mục tiêu iOS 5+ (bao gồm toàn bộ thế giới iOS), hãy sử dụng tốt nhất NSOrderedSet. Nó loại bỏ trùng lặp và giữ lại thứ tự của bạnNSArray .

Cứ làm đi

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

Bây giờ bạn có thể chuyển đổi nó trở lại thành một NSArray duy nhất

NSArray *uniqueArray = orderedSet.array;

Hoặc chỉ sử dụng order set vì nó có cùng phương thức như NSArray objectAtIndex:, firstObjectv.v.

Kiểm tra tư cách thành viên containsthậm chí còn nhanh NSOrderedSethơn so với trênNSArray

Để kiểm tra thêm, tham khảo NSOrderedset


Điều này nhận được phiếu bầu của tôi, tôi đọc tất cả và đó là câu trả lời tốt nhất. Không thể tin câu trả lời hàng đầu là một vòng lặp thủ công. Oh họ đã sao chép câu trả lời này.
malhal

19

Có sẵn trong OS X v10.7 trở lên.

Nếu bạn lo lắng về thứ tự, đúng cách để làm

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

Đây là mã loại bỏ các giá trị trùng lặp khỏi NSArray trong Thứ tự.


1
allObjects phải là mảng
malhal

7

cần đặt hàng

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

hoặc không cần đặt hàng

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

3

Ở đây tôi đã loại bỏ các giá trị tên trùng lặp khỏi mainArray và lưu trữ kết quả trong 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

Lưu ý rằng nếu bạn có một mảng được sắp xếp, bạn không cần phải kiểm tra đối với mọi mục khác trong mảng, chỉ mục cuối cùng. Điều này sẽ nhanh hơn nhiều so với việc kiểm tra đối với tất cả các mục.

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

Có vẻ như các NSOrderedSetcâu trả lời cũng được đề xuất yêu cầu ít mã hơn, nhưng nếu bạn không thể sử dụng một NSOrderedSetlý do nào đó và bạn có một mảng được sắp xếp, tôi tin rằng giải pháp của tôi sẽ là nhanh nhất. Tôi không chắc nó so sánh với tốc độ của các NSOrderedSetgiải pháp như thế nào . Cũng lưu ý rằng mã của tôi đang kiểm tra isEqualToString:, do đó, một loạt các chữ cái sẽ không xuất hiện nhiều hơn một lần newArray. Tôi không chắc liệu các NSOrderedSetgiải pháp sẽ loại bỏ trùng lặp dựa trên giá trị hoặc dựa trên vị trí bộ nhớ.

Ví dụ của tôi giả sử sortedSourceArraychỉ chứa NSStrings, chỉNSMutableString s hoặc kết hợp cả hai. Nếu sortedSourceArraythay vào đó chỉ chứa NSNumbers hoặc chỉ NSDates, bạn có thể thay thế

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

với

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

và nó sẽ hoạt động hoàn hảo. Nếu sortedSourceArraychứa hỗn hợp NSStrings, NSNumbers và / hoặc NSDates, nó có thể sẽ bị sập.


1

Có một toán tử đối tượng KVC cung cấp một giải pháp thanh lịch hơn uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];Đây là một thể loại NSArray .


1

Một cách đơn giản hơn mà bạn có thể thử sẽ không thêm Giá trị trùng lặp trước khi thêm đối tượng vào mảng: -

// Giả sử mutableArray được phân bổ và khởi tạo và chứa một số giá trị

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

1

Xóa các giá trị trùng lặp khỏi NSMutableArray trong Objective-C

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

0

Đây là mã loại bỏ các giá trị trùng lặp khỏi NSMutable Array. .it sẽ làm việc cho bạn. myArray là Mảng Mutable của bạn mà bạn muốn xóa các giá trị trùng lặp ..

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

Sử dụng Orderedsetsẽ làm các mẹo. Điều này sẽ giữ cho các bản sao loại bỏ khỏi mảng và duy trì thứ tự mà các bộ thường không làm


-3

chỉ cần sử dụng mã đơn giản này:

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

vì nsset không cho phép các giá trị trùng lặp và tất cả các đối tượng trả về một mảng


Đã làm cho tôi. Tất cả những gì bạn phải làm là sắp xếp lại NSArray của mình khi NSSet trả về một NSArray chưa được sắp xếp.
lindinax

Hoặc đơn giản là sử dụng NSOrderedSetinsteed của NSSet.
lindinax
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.