この質問は簡単に思えるかもしれませんが、最も効率的でメモリに優しい方法を探しています。
Personオブジェクトの配列があるとしましょう。各人は、NSString
で表される髪の色を持っています。次に、髪の色が茶色である配列からすべてのPersonオブジェクトを削除するとします。
どうすればいいですか?
列挙されている配列からオブジェクトを削除できないことに注意してください。
2つの一般的なアプローチがあります。各要素をテストし、テスト基準を満たす場合はすぐに要素を削除できます。または、各要素をテストし、テスト基準を満たす要素のインデックスを保存してから、そのような要素をすべて一度に削除できます。メモリ使用量が大きな懸念事項であるため、後者のアプローチのストレージ要件は望ましくない場合があります。
「削除するすべてのインデックスを保存してから削除する」というアプローチをテーブルから外すと、前者のアプローチに含まれる詳細と、それらがアプローチの正確さと速度にどのように影響するかを考慮する必要があります。このアプローチでは、2つの致命的なエラーが待機しています。最初の方法は、配列内のインデックスにではなく、removeObject:
方法。 removeObject:
は、削除するオブジェクトを見つけるために配列の線形検索を行います。ソートされていない大規模なデータセットでは、入力サイズの2乗で時間が長くなるとパフォーマンスが低下します。ちなみに、indexOfObject:
その後 removeObjectAtIndex:
も同様に悪いので、避けるべきです。 2番目の致命的なエラーは、インデックス0で反復を開始することです。NSMutableArray
は、オブジェクトを追加または削除した後にインデックスを再配置します。したがって、インデックス0反復中に1つのオブジェクトが削除されます。そのため、配列の後ろから開始し、これまでにチェックしたすべてのインデックスよりも低いインデックスを持つオブジェクトのみを削除する必要があります。
それをレイアウトしたら、2つの明白な選択肢があります。配列の先頭ではなく末尾から開始するfor
ループ、またはNSArray
メソッドenumerateObjectsWithOptions:usingBlock:
方法。それぞれの例を次に示します。
[persons enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Person *p, NSUInteger index, BOOL *stop) {
if ([p.hairColor isEqualToString:@"brown"]) {
[persons removeObjectAtIndex:index];
}
}];
NSInteger count = [persons count];
for (NSInteger index = (count - 1); index >= 0; index--) {
Person *p = persons[index];
if ([p.hairColor isEqualToString:@"brown"]) {
[persons removeObjectAtIndex:index];
}
}
私のテストでは、for
ループがわずかに高速であるように見えます-500,000の要素で約1/4秒高速になる可能性があり、これは基本的に8.5秒と8.25秒の違いです。したがって、ブロックアプローチを使用することをお勧めします。ブロックアプローチはより安全で、より慣用的に感じられるからです。
可変配列を処理しており、ソート/インデックス付けされていない(つまり、配列全体をスキャンする必要がある)と仮定すると、 enumerateObjectsWithOptions
を使用して逆順で配列を反復処理できます。 = NSEnumerationReverse
オプション付き:
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// now you can remove the object without affecting the enumeration
}];
逆の順序で実行すると、列挙されている配列からオブジェクトを削除できます。
NSMutableArray * tempArray = [self.peopleArray mutableCopy];
for (Person * person in peopleArray){
if ([person.hair isEqualToString: @"Brown Hair"])
[tempArray removeObject: person]
}
self.peopleArray = tempArray;
またはNSPredicateも動作します: http://nshipster.com/nspredicate/
重要なのは、配列のフィルタリングに述語を使用することです。以下のコードを参照してください。
- (NSArray*)filterArray:(NSArray*)list
{
return [list filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings){
People *currentObj = (People*)evaluatedObject;
return (![currentObj.hairColour isEqualToString:@"brown"]);
}]];
}
特定の項目がフィルターで除外された配列のコピーを作成している場合は、新しい可変配列を作成し、オリジナルを反復処理して、その回答に他の人が提案しているように、その場でコピーに追加します。しかし、あなたの質問は、既存の(おそらく可変の)配列から削除することです。
繰り返しながら、削除するオブジェクトの配列を作成し、後で削除することができます。
NSMutableArray *thePeople = ...
NSString *hairColorToMatch = ...
NSMutableArray *matchingObjects = [NSMutableArray array];
for (People *person in thePeople) {
if (person.hairColor isEqualToString:hairColorToMatch])
[matchingObjects addObject:person];
[thePeople removeObjects:matchingObjects];
しかし、これは無駄だと思うかもしれない一時的な配列を作成し、さらに重要なことには、removeObjects:
が非常に効率的であることを確認するのは困難です。また、誰かが重複項目を持つ配列について何かを述べましたが、これはその場合は機能するはずですが、最善ではありません。各重複は一時配列にもあり、removeObjects:
内の冗長マッチングです。
代わりにインデックスで反復して削除することもできますが、ループロジックがかなり厄介になります。代わりに、インデックスセット内のインデックスを収集し、後で削除します。
NSMutableIndexSet *matchingIndexes = [NSMutableIndexSet indexSet];
for (NSUInteger n = thePeople.count, i = 0; i < n; ++i) {
People *person = thePeople[i];
if ([person.hairColor isEqualToString:hairColorToMatch])
[matchingIndexes addIndex:i];
}
[thePeople removeObjectsAtIndexes:matchingIndexes];
インデックスセットのオーバーヘッドは非常に低いため、これはほぼ効率的で、手間がかかります。このように最後にバッチで削除することに関するもう1つのことは、AppleがremoveObjectsAtIndexes:
をremoveObjectAtIndex:
のシーケンスよりも最適化した可能性があることです。インデックスセットのデータ構造を作成するオーバーヘッドであるため、反復中にその場で削除するよりも優れている場合があります。
代わりに、実際にフィルターされたコピーを作成している場合、使用できるいくつかのKVC
コレクション演算子があると思いました(最近それらについて読んでいたので、 NSHipster & 英語 )。明らかにいいえ、しかし近い、KVCandNSPredicateを使用する必要があります。
NSArray *subsetOfPeople = [allPeople filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:@"SELF.hairColor != %@", hairColorToMatch]];
先に進んでNSArray
にカテゴリを作成し、コード、filterWithFormat:
などの内容をより簡潔にします。
(すべて未テスト、SOに直接入力)
このようにしてみてください
NSIndexSet *indices = [personsArray indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:@"hair"] isEqual:@"Brown Hair"];
}];
NSArray *filtered = [personsArray objectsAtIndexes:indices];
OR
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.hair=%@ ",@"Brown Hair"];
NSArray* myArray = [personsArray filteredArrayUsingPredicate:predicate];
NSLog(@"%@",myArray);