在Objective-C中从NSMutableArray中删除重复值的最佳方法是什么?

时间:2009-06-22 06:11:04

标签: ios objective-c nsmutablearray

从Objective-C中的NSString中删除重复值(NSMutableArray)的最佳方法是什么?

这是最简单正确的方法吗?

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

14 个答案:

答案 0 :(得分:234)

如果您不担心对象的顺序,那么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];

答案 1 :(得分:77)

我知道这是一个老问题,但如果您不关心订单,有一种更优雅的方法可以删除NSArray 中的重复项。

如果我们使用Object Operators from Key Value Coding,我们可以这样做:

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

由于AnthoPak还注意到可以根据属性删除重复项。一个例子是:@distinctUnionOfObjects.name

答案 2 :(得分: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:,或在Jim中使用indexOfObject:inRange:)对于大型数组而言不能很好地扩展。这些检查在O(N)时间运行,这意味着如果您将原始数组的大小加倍,那么每次检查将花费两倍的时间来运行。由于您正在检查数组中的每个对象,因此您还将运行更多这些更昂贵的检查。整个算法(我和Jim都是)在O(N 2 )时间内运行,随着原始阵列的增长,这种算法会很快变得昂贵。

要将其降低到O(N)时间,您可以使用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++;
    }
}

更新:Yuri Niyazov pointed out我的上一个答案实际上是在O(N 2 )中运行,因为removeObjectAtIndex:可能在O(N)中运行) 时间。

(他说“可能”因为我们不确定它是如何实现的;但是一种可能的实现是在删除索引X处的对象后,该方法然后遍历索引X + 1中的每个元素数组中的最后一个对象,将它们移动到上一个索引。如果是这种情况,则确实是O(N)性能。)

那么,该怎么办?这取决于实际情况。如果你有一个大型阵列并且你只需要少量的重复数据,那么就地重复数据删除工作就可以正常工作,而且你需要建立一个重复的数组。如果你有一个数组,你期望有很多重复,那么构建一个单独的,去掉的数组可能是最好的方法。这里的内容是大O符号仅描述算法的特征,它不会明确地告诉您哪种情况最适合任何特定情况。

答案 3 :(得分:19)

在OS X v10.7及更高版本中可用。

如果您担心订单,正确的方法

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

以下是在订单中从NSArray中删除重复值的代码。

答案 4 :(得分:19)

如果您的目标是iOS 5+(涵盖整个iOS世界),最好使用NSOrderedSet。它会删除重复项并保留NSArray的订单。

只做

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

您现在可以将其转换回唯一的NSArray

NSArray *uniqueArray = orderedSet.array;

或者只使用orderedSet,因为它具有与objectAtIndex:firstObject之类的NSArray相同的方法,等等。

使用contains进行的成员资格检查在NSOrderedSet上比在NSArray上更快

如需更多结帐NSOrderedSet Reference

答案 5 :(得分:6)

需要订单

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);

答案 6 :(得分: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]];
    }
}

答案 7 :(得分: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个,或两者的混合。如果sortedSourceArray只包含NSNumber或仅NSDate,则可以替换

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

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

它应该完美无缺。如果sortedSourceArray包含NSString s,NSNumber和/或NSDate的混合,则可能会崩溃。

答案 8 :(得分:1)

有一个KVC对象运算符可以提供更优雅的解决方案uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];这是一个NSArray category

答案 9 :(得分:1)

您可以尝试一种更简单的方法,在数组中添加对象之前不会添加重复值: -

//假设mutableArray已分配并初始化并包含一些值

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

答案 10 :(得分:0)

以下是从NSMutableArray中删除重复值的代码。。它将适合您。 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

答案 11 :(得分:0)

使用Orderedset可以解决问题。这将保留从数组中删除重复项并保持通常不执行的顺序

答案 12 :(得分:0)

从Objective-C

中的NSMutableArray中删除重复值
NSMutableArray *datelistArray = [[NSMutableArray alloc]init];
for (Student * data in fetchStudentDateArray)
{
    if([datelistArray indexOfObject:data.date] == NSNotFound)
    [datelistArray addObject:data.date];
}

答案 13 :(得分:-3)

只需使用这个简单的代码:

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

因为nsset不允许重复值,所有对象都返回一个数组