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


147

NSStringNSMutableArrayObjective-C中删除重复值()的最佳方法?

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

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

5
您可能需要澄清,是要消除对完全相同的对象的引用,还是要消除对截然不同的对象但每个字段具有相同值的引用。
Amagrammer,2009年

没有创建数组的任何副本就没有办法做到这一点吗?
hfossli 2014年

这种方法很容易,也许是最好的。但是,例如,它不适用于我的情况-数组的项不是完全重复的,应该由一个属性进行比较。
Vyachaslav Gerchicov

Answers:


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首先返回数组。
后悔者

10
@Regexident的解决方案是理想的。只需要替换[orderedSet allObjects][orderedSet array]
inket 2013年

不错;)我喜欢使开发人员无需大量修改即可复制和粘贴的答案,这是每个iOS开发人员都会喜欢的答案;)@ abo3atef
Abo3atef 2014年

谢谢,但您应该修复示例。原因-我们通常有NSArray并且应该创建temp NSMutableArray。在您的示例中,反之亦然
Vyachaslav Gerchicov

任何人都知道删除重复项的最佳视图是此方法(使用NSSet)或@Simon Whitaker 链接防止在添加重复项值之前这样做是有效的方法吗?
Mathi Arasan

78

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

如果我们使用键值编码中的对象运算符,则可以执行以下操作:

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

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


3
是的,这也是我使用的!这是一种非常强大的方法,很多iOS开发人员都不知道!
Lefteris

1
当我得知这是可能的时,我感到很惊讶。我以为很多iOS开发人员都不知道这就是为什么我决定添加此答案的原因:)
Tiago Almeida

12
这不会保持对象的顺序。
鲁道夫·亚当科维奇(RudolfAdamkovič),2014年

2
是的,它破坏了订单。
Rostyslav Druzhchenko 2014年

请注意,它也可以像@distinctUnionOfObjects.property通过自定义对象数组的属性删除重复项一样使用。例如@distinctUnionOfObjects.name
AnthoPak '18

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的示例中使用)都无法很好地用于大型数组。这些检查的运行时间为O(N),这意味着如果将原始数组的大小加倍,则每次检查将花费两倍的时间。由于您要对数组中的每个对象进行检查,因此您还将运行更多这些更昂贵的检查。整个算法(我的和Jim的算法)都在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++;
    }
}

更新:Yuri Niyazov 指出,我的最后一个答案实际上以O(N 2removeObjectAtIndex:运行,因为它可能以O(N)的时间运行。

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

那么该怎么办?这取决于实际情况。如果您的阵列很大,并且只期望少量的重复项,那么就地重复数据删除就可以很好地工作,并且省去了构建重复阵列的麻烦。如果您希望在阵列中有很多重复项,那么建立一个单独的,重复数据删除的阵列可能是最好的方法。这里的要点是big-O表示法仅描述算法的特征,而不能明确地告诉您哪种方法最适合给定的情况。


20

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

做就是了

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:yourArray];

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

NSArray *uniqueArray = orderedSet.array;

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

的会员资格检查比contains上的会员检查更快NSOrderedSetNSArray

有关更多信息,请参阅NSOrderedSet参考。


这获得了我的投票,我阅读了所有内容,这是最好的答案。不能相信最佳答案是手动循环。哦,他们现在已经复制了此答案。
malhal

19

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

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

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

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


1
allObjects应该是数组
malhal,2016年

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由于某种原因您不能使用an ,并且您有一个排序数组,那么我相信我的解决方案将是最快的。我不确定将其与NSOrderedSet解决方案的速度进行比较。另请注意,我的代码正在与核对isEqualToString:,因此同一字母序列在中不会出现多次newArray。我不确定NSOrderedSet解决方案是否将基于值或基于内存位置删除重复项。

我的示例假定sortedSourceArray仅包含NSStrings,仅NSMutableStrings或两者的混合。如果sortedSourceArray只包含NSNumberNSDate,则可以替换

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 Object Operator提供了更优雅的解决方案uniquearray = [yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"];这是NSArray类别


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

0

这是从NSMutable Array中删除重复值的代码。。它将为您工作。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

0

使用Orderedset将达到目的。这将保留从数组中删除重复项并保持通常不执行的顺序


-3

只需使用以下简单代码:

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

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


为我工作。您要做的就是再次对NSArray进行排序,因为NSSet返回了未排序的NSArray。
lindinax 2014年

或者干脆用NSOrderedSetinsteed的NSSet
lindinax 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.