什么是在Objective-C中从数组中删除重复项的最快方法

时间:2016-04-16 17:39:29

标签: objective-c algorithm nsmutablearray memset array-algorithms

准备面试。我试图通过解决以下问题来练习:给定一个NSNumbers的输入数组,其中一些数字是重复的,如何创建另一个只有原始数组中唯一值的数组。

我看到两种方法:

  1. 暴力:循环遍历数组中的每个元素,而在元素中将其与唯一列表中的数字集进行比较,如果匹配,则不要存储它,否则添加它到唯一的列表。 O(n ^ 2)最坏情况时间?

  2. 基于哈希表的方法:拥有长度为N的哈希表.has-table的每个元素都是NSSet。使用散列函数将每个数字映射到0,... N-1。如果它存在于对应于" mapped-index"的NSSet中,则不会将其添加到"唯一数组"。如果没有,则将其添加到set和unique数组中。

  3. 这是否是O(N)的复杂性?

    • 我看了两种方法来实现方法2 答:大小为N的NSMutableArray在start时初始化为[NSNull null]对象。 B. NSMutableDictionary其中key =哈希映射整数

    每种方法的代码如下。

    我注意到了 一世。 2A(阵列方法)的运行时间是2B(Mutabledictionary方法)的运行时间的一半,长度为403的输入数组如下所示(0.055ms vs .12ms)。
    II。 1的运行时间是0.25ms的~5倍。如果没有重复,这种差异就更糟了。

    我的Qs是:

    1. 是否有比2更好的算法?
    2. 是否有更好的算法2实现?
    3. 为什么字典接近慢?我如何使用仪器分析为自己回答这个问题。我怎样才能知道每个步骤使用仪器所花费的确切时间?
    4. 代码

      哈希码功能

      #define NUM_BUCKETS 127
      #define RANDOMIZER 11
      #define NUM_ITER 40000
      
      int hashcode(int value)
      {
          int retVal = (value*RANDOMIZER)%NUM_BUCKETS ;
          if(retVal<0)
          {
              retVal+=NUM_BUCKETS ;
          }
          return retVal ;
      }
      

      1。蛮力方法

          NSMutableArray *smooshedArr=[[NSMutableArray alloc] init] ;
          double startTime ;
      
          startTime=CFAbsoluteTimeGetCurrent() ;
          for(int iter=0;iter<=NUM_ITER;iter++)
          {
              [smooshedArr removeAllObjects] ;
              [smooshedArr addObject:ints[0]] ;
      
              int i,j ;
              for(i=1;i<[ints count];i++)
              {
                  for(j=0;j<[smooshedArr count];j++)
                  {
                      if([ints[i] intValue] == [smooshedArr[j] intValue])
                      {
                          break ;
                      }
                  }
                  if(j==[smooshedArr count])
                  {
                      [smooshedArr addObject:ints[i]] ;
                  }
              }
          }
          NSLog(@"Bruteforce took %.3fms to remove duplicates from array of length %lu",(CFAbsoluteTimeGetCurrent()-startTime)*1000/NUM_ITER,(unsigned long)[ints count]) ;
          NSLog(@"Smooshed arary is %@",smooshedArr) ;
      

      2A。基于数组的哈希表

          NSMutableArray *hashTable = [[NSMutableArray alloc] init] ;
      
          startTime=CFAbsoluteTimeGetCurrent() ;
          for(int iter=0;iter<=NUM_ITER;iter++)
          {
              [smooshedArr removeAllObjects];
              for (NSInteger i = 0; i < NUM_BUCKETS; ++i)
              {
                  [hashTable addObject:[NSNull null]];
              }
      
              [smooshedArr addObject:ints[0]] ;
      
              int indexToInsert = hashcode([ints[0] intValue]) ;
              hashTable[indexToInsert]=[[NSMutableSet alloc] init] ;
              [hashTable[indexToInsert] addObject:ints[0]] ;
      
              int i ;
              for(i=1;i<[ints count];i++)
              {
                  //Find hascode of element i
                  //If the list at index = hashcode in hashCodeArary is empty, then create a NSMutableSet, set toInsert = True
                  //If not empty, check if the element exists in the set. If yes, setToInsert=False. If no, setToInsert=True
                  int indexToInsert = hashcode([ints[i] intValue]) ;
                  BOOL toInsert=false ;
      
                  if(hashTable[indexToInsert] == [NSNull null])
                  {
                      hashTable[indexToInsert]=[[NSMutableSet alloc] init] ;
                      toInsert=true ;
                  }
                  else
                  {
                      if(![hashTable[indexToInsert] containsObject:ints[i]])
                          toInsert=true ;
                  }
                  if(toInsert)
                  {
                      [hashTable[indexToInsert] addObject:ints[i]] ;
                      [smooshedArr addObject:ints[i]] ;
                  }
              }
          }
          NSLog(@"MutableArray (no cheat) took %.3fms to remove duplicates from array of length %lu",(CFAbsoluteTimeGetCurrent()-startTime)*1000/NUM_ITER,(unsigned long)[ints count]) ;
      

      2B。基于字典的哈希表

          NSMutableDictionary *hashDict = [[NSMutableDictionary alloc] init] ;
          //NSLog(@"Start of hashcode approach %.6f", CFAbsoluteTimeGetCurrent()) ;
          startTime=CFAbsoluteTimeGetCurrent() ;
          for(int iter=0;iter<=NUM_ITER;iter++)
          {
              //if(iter <4) NSLog(@"iter start: %.6f", CFAbsoluteTimeGetCurrent()) ;
      
              //if(iter <4) NSLog(@"init start: %.6f", CFAbsoluteTimeGetCurrent()) ;
                [smooshedArr removeAllObjects];
                [hashDict removeAllObjects] ;
              //if (iter<4) NSLog(@"init end: %.6f", CFAbsoluteTimeGetCurrent()) ;
      
      
              [smooshedArr addObject:ints[0]] ;
      
              int indexToInsert = hashcode([ints[0] intValue]) ;
              hashDict[@(indexToInsert)]=[[NSMutableSet alloc] init] ;
              [hashDict[@(indexToInsert)] addObject:ints[0]] ;
      
              int i ;
              for(i=1;i<[ints count];i++)
              {
                  //Find hascode of element i
                  //If the list at index = hashcode in hashCodeArary is empty, then create a NSMutableSet, set toInsert = True
                  //If not empty, check if the element exists in the set. If yes, setToInsert=False. If no, setToInsert=True
                  int indexToInsert = hashcode([ints[i] intValue]) ;
                  BOOL toInsert=false ;
      
                  if(hashDict[@(indexToInsert)] == nil)
                  {
                      hashDict[@(indexToInsert)]=[[NSMutableSet alloc] init] ;
                      toInsert=true ;
                  }
                  else
                  {
                      if(![hashDict[@(indexToInsert)] containsObject:ints[i]])
                          toInsert=true ;
                  }
                  if(toInsert)
                  {
                      [hashDict[@(indexToInsert)] addObject:ints[i]] ;
                      [smooshedArr addObject:ints[i]] ;
                  }
              }
          }
          NSLog(@"Dictionary approach: %.3fms to remove duplicates from array of length %lu",(CFAbsoluteTimeGetCurrent()-startTime)*1000/NUM_ITER,(unsigned long)[ints count]) ;
      

      输入测试开启,430个元素有一些重复,平均超过40000次迭代

         NSArray *ints = @[@(2),@(3),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(2727272),@(112),@(3),@(4),@(1),@(612211),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(7272),@(1232),@(3),@(4),@(1),@(60),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(2727272),@(2),@(3),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(2727272),@(2),@(3),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(72),@(2),@(3),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(13272),@(2),@(3),@(4),@(18),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(972),@(2),@(3),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(3272),@(2),@(3),@(4),@(1),@(69),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(1272),@(2),@(3),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(2272),@(2),@(3),@(4),@(1),@(6),@(91),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(7272),@(2),@(3),@(4),@(12),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(111),@(27272),@(2),@(321),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(4545411),@(12341),@(34210),@(123),@(1234),@(1111),@(727272),@(11187),@(9086),@(876543),@(74532),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(13272),@(2),@(3),@(4),@(18),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(658),@(45454),@(12934),@(38421),@(1243),@(12345),@(1112),@(72),@(52),@(3),@(498),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(45454),@(1234),@(650),@(45454),@(1234),@(3421),@(123),@(1234),@(111),@(27272),@(2),@(321),@(4),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(65),@(4545411),@(12341),@(34210),@(123),@(1234),@(1111),@(727272),@(11187),@(9086),@(876543),@(74532),@(464642),@(65),@(45454),@(1234),@(3421),@(123),@(1234),@(11111),@(13272),@(2),@(3),@(4),@(18),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(45454),@(464642),@(658),@(45454),@(12934),@(38421),@(1243),@(19992345),@(119875412),@(72),@(52),@(3),@(498),@(1),@(6),@(9),@(2),@(2),@(3),@(21),@(22),@(450454),@(46908764642),@(6753435),@(45498754),@(100234),@(65)] ;
      

