Удалить дубликаты из массива, сравнивающего свойства его объектов
Предположим, что у меня есть класс Event, и он имеет 2 свойства: действие (NSString) и дату (NSDate).
И пусть у меня есть массив объектов Event. Проблема в том, что свойства даты могут совпадать.
Мне нужно удалить дубликаты, что означает, что 2 разных объекта с той же датой являются дубликатом.
Я могу удалить дубликаты в любом массиве строк или nsdates, их легко сравнить. Но как это сделать со сложными объектами, где их свойства должны сравниваться?
Не спрашивайте меня, что я сделал до сих пор, потому что "единственное, что приходит мне в голову - это пузырьковый вид, , но это решение для новичков и медленное.
Достаточно высоко оценивается любая помощь (ссылки, ссылки, код).
Спасибо заранее.
ИЗМЕНИТЬ
Благодаря 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];
}
Здесь YourClass
- это имя вашего класса, к которому принадлежит объект, и propertyName
- свойство этого объекта, который вы собираетесь сравнить.
Предположим, что self.arrayWithObjects содержит объекты YourClass.
После заполнения его используйте
self.arrayWithObjects = [self removeDuplicatesInArray:self.arrayWithObjects];
и все готово.
Все кредиты для dasblinkenlight.
Ура!
Ответы
Ответ 1
Вы можете создать 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];
Ответ 2
Не будет ли это работать с kvc. Я полагаю, что в вашем случае может работать следующее решение:
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"];
Ответ 3
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];
}
Ответ 4
Я думаю, что наиболее эффективным способом является использование 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;
}
вы можете использовать его как
self.arrayWithObjects = [self removeDuplicatesFromArray:self.arrayWithObjects onProperty:@"when"];
Ответ 5
Вот расширение Swift в классе NSArray, которое удаляет дубликаты для указанного свойства:
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)
}
}
Ответ 6
Здесь работает код 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
И только если вам интересно, как это уменьшает магию, - вот то же самое, но с использованием более расширенного синтаксиса сокращения
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
Вы можете просто скопировать этот код в Swift Playground и поиграть.