197

Cocoaで、NSMutableArrayをループして特定の基準に一致する複数のオブジェクトを削除する場合、オブジェクトを削除するたびにループを再開せずにこれを行うための最良の方法は何ですか?

ありがとう、

編集:明確にするために-私は最良の方法を探していました。たとえば、現在のインデックスを手動で更新するよりもエレガントな方法を探していました。たとえば、C++では次のことができます。

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}
4

20 に答える 20

391

わかりやすくするために、削除するアイテムを収集する最初のループを作成するのが好きです。それから私はそれらを削除します。これは、Objective-C2.0構文を使用したサンプルです。

NSMutableArray *discardedItems = [NSMutableArray array];

for (SomeObjectClass *item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addObject:item];
}

[originalArrayOfItems removeObjectsInArray:discardedItems];

次に、インデックスが正しく更新されているかどうか、またはその他の小さな簿記の詳細については疑問の余地はありません。

追加するために編集:

他の回答では、逆定式化の方が高速である必要があることが指摘されています。つまり、配列を反復処理して、破棄するオブジェクトではなく、保持するオブジェクトの新しい配列を作成する場合。それは本当かもしれませんが(新しいアレイを割り当てて古いアレイを破棄するためのメモリと処理コストはどうですか?)、たとえ高速であっても、NSArraysのため、単純な実装の場合ほど大したことではないかもしれません。 「通常の」アレイのように動作しないでください。彼らは話をしますが、彼らは別の散歩をします。ここで良い分析を参照してください:

逆の定式化はより速いかもしれませんが、上記の定式化は常に私のニーズに対して十分に速いので、私はそれがそうであるかどうかを気にする必要はありませんでした。

私にとって、持ち帰りのメッセージは、あなたにとって最も明確な定式化を使用することです。必要な場合にのみ最適化してください。私は個人的に上記の定式化が最も明確であると感じています。それが私がそれを使用する理由です。しかし、逆の定式化があなたにとってより明確であるならば、それを選んでください。

于 2008-09-21T23:23:49.080 に答える
83

もう1つのバリエーション。したがって、読みやすさと優れたパフォーマンスが得られます。

NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;

for (item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addIndex:index];
    index++;
}

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
于 2009-06-19T20:33:29.977 に答える
47

これは非常に単純な問題です。逆方向に繰り返すだけです:

for (NSInteger i = array.count - 1; i >= 0; i--) {
   ElementType* element = array[i];
   if ([element shouldBeRemoved]) {
       [array removeObjectAtIndex:i];
   }
}

これは非常に一般的なパターンです。

于 2013-08-27T04:24:18.827 に答える
39

他の回答のいくつかは、非常に大きな配列ではパフォーマンスが低下します。これは、レシーバーの線形検索を実行するなどの方法が含まれるためです。これは、オブジェクトがどこにあるかを既に知っているため、無駄ですremoveObject:removeObjectsInArray:また、を呼び出すremoveObjectAtIndex:場合は、インデックスから配列の最後まで、一度に1スロットずつ値をコピーする必要があります。

より効率的なのは次のとおりです。

NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
    if (! shouldRemove(object)) {
        [itemsToKeep addObject:object];
    }
}
[array setArray:itemsToKeep];

の容量を設定しているためitemsToKeep、サイズ変更中に値をコピーする時間を無駄にすることはありません。インプレースで配列を変更しないため、FastEnumerationを自由に使用できます。の内容をsetArray:に置き換えるために使用すると効率的です。コードによっては、最後の行を次のように置き換えることもできます。arrayitemsToKeep

[array release];
array = [itemsToKeep retain];

したがって、値をコピーする必要はなく、ポインタを交換するだけです。

于 2008-09-24T08:51:49.947 に答える
28

NSpredicate を使用して、変更可能な配列からアイテムを削除できます。これには for ループは必要ありません。

