【问题标题】:filter duplicate from NSArray using object property使用对象属性从 NSArray 中过滤重复项
【发布时间】:2014-07-10 19:51:16
【问题描述】:

我有一个包含 Order 对象列表的 NSArray,一个 Order 对象具有三个属性(id、typeID 和 description),我想根据 typeID 过滤我的数组以排除重复项。重复项由 typeID 确定,例如,如果有 2 个项目的 typeID=7,那么我想选择具有最大 id 的订单,所以在这种情况下它将是 => id=2。

我的带有 Order 对象的 src 数组:

Item 1: id=1, typeID=7, description="some text 1"
Item 2: id=2, typeID=7, description="some text 2"
Item 3: id=3, typeID=5, description="some text 3"
Item 4: id=4, typeID=5, description="some text 4"
Item 5: id=5, typeID=8, description="some text 5"

应用过滤器后,我返回的数组应如下所示:

Item 2: id=2, typeID=7, description="some text 2"
Item 4: id=4, typeID=5, description="some text 4"
Item 5: id=5, typeID=8, description="some text 5"

有人可以建议最好的方法吗,谢谢。

【问题讨论】:

  • 感谢大家的建议,今天将尝试所有解决方案。很快就会更新你

标签: ios objective-c nsarray


【解决方案1】:

我使用了一种利用NSSet的不重复能力的方法。

这里是代码

我在这里使用了+ 方法,因为您可以在任何共享类中使用此方法并在您想要的任何类中访问它。

+ (NSArray *)removeDuplicateEntriesFromArray:(NSArray *)array basedOnKey:(NSString *)key{
NSMutableArray *newArray = [NSMutableArray new];
//get array containing all the keys.
NSArray *keysArray = [array valueForKey:key];
//putting these keys into a set which will remove duplicate keys
NSSet *noDuplicateKeys = [[NSSet alloc]initWithArray:keysArray];

for (NSString *currentKey in noDuplicateKeys) {
    //Now searching objects with all the keys available in the set and putting those objects into newArray.
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@",key ,currentKey];
    NSArray *allObjectsWithKey = [array filteredArrayUsingPredicate:predicate];
    [newArray addObject:[allObjectsWithKey firstObject]];
}
return [newArray copy];
}

