2011-05-04 119 views
1

我已經編寫了一個小型實用程序,用於識別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個小時才能運行,現在需要兩個小時才能完成四核。這仍然太慢。但是(在這裏談論我的薪酬等級) 也許有一種方法可以將我需要的所有值存儲在「適合堆棧」的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過濾數組? ?是否有一個在線資源 描述這個問題和解決方案?

我願意以任何方式重構軌道字典 (數據結構)最有效。我起初以爲我需要 通過TrackID執行許多查找,但事實並非如此。

2)有沒有更有效的方法來解決這個問題?搖滾明星如何從第1段轉到優化的解決方案?

...我已經尋找答案,時間比我願意承認,發現 這些有趣的,但無益答案:

find duplicates

Find all duplicates and missing values in a sorted array

感謝您的幫助,您可以提供, 蘭斯

回答

1

我的第一個想法是將一些排序後的集合作爲索引保存到字典中,這樣您就可以停止將每個軌道與其他軌道進行比較的O(n^2)搜索。

如果您有按持續時間排序的TrackID陣列,那麼對於任何軌道,您可以進行更高效的O(log n)二分搜索以查找持續時間在5秒內的軌道。

更好的藝術家和名字,你可以存儲一個字典鍵入藝術家或軌道名稱的值是TrackIDs的數組。然後你只需要一個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. 
+0

這些都是很好的建議約拿。我會在這個週末編寫一些代碼並在這裏發佈結果。 – Lance 2011-05-06 17:52:01

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 )算法(如每首歌曲具有相同的名稱,藝術家和持續時間)。這是最好的O(n)算法(如果每首歌都有不同的名字/藝術家/持續時間),並且實際上最終會更接近O(n)而不是O(最有可能)。