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

151

如何在Objective-C中从NSMutableArray中删除重复的值(NSString)?

这是最简单且正确的方式吗?

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

5
请问您是想澄清是否要消除对完全相同的对象的引用,还是包括那些虽然是不同的对象,但每个字段的值都相同的情况? - Amagrammer
有没有一种方法可以在不创建任何数组副本的情况下完成这个操作? - hfossli
这种方法非常简单,也可能是最好的。但是例如对于我的情况它行不通 - 数组的项不是完全相同的,而应该按一个属性进行比较。 - Vyachaslav Gerchicov
尝试一下这个... https://dev59.com/Dpjga4cB1Zd3GeqPIlQ4#38007095 - Meet Doshi
14个回答

246

如果您不担心对象的顺序,那么您的NSSet方法是最好的选择,但是,如果您不关心顺序,为什么不一开始就将它们存储在NSSet中呢?

我在2009年撰写了下面的答案;在2011年,苹果公司推出了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]; 获取一个数组,或者在开始时直接使用 NSOrderedSet 而不是 NSArray - Regexident
10
@Regexident的解决方案非常理想。只需要将[orderedSet allObjects]替换为[orderedSet array] - inket
不错的答案;) 我喜欢那些让开发者无需进行大量修改即可复制和粘贴的答案,这是每个iOS开发者都会喜欢的答案;) @abo3atef - Atef
谢谢,但你应该修正你的例子。原因是我们通常使用NSArray,并且应该创建临时的NSMutableArray。在你的例子中,你的做法是相反的。 - Vyachaslav Gerchicov
有人知道哪种视图是最好的,以删除重复项为例,这种方法(使用NSSet)还是@Simon Whitaker link在添加重复值之前进行预防,这是一种有效的方式? - Mathi Arasan

78

我知道这是一个老问题,但是有一种更加优雅的方法可以在 NSArray 中删除重复项如果你不关心顺序的话

如果我们使用Key Value Coding中的对象运算符,我们可以这样做:

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

正如AnthoPak所指出的那样,可以根据属性删除重复项。例如:@distinctUnionOfObjects.name


3
是的,这也是我使用的!这是一种非常强大的方法,许多iOS开发者并不知道! - Lefteris
1
当我了解到这是可能的时,我感到非常惊讶。我认为很多iOS开发者可能不知道这一点,这就是为什么我决定添加这个答案的原因 :) - Tiago Almeida
12
这不保持对象的顺序。 - Rudolf Adamkovič
2
是的,它打破了顺序。 - Rostyslav Druzhchenko
请注意,它也可以像 @distinctUnionOfObjects.property 一样使用,通过自定义对象数组的属性来删除重复项。例如 @distinctUnionOfObjects.name - AnthoPak
感谢您的提示@AnthoPak,已添加到答案中 :) - Tiago Almeida

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 指出,我的上一个答案实际上运行时间为O(N2),因为removeObjectAtIndex:可能需要O(N)的时间。

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

那么,该怎么办呢?这取决于情况。如果您有一个大数组,并且只期望有少量重复项,则原地去重将完美地工作,并节省构建重复数组的时间。如果您有一个数组,其中有很多重复项,则构建一个单独的去重数组可能是最好的方法。这里的要点是,大O符号仅描述算法的特征,它不能明确告诉您在任何给定情况下哪种方法最好。


21
如果您的目标是 iOS 5+(覆盖整个 iOS),最好使用 NSOrderedSet。它可以去重并保留您的 NSArray 的顺序。
请执行以下操作:
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

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

NSArray *uniqueArray = orderedSet.array;

或者直接使用 NSOrderedSet,因为它具有与 NSArray 相同的方法,例如 objectAtIndex:firstObject 等等。

NSOrderedSet 上进行成员检查,甚至比在 NSArray 上进行检查更快。

欲了解更多,请参阅 NSOrderedSet 参考文献


这得到了我的投票,我读了所有的答案,这是最好的答案。不敢相信排名第一的答案是手动循环。哦,他们现在复制了这个答案。 - malhal

19

适用于OS X v10.7及更高版本。

如果您担心顺序,正确的做法是:

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

这里是按顺序从NSArray中删除重复值的代码。


1
allObjects 应该是数组。 - 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

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

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

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

1

在Objective-C中从NSMutableArray中删除重复值

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

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包含了混合的NSStringNSNumber和/或NSDate,那么它很可能会崩溃。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接