たとえば、名前の NSMutableArray がある場合、次のような述語を作成できます。

NSPredicate *caseInsensitiveBNames = 
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];

次の行は、b で始まる名前のみを含む配列を残します。

[namesArray filterUsingPredicate:caseInsensitiveBNames];

必要な述語の作成に問題がある場合は、このApple 開発者リンクを使用してください。

于 2008-09-24T20:59:56.250 に答える
18

4 つの異なる方法を使用してパフォーマンス テストを行いました。各テストは、100,000 要素配列内のすべての要素を反復処理し、5 番目の項目ごとに削除しました。最適化の有無にかかわらず、結果はあまり変わりませんでした。これらは iPad 4 で行われました。

(1) removeObjectAtIndex:-- 271 ミリ秒

(2) removeObjectsAtIndexes:-- 1010 ミリ秒(インデックス セットの構築には約 700 ミリ秒かかるため、それ以外の場合は、各アイテムに対して removeObjectAtIndex: を呼び出すのと基本的に同じです)

(3) removeObjects:-- 326ms

(4) テストに合格したオブジェクトで新しい配列を作成 -- 17 ミリ秒

そのため、新しい配列を作成するのが断然最速です。removeObjectsAtIndexes: を使用すると、インデックス セットの作成に時間がかかるため、削除するアイテムが増えるとさらに悪化することを除いて、他のメソッドはすべて同等です。

于 2013-06-03T21:04:53.577 に答える
17

インデックスをカウントダウンするループを使用するか、次のようにします。

for (NSInteger i = array.count - 1; i >= 0; --i) {

または、保持したいオブジェクトを使用してコピーを作成します。

for (id object in array)特に、ループや。は使用しないでくださいNSEnumerator

于 2008-09-21T19:52:07.170 に答える
12

iOS 4 以降または OS X 10.6 以降の場合、Apple はpassingTest一連の API を にNSMutableArray追加しまし– indexesOfObjectsPassingTest:た。このような API を使用したソリューションは次のようになります。

NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
    ^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];
于 2012-08-18T16:41:11.723 に答える
12

最近では、逆のブロックベースの列挙を使用できます。簡単なコード例:

NSMutableArray *array = [@[@{@"name": @"a", @"shouldDelete": @(YES)},
                           @{@"name": @"b", @"shouldDelete": @(NO)},
                           @{@"name": @"c", @"shouldDelete": @(YES)},
                           @{@"name": @"d", @"shouldDelete": @(NO)}] mutableCopy];

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if([obj[@"shouldDelete"] boolValue])
        [array removeObjectAtIndex:idx];
}];

結果:

(
    {
        name = b;
        shouldDelete = 0;
    },
    {
        name = d;
        shouldDelete = 0;
    }
)

コードを 1 行だけ追加する別のオプション:

[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];
于 2013-08-28T02:05:49.933 に答える
8

より宣言的な方法では、削除するアイテムに一致する基準に応じて、次を使用できます。

[theArray filterUsingPredicate:aPredicate]

@ネイサンは非常に効率的でなければなりません

于 2008-09-21T20:51:06.093 に答える
6

これが簡単できれいな方法です。高速列挙呼び出しで配列を複製するのが好きです。

for (LineItem *item in [NSArray arrayWithArray:self.lineItems]) 
{
    if ([item.toBeRemoved boolValue] == YES) 
    {
        [self.lineItems removeObject:item];
    }
}

このようにして、削除される配列のコピーを列挙し、両方とも同じオブジェクトを保持します。NSArray はオブジェクト ポインタのみを保持するため、これはメモリ/パフォーマンスに関してはまったく問題ありません。

于 2013-08-28T01:42:06.800 に答える
5

削除するオブジェクトを2番目の配列に追加し、ループの後に-removeObjectsInArray:を使用します。

于 2008-09-21T19:50:41.963 に答える
5

