匹配重复的优化算法

时间:2011-05-04 21:31:44

标签: objective-c nsarray block duplicate-removal

我写了一个小实用程序来识别iTunes中的重复曲目。 轨道的实际匹配需要很长时间,我想优化它。 我将轨道数据存储在NSMutableDictionary中,该数据存储单个轨道数据 由trackID键入的NSMutableDictionaries。这些单独的轨道词典有 至少以下键:

  • 的TrackID
  • 名称
  • 艺术家
  • 持续时间(以毫米为####。####)

要确定是否有任何曲目彼此匹配,我必须检查:

  • 如果两首曲目的持续时间在5秒之内
  • 名称匹配
  • 艺术家匹配

我这样做的慢点是使用两个for循环:

-(void)findDuplicateTracks {

    NSArray *allTracks = [tracks allValues];

    BOOL isMatch = NO;

    int numMatches = 0;

    // outer loop

    NSMutableDictionary *track      = nil;
    NSMutableDictionary *otherTrack = nil;

    for (int i = 0; i < [allTracks count]; i++) { 

        track = [allTracks objectAtIndex:i];

        NSDictionary *summary = nil;

        if (![claimedTracks containsObject:track]) {

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

            NSUInteger duration1  = (NSUInteger) [track objectForKey:kTotalTime];
            NSString *nName       = [track objectForKey:knName];
            NSString *nArtist     = [track objectForKey:knArtist];


            // inner loop - no need to check tracks that have
            // already appeared in i

            for (int j = i + 1; j < [allTracks count]; j++) { 

                otherTrack = [allTracks objectAtIndex:j];

                if (![claimedTracks containsObject:otherTrack]) {

                    NSUInteger duration2 = (NSUInteger)[otherTrack objectForKey:kTotalTime];

                    // duration check
                    isMatch = (abs(duration1 - duration2) < kDurationThreshold);

                    // match name
                    if (isMatch) {

                        NSString *onName = [otherTrack objectForKey:knName];

                        isMatch = [nName isEqualToString:onName];
                    }

                    // match artist
                    if (isMatch) {

                        NSString *onArtist = [otherTrack objectForKey:knArtist];

                        isMatch = [nArtist isEqualToString:onArtist];

                    }

                    // save match data
                    if (isMatch) {

                        ++numMatches;

                        // claim both tracks
                        [claimedTracks addObject:track];
                        [claimedTracks addObject:otherTrack];

                        if (![summary isMemberOfClass:[NSDictionary class]]) {

                            [track setObject:[NSNumber numberWithBool:NO] forKey:@"willDelete"];
                            summary = [self dictionarySummaryForTrack:track];

                        }


                        [otherTrack setObject:[NSNumber numberWithBool:NO] forKey:@"willDelete"];                        
                        [[summary objectForKey:kMatches] 
                                            addObject:otherTrack];

                    }
                }
            }

            [aPool drain];
        }
    }
}

对于大型音乐库来说这变得非常慢,并且仅使用1 处理器。一个推荐的优化是使用块和过程 批量(100首曲目)的曲目。我试过了。如果我的代码 最初运行需要9个小时,现在大约需要2个小时 四核。那还是太慢了。但是(在这里谈论我的工资等级) 也许有一种方法可以将我需要的所有值存储在“适合堆栈”的C结构中,然后我就不必从较慢的内存中获取值。这对我来说似乎太低了,但我愿意学习我是否有一个例子。

顺便说一下,我在乐器中对此进行了分析,[NSCFSet member:]占用了 占CPU时间的86.6%。

然后我认为我应该将所有持续时间提取到一个排序的数组中,所以我不会 查找字典中的持续时间值。我觉得这很好 想法,但当我开始实施它时,我想知道如何确定 最佳批量。

如果我有以下时间:

    2 2 3 4 5 6 6 16 17 38 59   Duration
    0 1 2 3 4 5 6  7  8  9 10   Index

然后只是迭代数组,我知道找到匹配 索引0的歌曲曲目,我只需要将它与歌曲进行比较 索引6.这很好,我有我的第一批。但现在我必须这样做 从索引1开始,发现它的批处理也应该停在 索引6并排除索引0.我假设我浪费了很多 这里的处理周期确定批次应该是什么/持续时间 火柴。这似乎是一个“固定”问题,但我们没有做太多 在我的Intro to Algorithms类中。

我的问题是:

1)识别匹配轨道的最有效方法是什么?是吗 类似于上面的东西?它是否使用不相交和[统一] 设置略高于我的知识水平的操作?是吗 使用NSArray过滤数组?是否有在线资源 描述了这个问题和解决方案?

