วิธีที่ดีที่สุดในการลบค่าที่ซ้ำกันออกจาก NSMutableArray ใน Objective-C?


147

วิธีที่ดีที่สุดในการลบค่าที่ซ้ำกัน ( NSString) จากNSMutableArrayใน Objective-C?

นี่เป็นวิธีที่ง่ายที่สุดและถูกต้องใช่ไหม

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

5
คุณอาจต้องการชี้แจงว่าคุณต้องการกำจัดการอ้างอิงไปยังวัตถุเดียวกันที่แน่นอนหรือสิ่งที่เป็นวัตถุที่แตกต่างกัน แต่มีค่าเดียวกันสำหรับทุกฟิลด์
Amagrammer

ไม่มีวิธีการทำเช่นนี้หากไม่สร้างสำเนาของอาร์เรย์หรือไม่
hfossli

วิธีนี้ง่ายพอและอาจดีที่สุด แต่ตัวอย่างเช่นมันใช้ไม่ได้กับเคสของฉัน - ไอเท็มของอาร์เรย์ไม่ซ้ำกันเต็มและควรนำมาเปรียบเทียบโดยคุณสมบัติเดียว
Vyachaslav Gerchicov

ลองสิ่งนี้สักครั้ง .. stackoverflow.com/a/38007095/3908884
พบกับ Doshi

คำตอบ:


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]!
inket

Nice One;) ฉันชอบคำตอบที่ทำให้ผู้พัฒนาคัดลอกและวางโดยไม่มีการดัดแปลงมากมายนี่คือคำตอบที่นักพัฒนา iOS ทุกคนจะชอบ;) @ abo3atef
Abo3atef

ขอบคุณ แต่คุณควรแก้ไขตัวอย่างของคุณ เหตุผล - เรามักจะมีและควรสร้างชั่วคราวNSArray NSMutableArrayในตัวอย่างของคุณคุณทำงานในทางกลับกัน
Vyachaslav Gerchicov

ใครรู้ว่าวิธีที่ดีที่สุดในการลบรายการที่ซ้ำกันคือวิธีนี้ (ใช้NSSet) หรือลิงค์ @Simon Whitaker ป้องกันก่อนที่จะเพิ่มมูลค่าที่ซ้ำกันซึ่งเป็นวิธีที่มีประสิทธิภาพ?
Mathi Arasan

78

ฉันรู้ว่านี้เป็นคำถามที่เก่า แต่มีวิธีที่สง่างามมากขึ้นที่จะลบข้อมูลที่ซ้ำกันในถ้าคุณไม่สนใจเกี่ยวกับการสั่งซื้อNSArray

ถ้าเราใช้Object ประกอบจากการเข้ารหัสค่าคีย์เราสามารถทำได้:

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

ในฐานะที่เป็นAnthoPakยังตั้งข้อสังเกตว่าเป็นไปได้ที่จะลบรายการที่ซ้ำกันตามคุณสมบัติ ตัวอย่างจะเป็น:@distinctUnionOfObjects.name


3
ใช่นี่คือสิ่งที่ฉันใช้ด้วย! นี่เป็นวิธีที่ทรงพลังมากซึ่งนักพัฒนา iOS จำนวนมากไม่รู้!
Lefteris

1
ฉันประหลาดใจเมื่อรู้ว่าสิ่งนี้อาจเป็นไปได้ ผมคิดว่านักพัฒนา iOS ของคุณจำนวนมากไม่สามารถรู้ว่านี้คือเหตุผลที่ผมตัดสินใจที่จะเพิ่มคำตอบนี้ :)
ติอาโก้อัลเมดา

12
สิ่งนี้ไม่รักษาลำดับของวัตถุ
Rudolf Adamkovič

2
ใช่มันทำลายคำสั่ง
Rostyslav Druzhchenko

โปรดทราบว่ามันยังสามารถใช้เช่น@distinctUnionOfObjects.propertyการลบรายการที่ซ้ำกันโดยคุณสมบัติของอาร์เรย์ของวัตถุที่กำหนดเอง ตัวอย่างเช่น@distinctUnionOfObjects.name
AnthoPak

47

ใช่การใช้ NSSet เป็นแนวทางที่สมเหตุสมผล

หากต้องการเพิ่มคำตอบของ Jim Puls ต่อไปนี้เป็นวิธีการอื่นในการลอกรายการที่ซ้ำกันในขณะที่รักษาลำดับ:

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

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

โดยพื้นฐานแล้วมันเป็นวิธีการเดียวกับของ Jim แต่คัดลอกรายการที่ไม่ซ้ำกันไปยังอาร์เรย์ที่ไม่แน่นอนซึ่งสดใหม่แทนที่จะลบรายการที่ซ้ำจากต้นฉบับ ทำให้หน่วยความจำมีประสิทธิภาพมากขึ้นเล็กน้อยในกรณีของอาเรย์ขนาดใหญ่ที่มีการซ้ำซ้อนจำนวนมาก (ไม่จำเป็นต้องทำสำเนาของทั้งอาเรย์) และฉันคิดว่าสามารถอ่านได้อีกเล็กน้อย

