是的,使用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)时间内运行,这意味着如果将原始数组的大小增加一倍,那么每张支票要跑两倍的时间。由于您正在对数组中的每个对象进行检查,您还将运行更多的这些更昂贵的检查。整个算法(我的和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
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)2)因为removeObjectAtIndex:可能在O(N)时间内运行。
(他说“可能”是因为我们不确定它是如何实现的;但一个可能的实现是,在删除索引X处的对象之后,该方法循环遍历从索引X+1到数组中的最后一个对象的每个元素,然后将它们移动到前一个索引。如果是这样的话,那就是O(N)的性能。
那么,该怎么办呢?这要视情况而定。如果你有一个很大的数组,而且你只需要少量的重复,那么就地去复制就可以正常工作了,这样你就不必构建一个重复的数组了。如果您有一个数组,您需要大量的重复,那么构建一个单独的、去欺骗的数组可能是最好的方法。这里的方法是,大O表示法只描述一个算法的特性,它不会确切地告诉你哪种方法对任何给定的情况都是最好的。