3 个答案:

答案 0 :(得分:3)

如果您正在准备面试,我建议您使用已经实施的框架类。不要重新实现方向盘。尝试从上到下解决问题。不要考虑细节(散列函数),考虑算法结构:

在伪代码中:

for number in input {
   if number appears for the first time {
      add number to output
   }
}

我们唯一的问题是如何实施number appears for the first time。这是唯一具有一些性能影响的点。

在Objective-C中,我们可以使用NSSet,它是为此问题创建的类。

NSArray *input = @[... array of numbers];

NSMutableSet *foundNumbers = [NSMutableSet set];
NSMutableArray *output = [NSMutableArray array];

for (NSNumber *number in input) {
    if (![foundNumbers containsObject:number])) {
       [foundNumbers addObject:number];
       [output addObject:number];
    }
}

NSLog(@"Output: %@", output);

您只需要传递一个输入数组。提高性能的唯一方法是使用与NSSet不同的结构,但NSSet已经过高度优化,并且您不太可能找到更好的选择。

如果你想开箱即用并且输入中的数字被限制在足够小的范围内(例如0 ... 65000),你可以创建一个包含65000项的BOOL数组,全部初始化到NO并将其用作快速设置实现。 但是,这需要大量的内存,除非input数组很长,否则它将无法获得回报。