これはそれを行う必要があります:

    NSMutableArray* myArray = ....;

    int i;
    for(i=0; i<[myArray count]; i++) {
        id element = [myArray objectAtIndex:i];
        if(element == ...) {
            [myArray removeObjectAtIndex:i];
            i--;
        }
    }

お役に立てれば...

于 2008-09-21T19:55:11.110 に答える
1

削除するオブジェクトを別のNSMutableArrayに追加してみませんか。反復が終了したら、収集したオブジェクトを削除できます。

于 2008-09-21T19:51:40.620 に答える
1

削除したい要素を「n」番目の要素、「n-1」番目の要素などと交換するのはどうですか?

完了したら、配列のサイズを「以前のサイズ-スワップの数」に変更します。

于 2008-09-21T21:12:19.100 に答える
1

次のように、ブロックを使用してフィルタリングできるカテゴリを定義します。

@implementation NSMutableArray (Filtering)

- (void)filterUsingTest:(BOOL (^)(id obj, NSUInteger idx))predicate {
    NSMutableIndexSet *indexesFailingTest = [[NSMutableIndexSet alloc] init];

    NSUInteger index = 0;
    for (id object in self) {
        if (!predicate(object, index)) {
            [indexesFailingTest addIndex:index];
        }
        ++index;
    }
    [self removeObjectsAtIndexes:indexesFailingTest];

    [indexesFailingTest release];
}

@end

これは、次のように使用できます。

[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
    return [self doIWantToKeepThisObject:obj atIndex:idx];
}];
于 2012-01-23T13:10:36.610 に答える
1

上記の benzado の anwser は、前処理のために行うべきことです。私のアプリケーションの 1 つで、removeObjectsInArray の実行時間は 1 分で、新しい配列に追加するだけで 0.023 秒かかりました。

于 2010-06-17T21:31:00.180 に答える
1

より適切な実装は、NSMutableArray で以下のカテゴリ メソッドを使用することです。

@implementation NSMutableArray(BMCommons)

- (void)removeObjectsWithPredicate:(BOOL (^)(id obj))predicate {
    if (predicate != nil) {
        NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:self.count];
        for (id obj in self) {
            BOOL shouldRemove = predicate(obj);
            if (!shouldRemove) {
                [newArray addObject:obj];
            }
        }
        [self setArray:newArray];
    }
}

@end

述語ブロックを実装して、配列内の各オブジェクトを処理できます。述語が true を返す場合、オブジェクトは削除されます。

過去にあるすべての日付を削除する日付配列の例:

NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
    NSDate *date = (NSDate *)obj;
    return [date timeIntervalSinceNow] < 0;
}];
于 2015-08-27T09:58:43.713 に答える
1

配列内のすべてのオブジェクトが一意である場合、または見つかったオブジェクトをすべて削除したい場合は、配列のコピーをすばやく列挙し、[NSMutableArray removeObject:] を使用して元のオブジェクトからオブジェクトを削除できます。

NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];

for (NSObject *anObject in myArrayCopy) {
    if (shouldRemove(anObject)) {
        [myArray removeObject:anObject];
    }
}
于 2008-09-22T05:06:19.507 に答える
0

逆方向に反復することは何年もの間私のお気に入りでしたが、長い間、「最も深い」(最大カウント) オブジェクトが最初に削除されるケースに遭遇したことはありませんでした。ポインターが次のインデックスに移動する少し前に何もなく、クラッシュします。

Benzado の方法は、私が現在行っていることに最も近いものですが、削除するたびにスタックの再シャッフルがあるとは思いもしませんでした。

Xcode 6ではこれが機能します

NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];

    for (id object in array)
    {
        if ( [object isNotEqualTo:@"whatever"]) {
           [itemsToKeep addObject:object ];
        }
    }
    array = nil;
    array = [[NSMutableArray alloc]initWithArray:itemsToKeep];
于 2015-08-10T06:55:40.960 に答える