使用对象属性从NSArray中过滤重复项

3

我有一个包含Order对象的NSArray,每个Order对象都有三个属性(id、typeID和description)。我想根据typeID筛选我的数组,以排除重复项。重复项是通过typeID确定的,例如,如果有两个typeID = 7的项,则我希望选择具有最大id的Order,因此在这种情况下,它将是=> id = 2。

我的src数组与Order对象:

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"

有人能提供最佳的方法来实现这个吗,谢谢。


谢谢大家的建议,我今天会尝试所有的解决方案。稍后会给你们更新。 - DP2
7个回答

3

方法一:

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

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

方法二:

或者使用NSSet。如果你的对象确实是唯一的,那么将数组转换为集合,然后再转换回数组-这是最简单的去重方式。

NSSet *set = [NSSet setWithArray:array];

使集合由唯一对象组成。
[set allObjects];

为您提供了一个包含集合中所有对象的数组。

第三种方法:

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


为了让数组->设置->数组的方法生效,你需要在Order类中实现isEqual:hash方法的自定义版本。 - Duncan C
indexesOfObjectsPassingTest方法的实现方式是什么?你会编写一个块,使用封闭作用域中的数组,然后查找数组中具有相同typeID的其他对象吗?如何从每个typeID中选择一个且仅一个对象作为结果集?我不确定如何让它正常工作。 - Duncan C
数组->集合->数组:是的,因此要提供“真正独特”的条件,而不仅仅是唯一的索引。 - Adam Eberbach
indexesOfObjectsPassingTest - 请参见答案的编辑。但我可能会使用字典来完成最后一个任务。 - Adam Eberbach

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,抱歉不知道如何投票。我会研究一下并尝试投票。 - DP2
你实际上没有选择一个好的解决方案。排序数组有什么必要呢?不管怎样,由你决定。 - OutOnAWeekend
排序的原因是为了获取具有最大ID的最新创建或最新的重复项。 - DP2

1

首先使用排序方法(可能会生成一个单独的副本)来确保您首先按typeID排序,然后按相反的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" *

这是一个更加简洁的方法。 - trapper

0

没有理由对Adam的回答进行负评。此外,他给出的第一种方法可能可以通过这种方式更加简洁。

__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;
}
}];

0

自然而然地,如果我们读到“过滤重复项”,我们会想到集合和过滤操作。但在这种情况下,这将是棘手的,因为这些重复项并不真正是重复项,而且 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;
    })];
}];

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

答案

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

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降序排序。然后循环遍历项目,并为未见过的type 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,非常感谢您详细的解释,我今天会尝试一下。 - DP2

0

我认为最有效的方法是使用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;
}

0

我使用了一种利用 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];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接