绝对不要实现自己的哈希表,NSDictionary已经是哈希表。你在第二个实现中所做的只是NSDictionary的一个非常模糊的重新实现。只有当您可以将它们保存为简单数组时,才能使用存储桶。一旦你向它添加哈希函数,你就会失去性能提升。

另请注意,代码的整体质量对于面试非常重要。不要使用#define来声明常量。保持良好的编码风格(我强烈建议在运营商周围使用空间)。使用迭代器代替for(;;)尝试将变量命名为hashDict(为变量命名变量命名)。

现在有点秘密,还有一个课程NSOrderedSet,它将NSArrayNSSet合并到一个对象中,可以更轻松地解决您的问题:

NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:ints];
NSLog(@"Output: %@", orderedSet);

答案 1 :(得分:0)

实际上甚至不需要使用NSOrderedSet - 只需使用NSSet即可:

NSSet *set = [NSSet setWithArray:ints];

如果您需要一个数组作为输出,Key-Value Coding可以提供帮助:

NSArray *array =  [ints valueForKeyPath:@"@distinctUnionOfObjects.self"];

答案 2 :(得分:0)

如果你不想使用额外的空间(哈希),如果数组中的数字序列无关紧要,但你仍然不想像暴力一样慢,那么你可以对数组进行排序,之后删除一次通过重复。时间复杂度nlog(n)+ n