最も効率的でメモリにやさしい方法を探しています。
Person
オブジェクトの配列があるとしましょう。各人は で表される髪の色を持っていNSString
ます。Person
次に、髪の色が茶色であるすべてのオブジェクトを配列から削除したいとします。
どうすればいいですか?
列挙されている配列からオブジェクトを削除することはできないことに注意してください。
最も効率的でメモリにやさしい方法を探しています。
Person
オブジェクトの配列があるとしましょう。各人は で表される髪の色を持っていNSString
ます。Person
次に、髪の色が茶色であるすべてのオブジェクトを配列から削除したいとします。
どうすればいいですか?
列挙されている配列からオブジェクトを削除することはできないことに注意してください。
2 つの一般的なアプローチがあります。各要素をテストし、テスト基準を満たしている場合はすぐに要素を削除するか、各要素をテストしてテスト基準を満たす要素のインデックスを保存し、そのような要素をすべて一度に削除できます。メモリ使用量が実際の懸念事項であるため、後者のアプローチのストレージ要件により、望ましくない場合があります。
「削除するすべてのインデックスを保存してから削除する」というアプローチが検討されていないため、前者のアプローチに含まれる詳細と、それらがアプローチの正確さと速度にどのように影響するかを考慮する必要があります。このアプローチには、2 つの致命的なエラーが待ち受けています。1 つ目は、評価されたオブジェクトを配列内のインデックスではなくremoveObject:
メソッドで削除することです。removeObject:
配列の線形検索を実行して、削除するオブジェクトを見つけます。並べ替えられていない大規模なデータ セットでは、時間が入力サイズの 2 乗で増加するため、パフォーマンスが低下します。ちなみに、indexOfObject:
and thenの使用removeObjectAtIndex:
も同様に悪いため、これも避ける必要があります。2 番目の致命的なエラーは、反復をインデックス 0 から開始することです。NSMutableArray
オブジェクトを追加または削除した後にインデックスを再配置するため、インデックス 0 から開始すると、反復中にオブジェクトが 1 つでも削除された場合に、インデックス範囲外の例外が保証されます。したがって、配列の後ろから始めて、これまでチェックしたすべてのインデックスよりも低いインデックスを持つオブジェクトのみを削除する必要があります。
それを整理すると、実際には 2 つの明らかな選択肢があります。for
配列の先頭ではなく末尾から開始するループ、またはNSArray
メソッドenumerateObjectsWithOptions:usingBlock:
method です。それぞれの例は次のとおりです。
[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 要素で約 4 分の 1 秒高速です。これは、基本的に 8.5 秒と 8.25 秒の差です。したがって、ブロック アプローチを使用することをお勧めします。ブロック アプローチの方が安全で、より慣用的な感じがするからです。
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 & Guy Englishに従って、いくつかのクレイジーなことを行うことができます)。どうやらいいえ、しかし近いのですが、このやや冗長な行で KVCとNSPredicateを使用する必要があります。
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];
また
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.hair=%@ ",@"Brown Hair"];
NSArray* myArray = [personsArray filteredArrayUsingPredicate:predicate];
NSLog(@"%@",myArray);
NSMutableArray *arrayForStuff = ...
[arrayForStuff removeObjectAtIndex:[arrayForStuff indexOfObject:objectToRemove]];