简体   繁体   English

从数组中删除重复项,比较其对象的属性

[英]Remove duplicates from array comparing the properties of its objects

Suppose I have a class Event, and it has 2 properties: action (NSString) and date (NSDate). 假设我有一个类Event,它有2个属性:action(NSString)和date(NSDate)。

And suppose I have an array of Event objects. 假设我有一个Event对象数组。 The problem is that "date" properties can match. 问题是“日期”属性可以匹配。

I need to remove the duplicates, meaning that 2 different objects with the same date IS a duplicate. 我需要删除重复项,这意味着具有相同日期的2个不同对象是重复的。

I can remove duplicates in any array of strings or nsdates, they are easy to compare. 我可以删除任何字符串或nsdates数组中的重复项,它们很容易比较。 But how to do it with complex objects, where their properties are to be compared? 但是如何使用复杂对象来处理它们的属性呢?

Don't ask me what I did so far, cos' the only thing coming in my mind is a bubble sort, but it's a newbie solution, and slow . 不要问我到目前为止我做了什么,因为我脑子里唯一想到的就是冒泡, 但这是一个新手解决方案,而且很慢

Quite any help is highly appreciated (links, tuts, code). 非常感谢任何帮助(链接,tuts,代码)。

Thanks in advance. 提前致谢。

EDIT 编辑

Thanks to dasblinkenlight, I have made a custom method: 感谢dasblinkenlight,我制作了一个自定义方法:

- (NSArray *)removeDuplicatesInArray:(NSArray*)arrayToFilter{

    NSMutableSet *seenDates = [NSMutableSet set];
    NSPredicate *dupDatesPred = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind) {
        YourClass *e = (YourClass*)obj;
        BOOL seen = [seenDates containsObject:e.propertyName];
        if (!seen) {
            [seenDates addObject:e.when];
        }
        return !seen;
    }];
    return [arrayToFilter filteredArrayUsingPredicate:dupDatesPred];
} 

Here YourClass is the name of your class the object belongs to, and propertyName is the property of that object you are going to compare. 这里YourClass是对象所属类的名称, propertyName是您要比较的对象的属性。

Suppose self.arrayWithObjects contains the objects of YourClass. 假设self.arrayWithObjects包含YourClass的对象。

After populating it, use 填充后,使用

self.arrayWithObjects = [self removeDuplicatesInArray:self.arrayWithObjects];

and you are done. 你完成了

All credits to dasblinkenlight. 所有信用到dasblinkenlight。 Cheers! 干杯!

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日期集,迭代您的事件列表,并仅添加您之前没有遇到过的日期。

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. 这不会与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, ie the whole process will take O(n) 我认为最有效的方法是使用NSDictionary将对象存储为值,将属性值存储为键,并在将任何对象添加到字典之前检查是否存在O(1)操作,即整个过程将采取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: 这是NSArray类上的Swift扩展,它删除指定属性的重复项:

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 . 这是工作Swift代码剪切,它确实删除重复,同时保持元素的顺序

// 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 而且如果你想知道这种减少魔法是如何工作的 - 这里完全相同,但使用更多扩展的reduce语法

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. 您只需将此代码复制粘贴到Swift Playground中即可。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM