it-swarm-eu.dev

Nejlepší způsob, jak odstranit z NSMutableArray při iteraci?

V Cocoa, pokud chci smyčku přes NSMutableArray a odstranit více objektů, které vyhovují určitým kritériím, co je nejlepší způsob, jak to udělat bez restartování smyčky pokaždé, když odstraním objekt?

Dík,

Edit: Jen pro objasnění - Hledal jsem nejlepší způsob, např. něco elegantnějšího než ruční aktualizace indexu, na kterém jsem. Například v C++ můžu dělat;

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}
189
Andrew Grant

Pro přehlednost bych rád vytvořil úvodní smyčku, kde shromažďuji položky, které chcete smazat. Pak je smažu. Zde je příklad použití syntaxe Objective-C 2.0:

NSMutableArray *discardedItems = [NSMutableArray array];

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

[originalArrayOfItems removeObjectsInArray:discardedItems];

Pak není pochyb o tom, zda jsou indexy správně aktualizovány, nebo o jiných drobných účetních detailech.

Upraveno pro přidání:

V jiných odpovědích bylo uvedeno, že inverzní formulace by měla být rychlejší. tj. Pokud iterujete přes pole a vytvoříte nové pole objektů, které chcete zachovat, namísto objektů, které chcete zrušit. To může být pravda (ačkoli co je paměť a náklady na zpracování přidělení nového pole a vyřazení starého?), Ale i když je to rychlejší, nemusí to být tak velký problém, jako by to bylo pro naivní implementaci, protože NSArrays nechovají se jako "normální" pole. Mluví se o mluvení, ale chodí jinou cestou. Podívejte se na dobrou analýzu zde:

Inverzní formulace může být rychlejší, ale nikdy jsem nepotřebovala pečovat, zda je, protože výše uvedená formulace byla vždy dostatečně rychlá pro mé potřeby.

Poselství z domova je pro mě použít co nejjasnější formulaci. Optimalizujte pouze v případě potřeby. Osobně shledávám výše uvedenou formulaci nejjasnější, proto ji používám. Ale pokud je pro vás inverzní formulace jasnější, jděte na to.

379

Ještě jedna variace. Získáte tak čitelnost a dobrý výkon:

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

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

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
80
Corey Floyd

To je velmi jednoduchý problém. Prostě iterujete zpět:

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

To je velmi běžný vzor.

39
Hot Licks

Některé z dalších odpovědí by měly na velmi velkých polích špatný výkon, protože metody jako removeObject: a removeObjectsInArray: zahrnují lineární vyhledávání přijímače, což je odpad, protože již víte, kde je objekt. Jakékoliv volání removeObjectAtIndex: bude také muset kopírovat hodnoty z indexu do konce pole nahoru o jeden slot najednou.

Efektivnější by bylo následující:

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

Protože jsme nastavili kapacitu itemsToKeep, neztrácíme čas při kopírování hodnot. Nepoškozujeme pole na místě, takže můžeme používat rychlou výčet. Použití setArray: k nahrazení obsahu arrayitemsToKeep bude efektivní. V závislosti na kódu můžete dokonce nahradit poslední řádek znakem:

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

Takže ani není třeba kopírovat hodnoty, pouze vyměnit ukazatel.

38
benzado

NSpredicate můžete použít k odstranění položek z vašeho proměnlivého pole. To nevyžaduje žádné smyčky.

Například pokud máte název NSMutableArray, můžete vytvořit predikát jako tento:

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

Následující řádek vám ponechá pole, které obsahuje pouze názvy začínající písmenem b.

[namesArray filterUsingPredicate:caseInsensitiveBNames];

Pokud máte problémy s tvorbou potřebných predikátů, použijte tento odkaz Apple developer link .

28

Udělal jsem výkonnostní test pomocí 4 různých metod. Každý test iteroval prostřednictvím všech prvků v poli 100 000 prvků a odstranil každou 5. položku. Výsledky se příliš nelišily s/bez optimalizace. Byly provedeny v iPadu 4:

