NSArray:删除具有重复属性的对象

Posted

技术标签:

【中文标题】NSArray:删除具有重复属性的对象【英文标题】:NSArray: Remove objects with duplicate properties 【发布时间】:2011-09-20 21:08:24 【问题描述】:

我有一个包含一些自定义对象的 NSMutableArray。其中两个对象具有相同的属性,例如标题和作者。我想删除重复的对象并留下另一个。

Asset *asset;
NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];

// First
asset = [[Asset alloc] init];
asset.title = @"Developer";
asset.author = @"John Smith";
[items addObject:asset];
[asset release];

// Second
asset = [[Asset alloc] init];
asset.title = @"Writer";
asset.author = @"Steve Johnson";
[items addObject:asset];
[asset release];

// Third
asset = [[Asset alloc] init];
asset.title = @"Developer";
asset.author = @"John Smith";
[items addObject:asset];
[asset release];

由于它们不是同一个对象,但只有重复的属性,我怎样才能删除重复的?

【问题讨论】:

【参考方案1】:

您可以创建一个 HashSet,并在循环时将“title+author”连接集添加到 HashSet (NSMutableSet)。当您到达每个项目时,如果 HashSet 包含您的密钥,请将其删除或不复制(删除或创建不重复的副本)。

这使它排序为 n(1 个循环)

这是 NSMutableSet 类:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableSet_Class/Reference/NSMutableSet.html#//apple_ref/occ/cl/NSMutableSet

使用代码编辑:

代码的核心是一个循环。

void print(NSMutableArray *assets)

    for (Asset *asset in assets)
    
        NSLog(@"%@/%@", [asset title], [asset author]);
    


int main (int argc, const char * argv[])


    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    //
    // Create the initial data set
    //
    Asset *asset;
    NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];

    // First
    asset = [[Asset alloc] init];
    asset.title = @"Developer";
    asset.author = @"John Smith";
    [items addObject:asset];
    [asset release];

    // Second
    asset = [[Asset alloc] init];
    asset.title = @"Writer";
    asset.author = @"Steve Johnson";
    [items addObject:asset];
    [asset release];

    // Third
    asset = [[Asset alloc] init];
    asset.title = @"Developer";
    asset.author = @"John Smith";
    [items addObject:asset];
    [asset release];

    NSLog(@"****Original****");
    print(items);

    //
    // filter the data set in one pass
    //
    NSMutableSet *lookup = [[NSMutableSet alloc] init];
    for (int index = 0; index < [items count]; index++)
    
        Asset *curr = [items objectAtIndex:index];
        NSString *identifier = [NSString stringWithFormat:@"%@/%@", [curr title], [curr author]];

        // this is very fast constant time lookup in a hash table
        if ([lookup containsObject:identifier])
        
            NSLog(@"item already exists.  removing: %@ at index %d", identifier, index);
            [items removeObjectAtIndex:index];
        
        else
        
            NSLog(@"distinct item.  keeping %@ at index %d", identifier, index);
            [lookup addObject:identifier];
        
    

    NSLog(@"****Filtered****");
    print(items);

    [pool drain];
    return 0;

这是输出:

Craplet[11991:707] ****Original****
Craplet[11991:707] Developer/John Smith
Craplet[11991:707] Writer/Steve Johnson
Craplet[11991:707] Developer/John Smith
Craplet[11991:707] distinct item.  keeping Developer/John Smith at index 0
Craplet[11991:707] distinct item.  keeping Writer/Steve Johnson at index 1
Craplet[11991:707] item already exists.  removing: Developer/John Smith at index 2
Craplet[11991:707] ****Filtered****
Craplet[11991:707] Developer/John Smith
Craplet[11991:707] Writer/Steve Johnson

【讨论】:

所以你是说添加一个 NSString 对象到 NSMutableSet 然后在下一个循环检查相同的字符串是否存在? 是的 - 在一次通过中,您正在尝试查看您是否已经看过类似的项目。您必须确定相似的含义(在本例中为标题+作者)。 NSSet 提供了非常快速的查找(在内部使用哈希)来快速回答这个问题。一个缺点是为不同的标题+作者组合提供了额外的内存。典型的空间与时间。 好吧,我仍然对如何使用 NSSet 进行查找感到困惑。你能提供一个快速的代码示例吗? 在发布之前我没有注意到 NSSet containsObject 是常数时间,O(1)!这真是太棒了。我会争论这个算法是线性的,但如果 containsObject 是真正的常数时间,那么它就是。干得好,@bryanmac! 太棒了!当我删除对象时,我需要添加“索引--”【参考方案2】:

您可以使用 NSSet 的唯一性从原始数组中获取不同的项目。如果您有 Assest 的源代码,则需要覆盖 Asset 类的 hashisEqual: 方法。

@interface Asset : NSObject
@property(copy) NSString *title, *author;
@end

@implementation Asset
@synthesize title, author;

-(NSUInteger)hash

    NSUInteger prime = 31;
    NSUInteger result = 1;

    result = prime * result + [self.title hash];
    result = prime * result + [self.author hash];

    return result;


-(BOOL)isEqual:(id)object

    return [self.title isEqualToString:[object title]] && 
    [self.author isEqualToString:[object author]];