โปรดทราบว่าในกรณีใดกรณีหนึ่งการตรวจสอบเพื่อดูว่ามีรายการรวมอยู่ในอาร์เรย์เป้าหมาย (ใช้containsObject:ในตัวอย่างของฉันหรือindexOfObject:inRange:ใน Jim's) ไม่ได้ปรับขนาดสำหรับอาร์เรย์ขนาดใหญ่ การตรวจสอบเหล่านั้นทำงานในเวลา 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++;
    }
}

UPDATE : Yuri Niyazov ชี้ให้เห็นว่าคำตอบสุดท้ายของฉันทำงานจริงใน O (N 2 ) เพราะremoveObjectAtIndex:อาจทำงานในเวลา O (N)

(เขาบอกว่า "อาจจะ" เพราะเราไม่รู้ว่ามันถูกนำไปใช้อย่างไร แต่สิ่งหนึ่งที่เป็นไปได้คือการนำไปใช้หลังจากการลบวัตถุที่ index X วิธีนั้นจะวนซ้ำทุกองค์ประกอบจากดัชนี X + 1 ไปยังวัตถุสุดท้ายในอาร์เรย์ ให้ย้ายพวกมันไปยังดัชนีก่อนหน้าถ้าเป็นเช่นนั้นประสิทธิภาพ O (N) จะเป็นจริง)

แล้วจะทำอย่างไรดี? มันขึ้นอยู่กับสถานการณ์ หากคุณมีอาร์เรย์จำนวนมากและคุณคาดหวังว่าจะมีจำนวนรายการที่ซ้ำกันเพียงเล็กน้อยการยกเลิกการทำซ้ำแบบแทนที่จะทำงานได้ดีและช่วยให้คุณไม่ต้องสร้างอาร์เรย์ที่ซ้ำกัน หากคุณมีอาเรย์ที่คุณคาดหวังว่าจะมีการซ้ำซ้อนจำนวนมากจากนั้นสร้างอาเรย์แยกต่างหากอาเรย์ที่ซ้ำกันอาจเป็นวิธีที่ดีที่สุด สิ่งที่ต้องทำในที่นี้คือสัญกรณ์ใหญ่ ๆ O อธิบายเฉพาะคุณลักษณะของอัลกอริธึมเท่านั้นมันจะไม่บอกคุณอย่างแน่นอนว่าดีที่สุดสำหรับสถานการณ์ใดก็ตาม


20

หากคุณกำหนดเป้าหมาย iOS 5+ (สิ่งที่ครอบคลุมโลกทั้ง iOS), NSOrderedSetการใช้งานที่ดีที่สุด NSArrayมันเอารายการที่ซ้ำกันและยังคงรักษาคำสั่งของคุณ

แค่ทำ

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

ตอนนี้คุณสามารถแปลงกลับเป็น NSArray ที่เป็นเอกลักษณ์

NSArray *uniqueArray = orderedSet.array;

หรือเพียงแค่ใช้ orderedSet เพราะมีวิธีการเดียวกันเช่น NSArray เหมือนobjectAtIndex:, firstObjectและอื่น ๆ

การตรวจสอบการเป็นสมาชิกcontainsนั้นยิ่งเร็วNSOrderedSetกว่าที่เป็นอยู่NSArray

สำหรับการชำระเงินเพิ่มเติมการอ้างอิงชุด NSOrdered


นี่คือคะแนนของฉันฉันอ่านพวกเขาทั้งหมดและมันเป็นคำตอบที่ดีที่สุด ไม่อยากเชื่อเลยว่าคำตอบยอดนิยมคือการวนซ้ำแบบแมนนวล โอ้ตอนนี้พวกเขาได้คัดลอกคำตอบนี้แล้ว
malhal

19

พร้อมใช้งานใน OS X v10.7 และใหม่กว่า

หากคุณกังวลเกี่ยวกับการสั่งซื้อวิธีที่ถูกต้องที่จะทำ

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

นี่คือรหัสการลบค่าที่ซ้ำกันออกจาก NSArray ในการสั่งซื้อ


1
ทั้งหมดอ็อบเจกต์ควรเป็นอาร์เรย์
malhal

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มีเพียงNSStringแค่แค่NSMutableString s หรือผสมของทั้งสอง หาก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

มีผู้ประกอบการวัตถุ KVC ที่นำเสนอโซลูชั่นที่สง่างามมากขึ้นเป็นuniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];นี่คือหมวด NSArray


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 Array .มันจะทำงานสำหรับคุณ. myArray เป็น Array Mutable ที่คุณต้องการลบค่าที่ซ้ำกัน

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 ที่ไม่เรียงลำดับ
lindinax

หรือเพียงแค่ใช้NSOrderedSetinsteed NSSetของ
lindinax
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.