(1) removeObjectAtIndex: - 271 ms

(2) removeObjectsAtIndexes: - 1010 ms (protože vytváření sady indexů trvá ~ 700 ms; jinak je to v podstatě totéž jako volání removeObjectAtIndex: pro každou položku)

(3) removeObjects: - 326 ms

(4) vytvořit nové pole s objekty procházející testem - 17 ms

Vytvoření nového pole je tedy zdaleka nejrychlejší. Ostatní metody jsou všechny srovnatelné, s výjimkou toho, že pomocí removeObjectsAtIndexes: bude horší s více položek k odstranění, protože čas potřebný pro sestavení sady indexů.

18
user1032657

Použijte smyčku odpočítávání nad indexy:

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

nebo vytvořte kopii s objekty, které chcete zachovat.

Zejména nepoužívejte smyčku for (id object in array) nebo NSEnumerator.

17
Jens Ayton

Pro systém iOS 4+ nebo OS X 10.6+ přidal Apple passingTest řadu rozhraní API v souboru NSMutableArray, jako je – indexesOfObjectsPassingTest:. Řešení s takovým API by bylo:

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

V současné době můžete použít obrácené blokové enumerování. Jednoduchý příklad kódu:

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

Výsledek:

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

další možnost s jedním řádkem kódu:

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

Dle deklarativnějšího způsobu, v závislosti na kritériích odpovídajících položkám, které chcete odebrat, můžete použít:

[theArray filterUsingPredicate:aPredicate]

@Nathan by měl být velmi efektivní

8
Ivan Leider

Zde je jednoduchý a čistý způsob. Mám rád duplikovat své pole vpravo v rychlém výčtu volání:

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

Tímto způsobem vytvoříte výčet prostřednictvím kopie pole, které je odstraněno z obou, které drží stejné objekty. Nástroj NSArray uchovává ukazatele objektu pouze tak, aby to bylo naprosto jemné.

6
Matjan

Přidejte objekty, které chcete odebrat, do druhého pole a po smyčce použijte příkaz -removeObjectsInArray :.

5
Nathan Kinsinger

to by mělo být:

    NSMutableArray* myArray = ....;

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

snad to pomůže...

5
Pokot0

Příjemnější implementace by mohla být použití metody kategorie níže na 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

Blok predikátu může být implementován pro zpracování na každém objektu v poli. Pokud predikát vrátí hodnotu true, objekt bude odebrán.

Příklad pole data k odstranění všech dat, která leží v minulosti:

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

Proč nepřidáte objekty, které mají být odebrány, do jiného nástroje NSMutableArray. Po dokončení iterace můžete odebrat objekty, které jste shromáždili.

1
Paul Croarkin

Jak o výměně prvků, které chcete odstranit, s elementem 'n'th,' n-1'th a tak dále?

Až budete hotovi, změňte velikost pole na 'předchozí velikost - počet swapů'

1

Pokud jsou všechny objekty ve vašem poli jedinečné nebo chcete-li odstranit všechny výskyty objektu, když je nalezen, můžete rychle zkopírovat na kopii pole a pomocí příkazu [NSMutableArray removeObject:] objekt odebrat z originálu.

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

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

Definuji kategorii, která mi umožňuje filtrovat pomocí bloku, jako je tento:

@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

které lze použít takto:

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

benzado odpověď výše je to, co byste měli udělat pro výkon. V jednom z mých aplikací removeObjectsInArray trvalo dobu běhu 1 minuty, jen přidání do nového pole trvalo 0,23 sekund. 

1
Kaiser

Iterace zpětně byla moje nejoblíbenější léta, ale dlouho jsem se nikdy nesetkala s případem, kdy byl nejprve odstraněn „nejhlubší“ (nejvyšší počet) objekt. Krátce před tím, než ukazatel přejde k dalšímu indexu, není nic a padne. 

Benzado je nejblíže tomu, co teď dělám, ale nikdy jsem si neuvědomil, že by se po každém odstranění přeorientoval zásobník.

pod Xcode 6 to funguje

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];
0
aremvee