Frage

In Cocoa, wenn ich eine Schleife durch eine NSMutableArray will und mehrere Objekte entfernen, die ein bestimmten Kriterien passen, was ist der beste Weg, dies zu tun, ohne die Schleife jedes Mal neu zu starten ich ein Objekt entfernen?

Danke,

Edit: Nur um zu klären - ich war nach dem besten Weg suchen, zum Beispiel etwas eleganter als manuell den Index ich bin zu aktualisieren. Zum Beispiel in C ++ ich tun kann;

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}
War es hilfreich?

Lösung

Aus Gründen der Klarheit Ich mag eine anfängliche Schleife machen, wo ich die Gegenstände zu sammeln, zu löschen. Dann sie ich löschen. Hier ist ein Beispiel Objective-C 2.0-Syntax:

NSMutableArray *discardedItems = [NSMutableArray array];

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

[originalArrayOfItems removeObjectsInArray:discardedItems];

Dann gibt es keine Frage, ob Indizes korrekt aktualisiert werden, oder andere kleine Buchhaltung Details.

Edited hinzufügen:

Es ist schon in anderen Antworten darauf hingewiesen, dass die inverse Formulierung schneller sein sollte. d Wenn Sie das Array durchlaufen und komponieren, anstatt Objekte ein neues Array von Objekten zu halten, zu verwerfen. Das mag wahr sein (obwohl das, was über die Speicher- und Verarbeitungskosten für ein neue Array Zuweisung, und die alten verworfen?), Aber auch wenn es schneller es nicht so große Sache sein kann, wie es für eine naive Implementierung wäre, weil NSArrays nicht wie „normaler“ Arrays verhalten. Sie sprechen das Gespräch aber sie gehen einen anderen Weg. Siehe eine gute Analyse hier:

Die inverse Formulierung schneller sein kann, aber ich habe nie zu kümmern braucht, ob es, weil die obige Formulierung hat für meine Bedürfnisse immer schnell genug.

Für mich ist die Take-Home-Nachricht ist zu verwenden, was Formulierung ist Ihnen klarsten. Optimieren Sie nur bei Bedarf. Ich persönlich finde die obige Formulierung klarste, weshalb ich es verwenden. Aber wenn die inverse Formulierung klarer zu Ihnen, denn es geht.

Andere Tipps

Eine weitere Variation. So erhalten Sie die Lesbarkeit und eine gute Leistung:

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

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

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];

Dies ist ein sehr einfaches Problem. Sie durchlaufen gerade nach hinten:

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

Dies ist ein sehr häufiges Muster.

Einige der anderen Antworten würden auf sehr große Arrays schlechte Leistung haben, weil Methoden wie removeObject: und removeObjectsInArray: beinhalten eine lineare Suche des Empfängers zu tun, was eine Verschwendung ist, weil Sie bereits wissen, wo das Objekt ist. Auch jeder Anruf zu removeObjectAtIndex: wird Werte aus dem Index bis zum Ende des Arrays kopieren up um einen Schlitz zu einem Zeitpunkt müssen.

Effizienter wäre die folgende:

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

Weil wir die Kapazität von itemsToKeep gesetzt, wir verschwenden keine Zeit Kopieren Werte während eines Resize. Wir ändern das Array nicht an Ort und Stelle, so sind wir frei Schnelle Enumeration zu verwenden. Mit setArray: den Inhalt array mit itemsToKeep ersetzen wird effizient sein. Je nach Ihrem Code, könnten Sie sogar die letzte Zeile ersetzen mit:

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

So gibt es nicht einmal notwendig ist, Werte zu kopieren, tauschen nur einen Zeiger.

Sie können NSPredicate verwenden, um Elemente aus dem änderbaren Array zu entfernen. Dies erfordert nicht für Schleifen.

Wenn Sie zum Beispiel eine NSMutableArray von Namen haben, können Sie ein Prädikat wie diese erstellen:

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

Die folgende Zeile wird Sie mit einem Array lassen, die nur Namen enthalten, die mit b beginnen.

[namesArray filterUsingPredicate:caseInsensitiveBNames];

Wenn Sie Probleme haben die Prädikate Erstellen Sie benötigen, verwenden Sie diesen Apfel Entwickler Link .

Ich habe einen Performance-Test mit 4 verschiedenen Methoden. Jeder Test iteriert durch alle Elemente in einer 100.000-Element-Array und entfernt jedes fünftes Element. Die Ergebnisse haben variieren nicht viel mit / ohne Optimierung. Diese wurden durchgeführt auf einem iPad 4:

(1) removeObjectAtIndex: - 271 ms