【讨论】:

    【解决方案2】:

    我认为最有效的方法是使用NSDictionary 将对象存储为值,将属性值存储为键,然后在将任何对象添加到字典之前检查它是否存在,这是 O(1) 操作,即整个过程需要O(n)

    这里是代码

    - (NSArray *)removeDuplicatesFromArray:(NSArray *)array onProperty:(NSString *)propertyName {
        NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
    
        for (int i=0; i<array.count; i++) {
    
            NSManagedObject *currentItem = array[i];
            NSString *propertyValue = [currentItem valueForKey:propertyName];
    
            if ([dictionary valueForKey:propertyValue] == nil) {
                [dictionary setValue:currentItem forKey:propertyValue];
            }
        }
    
        NSArray *uniqueItems = [dictionary allValues];
    
        return uniqueItems;
    }
    

    【讨论】:

      【解决方案3】:

      首先感谢大家的所有提示,这就是我能够解决我的问题的方法:

      -( NSArray *) filterOutDuplicateOrder: (NSArray *)unFilteredArray
      {
      
          // First sort array by descending so I could capture the max id
          NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"itemID" ascending:NO];
          NSArray *sortedDescArray = [unFilteredArray sortedArrayUsingDescriptors:[NSArray arrayWithObjects:descriptor,nil]];
          
          // Filter out duplicates using typeID
          NSMutableArray *filteredArrayOfObjects = [[NSMutableArray alloc] init];
          for (Order *order in sortedDescArray)
          {
              if(!([[filteredArrayOfObjects valueForKeyPath:@"typeID"] containsObject:order.typeID]))
              {
                  [filteredArrayOfObjects addObject:progressNote];
              }
          }    
          return resultArray;
      }
      

      【讨论】:

      • vikingosegundo,抱歉不知道如何投票。我会调查并尝试投票。
      • 您并没有真正找到好的解决方案。需要对数组进行排序。无论如何,由你决定。
      • 排序的原因是为了获得最后创建的或最新的具有最大 ID 的重复项。
      【解决方案4】:

      方法一:

      - (NSIndexSet *)indexesOfObjectsPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate

      我在想这样的事情:

      __block NSMutableSet *uniqueTypeIDs = [NSMutableSet set];
      NSIndexSet *set = [myArrayOfObjects indexesOfObjectsPassingTest:^BOOL(id object, NSUInteger idx, BOOL *stop) {
          if([uniqueTypeIDs containsObject:[NSNumber numberWithInt:object.typeID]]) {
              return NO;
          } else {
              [uniqueTypeIDs addObject:[NSNumber numberWithInt:object.typeID]];
              return YES;
          }
      }];
      

      您的 typeID 是否需要转换为 NSNumber?你决定。 返回的 NSIndexSet 将包含所有通过测试的对象的索引。然后你可以对这些对象采取行动,或者将它们从你的数组中删除。

      方法二:

      或者使用 NSSet。如果您的对象确实是唯一的,则将数组转换为集合,然后再转换回数组 - 这是丢失重复对象的最简单方法。

      NSSet *set = [NSSet setWithArray:array];
      

      使集合由独特的对象组成

      [set allObjects];
      

      为您提供集合中所有对象的数组

      方法三:

      另一种方法是使用 NSMutableDictionary,使用类型 ID 作为键;遍历数组,并使用 typeID(变成 NSNumber)作为键来存储索引。如果您发现字典中已经存在该键,请不要再次添加它。结果是一个字典,其中包含原始数组中唯一对象的索引。

      【讨论】:

      • 为了使 array->set->array 方法起作用,您必须实现 isEqual:hash 方法的自定义版本。在 Order 类中
      • indexOfObjectsPassingTest 方法如何工作?您是否会编写一个使用封闭范围内的数组的块,然后在数组中查找具有相同 typeID 的其他对象?您将如何为结果集选择一个且只有一个具有每个 typeID 的对象?我不确定你是如何让它工作的。
      • array->set->array: 是的,因此附带“真正唯一”,而不仅仅是唯一索引。
      • indexesOfObjectsPassingTest - 见编辑回答。但我可能会做最后一个,使用字典。
      【解决方案5】:

      如果我们阅读“过滤重复”,我们自然会想到集合和过滤操作。但是在这种情况下这会很麻烦,因为重复项并不是真正的重复项,并且 NSSet 不会给我们机会来决定更喜欢哪个项目。

      我选择先根据其 typeID 对项目进行分段,然后在每个分段中选择第一个对象,然后根据其 id 对它们进行排序。

      准备工作

      我使用这个Item 类:

      @interface Item : NSObject
      @property NSInteger itemID;
      @property NSInteger typeID;
      @property(copy) NSString *itemDescription;
      @end
      
      @implementation Item
      
      -(NSString *)description
      {
          return [NSString stringWithFormat:@"Item: %li, typeID: %li, description: %@", (long)self.itemID, (long)self.typeID, self.itemDescription];
      }
      @end
      

      请注意,iddescription 是相当糟糕的属性名称。

      我使用此代码创建项目列表:

      NSArray *data =@[ @{@"itemID": @1, @"typeID": @7, @"description": @"some text 1"},
                        @{@"itemID": @2, @"typeID": @7, @"description": @"some text 2"},
                        @{@"itemID": @3, @"typeID": @5, @"description": @"some text 3"},
                        @{@"itemID": @4, @"typeID": @5, @"description": @"some text 4"},
                        @{@"itemID": @5, @"typeID": @8, @"description": @"some text 5"}];
      
      NSMutableArray *items = [@[ ] mutableCopy];
      
      
      [data enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
          [items addObject:({
              Item *item = [[Item alloc] init];
              item.itemID = [obj[@"itemID"] integerValue];
              item.typeID = [obj[@"typeID"] integerValue];
              item.itemDescription = obj[@"description"];
              item;
          })];
      }];
      

      这应该是您以类似方式拥有的所有代码。或者你不需要它。

      答案

      我创建了一个以 typeID 作为键的字典。作为值,我添加并填充可变数组:

      NSMutableDictionary *itemsByType = [@{} mutableCopy];
      
      [items enumerateObjectsUsingBlock:^(Item *item, NSUInteger idx, BOOL *stop) {
          id key = @(item.typeID);
          if (![[itemsByType allKeys] containsObject:key]) {
              itemsByType[key] = [@[] mutableCopy];
          }
          [itemsByType[key] addObject:item];
      }];
          
      

      现在我对每个可变数组进行排序:

      [itemsByType enumerateKeysAndObjectsUsingBlock:^(id key, NSMutableArray *items, BOOL *stop) {
          [items sortUsingComparator:^NSComparisonResult(Item *item1, Item *item2) {
              return item1.itemID < item2.itemID;
          }];
      }];
      

      并将每个数组的每个第一个对象放入结果中:

      NSMutableArray *resultArray = [@[] mutableCopy];
      [[itemsByType allKeys]  enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
          [resultArray addObject:itemsByType[key][0]];
      }];
      

      现在我按 itemID 对结果进行排序

      [resultArray sortUsingComparator:^NSComparisonResult(Item *item1, Item *item2){
          return item1.itemID > item2.itemID;
      }];
      

      结果:

      NSLog(@"%@", resultArray);
      

      打印

      (
          "Item: 2, typeID: 7, description: some text 2",
          "Item: 4, typeID: 5, description: some text 4",
          "Item: 5, typeID: 8, description: some text 5"
      )
      

      我的测试程序源码:gist


      另一种方法是对 typeID 升序和 itemID 降序进行排序。然后循环这些项目并将每个第一个项目作为一个看不见的类型 id。对 typeID 的结果进行排序。

      [items sortUsingDescriptors:@[[[NSSortDescriptor alloc] initWithKey:@"typeID" ascending:YES],
                                    [[NSSortDescriptor alloc] initWithKey:@"itemID" ascending:NO]
                                    ]];
      
      NSInteger lastestTypeID = -1;
      
      NSMutableArray *result = [@[] mutableCopy];
      
      for (Item *item in items) {
          if (item.typeID > lastestTypeID) {
              lastestTypeID = item.typeID;
              [result addObject:item];
          }
      }
      
      [result sortUsingComparator:^NSComparisonResult(Item *obj1, Item *obj2) {
          return obj1.itemID > obj2.itemID;
      }];
      

      【讨论】:

      • vikingosegundo,感谢您的详细解释,今天将尝试。
      【解决方案6】:

      没有理由对亚当的回答投反对票。同样他给出的第一种方法,这样可能会更简洁。

      __block NSMutableSet *uniqueTypeIDs = [NSMutableSet set];    
      NSMutableArray *myFilteredArrayOfObjects = [NSMutableArray new];
      
      [myArrayOfObjects indexesOfObjectsPassingTest:^BOOL(id object, NSUInteger idx, BOOL *stop) {
      if([uniqueTypeIDs containsObject:[NSNumber numberWithInt:object.typeID]]) {
          return NO;
      } else {
          [uniqueTypeIDs addObject:[NSNumber numberWithInt:object.typeID]];
          [myFilteredArrayOfObjects addObject:object];
          return YES;
      }
      }];
      

      编辑 - 甚至这也可以是一种方式。 (虽然没试过。)

      NSMutableArray *myFilteredArrayOfObjects = [NSMutableArray new];
      
      [myArrayOfObjects indexesOfObjectsPassingTest:^BOOL(id object, NSUInteger idx, BOOL *stop) {
      if([[myFilteredArrayOfObjects valueForKeyPath:@"typeID"] containsObject:object.typeID]) {
          return NO;
      } else {
          [myFilteredArrayOfObjects addObject:object];
          return YES;
      }
      }];
      

      【讨论】:

        【解决方案7】:

        首先使用排序方法(可能会生成一个单独的副本)以确保您首先按typeID 排序,然后在reverse 中按id 排序,如下所示:

        id=4, typeID=5, description="some text 4"
        id=3, typeID=5, description="some text 3"
        id=2, typeID=7, description="some text 2"
        id=1, typeID=7, description="some text 1"
        id=5, typeID=8, description="some text 5"
        

        现在按顺序遍历生成的数组,同时跟踪typeID。您可以保证,如果 typeID 与前一项不同(或者这是第一项),则此项将进入您的结果数组(加星标的项是那些):

        id=4, typeID=5, description="some text 4" *
        id=3, typeID=5, description="some text 3"
        id=2, typeID=7, description="some text 2" *
        id=1, typeID=7, description="some text 1"
        id=5, typeID=8, description="some text 5" *
        

        【讨论】:

        • 这是一种更简洁的方法。
        猜你喜欢
        • 2013-07-02
        • 1970-01-01
        • 2018-05-25
        • 2013-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-07
        • 1970-01-01
        相关资源
        最近更新 更多