Skip to content

KVC: Set And Array Operators @distinctUnionOfSets

2010.09.12

KVC (Key-Value Coding) の Set And Array Operators をマスターすると、コードを簡素化できたり、NSPredicate でできることの幅が増えたり、Cocoa Bindings でできることの幅が増えるなどの利点があります。Set And Array Operators の概要から始まったシリーズ。

今回は、@distinctUnionOfSets オペレータを取り上げます。


@distinctUnionOfSets オペレータは、取得した NSSet インスタンスに含まれるオブジェクトを重複のないように収集し、配列として返すオペレータです。基本的には前回取り上げた @unionOfSets オペレータと同じですが、重複がない配列を返すところが異なります。

前回は、次のような NSDictionary 型変数 bowling から、player A, B, C 全てのスコアを持つ配列を取得しました。

bowling = {
    players = (
        {
            name = A;
            scores = {(
                120,
                150,
                180
            )};
        },
        {
            name = B;
            scores = {(
                170,
                190,
                120
            )};
        },
        {
            name = C;
            scores = {(
                50,
                250
            )};
        }
    );
}

* scores は NSSet です

NSArray *allScores;
allScores = [bowling valueForKeyPath:
    @"players.@unionOfSets.scores"];

上の例で、allScores は (120, 150, 180, 120, 190, 170, 250, 50) という配列になりました。

@distinctUnionOfSets オペレータを使うと、重複したオブジェクトが含まれなくなります。

NSArray *distinctScores;
distinctScores = [bowling valueForKeyPath:
    @"players.@distinctUnionOfSets.scores"];

上の例で、distinctScores は (170, 180, 150, 250, 120, 190, 50) という配列になります。重複していた 120 がひとつだけになっています。

@distinctUnionOfSets を使わずに distinctScores を求める場合、コードは次のようになります。

NSArray *distinctScores;
NSMutableSet *scoreSet;
NSArray *players;
players = [bowling valueForKeyPath:@"players"];
scoreSet = [NSMutableSet set];
for (NSDictionary *player in players) {
    [scoreSet addObjectsFromArray:
        [[player valueForKeyPath:@"scores"] allObjects]];
}
distinctScores = [scoreSet allObjects];

@distinctUnionOfSets オペレータを使用した方が、簡潔に結果を求めることができます。

パフォーマンスはどうでしょうか。比較してみました。
比較には以下のコードを使用しました。使用した機種は MacBook Pro。Mac アプリとして作成し、実行しました。

@implementation distinctUnionOfSetsAppDelegate

- (NSDictionary*)bowling
{
    // Make players
    NSMutableArray *players;
    players = [NSMutableArray array];
    
    // Add player to players
    NSDictionary *player;
    NSSet *scores;
    for (NSInteger i = 0; i < 1000; i++) {
        // Make scores
        scores = [NSSet setWithObjects:
            [NSNumber numberWithUnsignedInt:arc4random()],
            [NSNumber numberWithUnsignedInt:arc4random()],
            [NSNumber numberWithUnsignedInt:arc4random()],
            nil];
        
        // Make player
        player = [NSDictionary dictionaryWithObjectsAndKeys:
            [NSString stringWithFormat:@"Name%ld", i], @"name",
            scores, @"scores",
            nil];
        
        // Add player to players
        [players addObject:player];
    }
    
    // Make bowling
    NSDictionary *bowling;
    bowling = [NSDictionary dictionaryWithObject:players forKey:@"players"];
    
    return bowling;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    CFAbsoluteTime startTime;
    CFTimeInterval sumOfTime;
    NSUInteger const count = 100;
    
    // Get bowling
    NSDictionary *bowling;
    bowling = [self bowling];
    
    // @distinctUnionOfSets version
    sumOfTime = 0.0;
    for (int i = 0; i < count; i++) {
        // Make pool
        NSAutoreleasePool *pool;
        pool = [[NSAutoreleasePool alloc] init];
        
        // Record startTime
        startTime = CFAbsoluteTimeGetCurrent();
        
        // Get distinctScores
        NSArray *distinctScores;
        distinctScores = [bowling valueForKeyPath:
            @"players.@distinctUnionOfSets.scores"];
        
        // Update sumOfTime
        sumOfTime += (CFAbsoluteTimeGetCurrent() - startTime);
        
        // Release pool
        [pool release];
    }
    NSLog(@"time average = %f", sumOfTime / (double)count);
    
    // Manual version
    sumOfTime = 0.0;
    for (int i = 0; i < count; i++) {
        // Make pool
        NSAutoreleasePool *pool;
        pool = [[NSAutoreleasePool alloc] init];
        
        // Record startTime
        startTime = CFAbsoluteTimeGetCurrent();
        
        // Get distinctScores
        NSArray *distinctScores;
        NSMutableSet *scoreSet;
        NSArray *players;
        players = [bowling valueForKeyPath:@"players"];
        scoreSet = [NSMutableSet set];
        for (NSDictionary *player in players) {
            [scoreSet addObjectsFromArray:
                [[player valueForKeyPath:@"scores"] allObjects]];
        }
        distinctScores = [scoreSet allObjects];
        
        // Update sumOfTime
        sumOfTime += (CFAbsoluteTimeGetCurrent() - startTime);
        
        // Release pool
        [pool release];
    }
    NSLog(@"time average = %f", sumOfTime / (double)count);
}

@end

測定結果は以下の通りになりました:

@distinctUnionOfSets version 0.001745 s
Manual version 0.002210 s

@distinctUnionOfSets オペレータを使用した方が速度が若干速くなる、という結果が出ました。@distinctUnionOfSets は普段使いしてもよさそうです。

ここまで数回の記事で、Set And Array Operators の基本的な使い方をマスターしました。次回は、Set And Array Operators のもう少し高度な使い方を取り上げたいと思います。

From → Develop

Leave a Comment

Leave a comment