我愿意以任何方式重组曲目词典 (datastructure)效率最高。我起初以为我需要这样做 通过TrackID执行许多查找,但情况不再如此。

2)有没有更有效的方法来解决这个问题?你怎么 摇滚明星从第1段到优化解决方案?

我找到答案的时间比我承认的要长,并且找到了 这些有趣但无益的答案:

find duplicates

Find all duplicates and missing values in a sorted array

感谢您提供的任何帮助, 兰斯

2 个答案:

答案 0 :(得分:1)

有几种方法可以做到这一点,但这是我的第一个天真的猜测:

有一个可变字典。 这本词典中的键是歌曲的名称。 每个键的值是另一个可变字典。 这个二级可变字典的关键是艺术家。 每个键的值是一个可变的歌曲数组。

你最终会得到这样的东西:

NSArray *songs = ...; //your array of songs
NSMutableDictionary *nameCache = [NSMutableDictionary dictionary];

for (Song *song in songs) {
  NSString *name = [song name];
  NSMutableDictionary *artistCache = [nameCache objectForKey:name];
  if (artistCache == nil) {
    artistCache = [NSMutableDictionary dictionary];
    [nameCache setObject:artistCache forKey:name];
  }

  NSString *artist = [song artist];
  NSMutableArray *songCache = [artistCache objectForKey:artist];
  if (songCache == nil) {
    songCache = [NSMutableArray array];
    [artistCache setObject:songCache forKey:artist];
  }

  for (Song *otherSong in songCache) {
    //these are songs that have the same name and artist
    NSTimeInterval myDuration = [song duration];
    NSTimeInterval otherDuration = [otherSong duration];
    if (fabs(myDuration - otherDuration) < 5.0f) {
      //name matches, artist matches, and their difference in duration is less than 5 seconds
    }
  }
  [songCache addObject:song];
}

这是最坏情况的O(n 2 )算法(如果每首歌具有相同的名称,艺术家和持续时间)。这是一个最好的O(n)算法(如果每首歌有不同的名字/艺术家/持续时间),并且实际上最终会更接近O(n)而不是O(n 2 ) (最有可能的)。

答案 1 :(得分:1)

我的第一个想法是将一些已排序的集合作为索引保存到您的字典中,这样您就可以停止进行O(n ^ 2)搜索,将每个音轨与每个其他音轨进行比较。

如果您有按持续时间排序的TrackID数组,那么对于任何音轨,您都可以进行更有效的O(log n)二分搜索,以查找持续时间在5秒容差范围内的曲目。

对于艺术家和名字更好的是,您可以存储键入艺术家或曲目名称的词典,其值为TrackID数组。然后,您只需要进行O(1)查找即可获得特定艺术家的曲目集,这样您就可以非常快速地确定是否存在任何可能的副本。

最后,如果你已经为TrackID构建了那种标题字典,那么你可以浏览所有的密钥,只有当有多个具有相同标题的曲目时才搜索重复项。仅当存在多个具有相同标题的轨道时才进行进一步的比较,应该消除相当大比例的库并大大减少搜索时间(降低到O(n)以构建字典,而另一个O(n)用于最坏情况搜索重复仍然留在O(n)而不是你现在的O(n ^ 2)。


如果最后一次优化没有其他功能,那么对于没有大量重复项的库来说,性能的提升应该是巨大的:

NSMutableArray *possibleDuplicates = [NSMutableArray array];
NSMutableDictionary *knownTitles = [NSMutableDictionary dictionary];
for (NSMutableDictionary *track in [tracks allKeys]) {
    if ([knownTitles objectForKey:[track objectForKey:@"title"]] != nil) {
        [possibleDuplicates addObject:track];
    }
    else {
        [knownTitles addObject:[track objectForKey:@"TrackID"] forKey:[track objectForKey:@"title"]];
    }
}
//check for duplicates of the tracks in possibleDuplicates only.