KVC: Set And Array Operators @distinctUnionOfSets
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 のもう少し高度な使い方を取り上げたいと思います。