(2) removeObjectsAtIndexes: - 1010 ms (weil Aufbau der Indexmenge nimmt ~ 700 ms, andernfalls ist dies im Grunde das gleiche wie der Aufruf von removeObjectAtIndex: für jedes Element)

(3) removeObjects: - 326 ms

(4) macht ein neues Array mit Objekten bestandene Prüfung - 17 ms

Also, ein neues Array zu schaffen ist mit Abstand am schnellsten. Die anderen Verfahren sind alle vergleichbar, mit der Ausnahme, dass die Verwendung removeObjectsAtIndexes: mit mehr Einzelteilen schlechter zu entfernen, wegen der Zeit erforderlich, um das Index-Set erstellen

.

Entweder über Indizes Verwendung Schleife Abzählen:

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

oder eine Kopie mit den Objekten, die Sie behalten möchten.

Insbesondere keine for (id object in array) Schleife oder NSEnumerator verwenden.

Für iOS 4+ oder OS X 10.6+ Apple hinzugefügt passingTest Reihe von APIs in NSMutableArray, wie – indexesOfObjectsPassingTest:. Eine Lösung mit einem solchen API wäre:

NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
    ^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];

Heutzutage kann man reversed blockbasierte Aufzählung verwenden. Ein einfaches Beispiel Code:

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];
}];

Ergebnis:

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

eine weitere Option mit nur einer Codezeile:

[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];

In einer deklarativen Weise auf den Kriterien in Abhängigkeit den gleichen Artikel zu entfernen, die Sie verwenden können:

[theArray filterUsingPredicate:aPredicate]

@Nathan sollte sehr effizient sein

Hier ist die einfache und saubere Art und Weise. Ich mag meinen Array direkt in dem schnellen Aufzählung Aufruf kopieren:

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

Auf diese Weise kann durch eine Kopie des Arrays aufzuzählen aus gelöscht werden, die beide die gleichen Objekten zu halten. Ein NSArray hält Objektzeiger nur so ist dies völlig in Ordnung Speicher / Leistung ist weise.

Fügen Sie die Objekte, die Sie auf ein zweites Array entfernen möchten, und nach der Schleife verwenden -removeObjectsInArray:.

Dies sollte es tun:

    NSMutableArray* myArray = ....;

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

hoffe, das hilft ...

Warum nicht Sie die Objekte in dem anderen NSMutableArray entfernt werden. Wenn Sie Iterieren fertig sind, können Sie die Objekte entfernen, die Sie gesammelt haben.

Wie wäre es, die Elemente tauschen Sie mit der zu löschenden ‚n-ten Element,‘ n-1-ten Element und so weiter?

Wenn Sie fertig sind Sie das Array, um die Größe ‚vorherige Größe - Anzahl der Swaps‘

Wenn alle Objekte in Ihrem Array eindeutig sind oder möchten Sie alle Vorkommen eines Objekts entfernen, wenn gefunden, könnten Sie schnell aufzuzählen, die auf einem Array kopieren und verwenden [NSMutableArray removeObject:]. Das Objekt aus dem Original entfernen

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

for (NSObject *anObject in myArrayCopy) {
    if (shouldRemove(anObject)) {
        [myArray removeObject:anObject];
    }
}

benzado Antwort oben ist, was Sie für die Leistung tun sollten. In einem meiner Anwendungen nahm removeObjectsInArray Laufzeit von 1 Minute, das Hinzufügen nur in ein neues Array dauerte 0,023 Sekunden.

Ich definiere eine Kategorie, die mich mit einem Block filtern lässt, wie folgt aus:

@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

die dann wie folgt verwendet werden:

[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
    return [self doIWantToKeepThisObject:obj atIndex:idx];
}];

Ein schöner Implementierung könnte die Kategorie Methode unten auf NSMutableArray zu verwenden.

@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

Das Prädikat Block kann implementiert werden, die Verarbeitung in der Anordnung auf jedes Objekt zu tun. Wenn das Prädikat true zurückgibt das Objekt entfernt wird.

Ein Beispiel für ein Datum Array alle Daten zu entfernen, die in der Vergangenheit liegen:

NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
    NSDate *date = (NSDate *)obj;
    return [date timeIntervalSinceNow] < 0;
}];

Iterieren rückwärts-ly war mein Favorit seit Jahren, aber für eine lange Zeit, die ich noch nie begegnet den Fall, in dem die ‚tiefste‘ (höchste Zählung) Objekt zuerst entfernt wurde. Für einen Moment, bevor der Zeiger auf den nächsten Index bewegt, ist es nichts und es stürzt ab.

benzado Weg ist in der Nähe zu dem, was ich jetzt tun, aber ich es nie realisierte die Stapel Kabinettsumbildung nach jedem Entfernen wäre.

unter Xcode 6 das funktioniert

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];
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top