- (void)dealloc 
    [title release];
    [author release];
    [super dealloc];


@end

然后去实现:

Asset *asset;
NSMutableArray *items = [[[NSMutableArray alloc] init] autorelease];

// First
asset = [[Asset alloc] init];
asset.title = @"Developer";
asset.author = @"John Smith";
[items addObject:asset];
[asset release];

// Second
asset = [[Asset alloc] init];
asset.title = @"Writer";
asset.author = @"Steve Johnson";
[items addObject:asset];
[asset release];

// Third
asset = [[Asset alloc] init];
asset.title = @"Developer";
asset.author = @"John Smith";
[items addObject:asset];
[asset release];

NSLog(@"Items: %@", items);

NSSet *distinctItems = [NSSet setWithArray:items];

NSLog(@"Distinct: %@", distinctItems);

如果你最后需要一个数组,你可以打电话给[distinctItems allObjects]

【讨论】:

【参考方案3】:

首先,我会像这样覆盖 Asset 的 isEqual: 方法:

-(BOOL)isEqual:(Asset *)otherAsset 
    return [self.title isEqual:otherAsset.title] && [self.author isEqual:otherAsset.author];

那么,如果你想首先避免在数组中放置重复项:

NSUInteger idx = [items indexOfObject:asset];  // tests objects for equality using isEqual:
if (idx == NSNotFound) [items addObject:asset];

如果数组已经包含重复项,那么任何找到它们的算法的运行时间都比线性差,但我认为创建一个新数组并只添加像上面这样的唯一元素是最好的算法。像这样的:

NSMutableArray *itemsWithUniqueElements = [NSMutableArray arrayWithCapacity:[items count]];

for (Asset *anAsset in items) 
    if ([items indexOfObject:anAsset] == NSNotFound)
        [itemsWithUniqueElements addObject:anAsset];


[items release];
items = [itemsWithUniqueElements retain];

在最坏的情况下(所有元素已经是唯一的)迭代次数是:

1 + 2 + 3 + ... + n =  n * (n+1) / 2

这仍然是 O(n^2) 但比@Justin Meiners 的算法略好。没有冒犯的意思! :)

【讨论】:

可以线性求解 O(n)。因为哈希表查找摊销到 O(1),有了额外的空间,你可以在一个循环中完成。典型的时间与空间。 该解决方案还需要添加一件事。在覆盖 -(BOOL)isEqual: 方法后,您可以使用 set 而不是数组。那么当您尝试添加到设置“重复”对象时,它不会被添加,直到您将您的集合创建为可以包含重复项的集合... @Ariel:如果你想在一个集合中使用自定义项目,你也必须覆盖 hash 我错过了。对集合没有太多工作... :) 感谢您的纠正。 关于覆盖散列的观点很好。我想过走这条路(最正确),但为了简单起见,我选择了简单的 concat 字符串散列。【参考方案4】:

这是您可以做到的一种方式 :

NSMutableArray* toRemove = [NSMutableArray array];

    for (Asset* asset1 in items)
    
        for (Asset* asset2 in items)
        
            if (asset1 != asset2)
            
                if ([asset1.title isEqualToString:asset2.title] && [asset1.author isEqualToString:asset2.author])
                
                    [toRemove addObject:asset2];
                
            
        
    

    for (Asset* deleted in toRemove)
    
        [items removeObject:toRemove];
    

【讨论】:

好的,但这是一个 n^2 算法,通常应该避免。 那时您还没有看到太多代码 :P 您将每个项目与其他每个项目进行比较并比较两者是合乎逻辑的。我有一个 toRemove 的原因是我在迭代时不修改 items 数组。 @bryanmac 是的,它是 n^2 并且可能有一些花哨的其他方法可以做到这一点,但这对我来说是最简单的思考方式,我也怀疑这将成为限制的规模。 我看过很多代码。我只是说我正在寻找一种更清洁的方法。 同意简单且有效。只是指出一个替代方案。我认为更快的循环是大约相同的代码,但可能会增加一点足迹(假设重复对你来说是一个小例子)。【参考方案5】:

如果您希望自定义 NSObject 子类在名称相同时被视为相同,您可以实现 isEqual:hash。这将允许您将对象添加到 NSSet/NSMutableSet(一组不同的对象)。

然后,您可以使用NSSetsortedArrayUsingDescriptors: 方法轻松创建已排序的NSArray

MikeAsh 写了一篇关于实现自定义相等的非常可靠的文章:Friday Q&A 2010-06-18: Implementing Equality and Hashing

【讨论】:

你为什么把这个答案发了两次?当您找到它们时标记重复项,而不是重复发布相同的内容。 我发了两次吗?我的意思是只发一次,你能把它标记为重复吗?

以上是关于NSArray:删除具有重复属性的对象的主要内容,如果未能解决你的问题,请参考以下文章

过滤对象的 NSArray 以具有唯一的属性对象

oc70--NSArray1

Swift 无法将 NSNumber 桥接到 Float [重复]

从 NSArray 获取单个属性的 NSArray

如何删除重复结果并合并在 mySQL 查询中具有相似属性的对象?

使用 NSPredicate 的不同对象