Remove duplicates from array comparing the properties of its objects

故事扮演 提交于 2019-12-02 22:44:37

You can create an NSMutableSet of dates, iterate your event list, and add only events the date for which you have not encountered before.

NSMutableSet *seenDates = [NSMutableSet set];
NSPredicate *dupDatesPred = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind) {
    Event *e = (Event*)obj;
    BOOL seen = [seenDates containsObject:e.date];
    if (!seen) {
        [seenDates addObject:e.date];
    }
    return !seen;
}];
NSArray *events = ... // This is your array which needs to be filtered
NSArray *filtered = [events filteredArrayUsingPredicate:dupDatesPred];

Wouldn't this work with kvc. I suppose the following solution could work in your case;

Event *event1 = [[Event alloc] init];
event1.name = @"Event1";
event1.date = [NSDate distantFuture];
Event *event2 = [[Event alloc] init];
event2.name = @"Event2";
event2.date = [NSDate distantPast];
Event *event3 = [[Event alloc] init];
event3.name = @"Event1";
event3.date = [NSDate distantPast];
NSArray *array = @[event1, event2, event3];

NSArray *filteredEvents =  [array valueForKeyPath:@"@distinctUnionOfObjects.name"];
NSMutableArray *leftObjects = [duplicateArray mutableCopy];
NSMutableArray *nonDuplicates = [NSMutableArray new];
while (leftObjects.count > 0)
{
    YourClass *object = [leftObjects objectAtIndex:0];

    // find all objects matching your comaprison equality definition for YourClass
    NSArray *matches = [leftObjects filteredArrayUsingPredicate:
                        [NSPredicate predicateWithBlock:^BOOL(YourClass *evaluatedObject, NSDictionary *bindings)
                         {
                             return (evaluatedObject.name == object.name);
                         }] ];
    [leftObjects removeObjectsInArray:matches];

    // add first object (arbitrary, may decide which duplicate to pick)
    [nonDuplicates addObject:matches.firstObject];
}

I think the most effective way is to use NSDictionary to store the object as value and the property value as key, and before adding any object to the dictionary you check if it exist or not which is O(1) operation, i.e. the whole process will take O(n)

Here is the code

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

you can use it as the following

self.arrayWithObjects = [self removeDuplicatesFromArray:self.arrayWithObjects onProperty:@"when"]; 

Here is a Swift extension on the NSArray class that removes duplicates for the specified property:

extension NSArray {
/**
 - parameter property: the name of the property to check for duplicates

 - returns: an array of objects without objects that share an identical value of the specified property
*/
  func arrayWithoutObjectsOfDuplicateProperty(property : String) -> [AnyObject] {
    var seenInstances = NSMutableSet()

    let predicate = NSPredicate { (obj, bind) -> Bool in
      let seen = seenInstances.containsObject(obj.valueForKey(property)!)

      if !seen {
        seenInstances.addObject(obj.valueForKey(property)!)
      }
        return !seen
      }      
      return self.filteredArrayUsingPredicate(predicate)
  }
}

Here is working Swift code snipped which does remove duplicates while keeping the order of elements.

// Custom Struct. Can be also class. 
// Need to be `equitable` in order to use `contains` method below
struct CustomStruct : Equatable {
      let name: String
      let lastName : String
    }

// conform to Equatable protocol. feel free to change the logic of "equality"
func ==(lhs: CustomStruct, rhs: CustomStruct) -> Bool {
  return (lhs.name == rhs.name && lhs.lastName == rhs.lastName)
}

let categories = [CustomStruct(name: "name1", lastName: "lastName1"),
                  CustomStruct(name: "name2", lastName: "lastName1"),
                  CustomStruct(name: "name1", lastName: "lastName1")]
print(categories.count) // prints 3

// remove duplicates (and keep initial order of elements)
let uniq1 : [CustomStruct] = categories.reduce([]) { $0.contains($1) ? $0 : $0 + [$1] }
print(uniq1.count) // prints 2 - third element has removed

And just if you are wondering how this reduce magic works - here is exactly the same, but using more expanded reduce syntax

let uniq2 : [CustomStruct] = categories.reduce([]) { (result, category) in
  var newResult = result
  if (newResult.contains(category)) {}
  else {
    newResult.append(category)
  }
  return newResult
}
uniq2.count // prints 2 - third element has removed

You can simply copy-paste this code into a Swift Playground and play around.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!