W Cocoa, jeśli chcę przejść przez NSMutableArray i usunąć wiele obiektów spełniających określone kryteria, jaki jest najlepszy sposób, aby to zrobić bez ponownego uruchamiania pętli za każdym razem, gdy usuwam obiekt?
Dzięki,
Edycja: Tylko dla wyjaśnienia - szukałem najlepszego sposobu, np. Czegoś bardziej eleganckiego niż ręczne aktualizowanie indeksu, w którym jestem. Na przykład w C ++ mogę zrobić;
iterator it = someList.begin();
while (it != someList.end())
{
if (shouldRemove(it))
it = someList.erase(it);
}
objective-c
cocoa
Andrew Grant
źródło
źródło
Odpowiedzi:
Dla jasności lubię tworzyć początkową pętlę, w której zbieram elementy do usunięcia. Potem je usuwam. Oto przykład z użyciem składni Objective-C 2.0:
Nie ma zatem wątpliwości, czy indeksy są aktualizowane poprawnie, czy też inne szczegóły dotyczące księgowości.
Edytowano, aby dodać:
W innych odpowiedziach zauważono, że odwrotne sformułowanie powinno być szybsze. tzn. jeśli wykonasz iterację przez tablicę i skomponujesz nową tablicę obiektów do zachowania, zamiast obiektów do odrzucenia. To może być prawda (choć co z pamięcią i kosztami przetwarzania przydzielania nowej tablicy i odrzucania starej?), Ale nawet jeśli jest szybsza, może nie być tak duża, jak w przypadku naiwnej implementacji, ponieważ NSArrays nie zachowuj się jak „normalne” tablice. Mówią, ale idą inną drogą.Zobacz dobrą analizę tutaj:
Odwrotne sformułowanie może być szybsze, ale nigdy nie musiałem się tym przejmować, ponieważ powyższy preparat zawsze był wystarczająco szybki dla moich potrzeb.
Dla mnie przesłaniem do domu jest użycie tego, co jest dla ciebie jasne. Zoptymalizuj tylko w razie potrzeby. Osobiście uważam powyższe sformułowanie za najbardziej zrozumiałe i dlatego go używam. Ale jeśli odwrotne sformułowanie jest dla ciebie wyraźniejsze, idź do niego.
źródło
Jeszcze jedna odmiana. Dzięki temu zyskujesz czytelność i dobrą wydajność:
źródło
removeObjectsAtIndexes
to najgorsza metoda na usunięcie obiektów, zgadzasz się z tym? Proszę o to, ponieważ twoja odpowiedź jest już za stara. Nadal dobrze wybrać najlepszy?enumerateObjectsUsingBlock:
dostaniesz przyrost indeksu za darmo.To bardzo prosty problem. Po prostu iterujesz wstecz:
Jest to bardzo powszechny wzór.
źródło
Niektóre inne odpowiedzi miałyby niską wydajność na bardzo dużych tablicach, ponieważ metody takie jak
removeObject:
iremoveObjectsInArray:
polegają na przeprowadzaniu liniowego przeszukiwania odbiornika, co jest marnotrawstwem, ponieważ już wiesz, gdzie jest obiekt. Ponadto każde wywołanie doremoveObjectAtIndex:
będzie musiało kopiować wartości z indeksu na koniec tablicy w górę o jedno miejsce na raz.Bardziej wydajne byłyby następujące:
Ponieważ ustawiamy pojemność
itemsToKeep
, nie marnujemy czasu na kopiowanie wartości podczas zmiany rozmiaru. Nie modyfikujemy tablicy w miejscu, więc możemy swobodnie korzystać z szybkiego wyliczania. KorzystaniesetArray:
zastąpić zawartośćarray
zeitemsToKeep
będzie skuteczny. W zależności od kodu możesz nawet zamienić ostatni wiersz na:Więc nie ma nawet potrzeby kopiowania wartości, wystarczy zamienić wskaźnik.
źródło
Możesz użyć NSpredicate, aby usunąć elementy ze swojej zmiennej tablicy. To nie wymaga pętli.
Na przykład, jeśli masz NSMutableArray nazw, możesz utworzyć predykat taki jak ten:
W poniższym wierszu zostanie wyświetlona tablica zawierająca tylko nazwy zaczynające się na b.
Jeśli masz problemy z utworzeniem potrzebnych predykatów, skorzystaj z tego linku dla programistów Apple .
źródło
Zrobiłem test wydajności przy użyciu 4 różnych metod. W każdym teście powtarzano wszystkie elementy w tablicy 100 000 elementów i usuwano co piąty element. Wyniki nie różniły się zbytnio z / bez optymalizacji. Zostały one wykonane na iPadzie 4:
(1)
removeObjectAtIndex:
- 271 ms(2)
removeObjectsAtIndexes:
- 1010 ms (ponieważ zbudowanie zestawu indeksów zajmuje ~ 700 ms; w przeciwnym razie jest to w zasadzie to samo, co wywołanie removeObjectAtIndex: dla każdego elementu)(3)
removeObjects:
- 326 ms(4) utwórz nową tablicę z obiektami, które przejdą test - 17 ms
Tak więc tworzenie nowej tablicy jest zdecydowanie najszybsze. Wszystkie pozostałe metody są porównywalne, z wyjątkiem tego, że użycie removeObjectsAtIndexes: będzie gorzej z większą liczbą elementów do usunięcia, ze względu na czas potrzebny na zbudowanie zestawu indeksów.
źródło
Użyj pętli odliczającej indeksy:
lub wykonaj kopię z obiektami, które chcesz zachować.
W szczególności nie używaj
for (id object in array)
pętli aniNSEnumerator
.źródło
W systemie iOS 4+ lub OS X 10.6+ Apple dodał
passingTest
serię interfejsów APINSMutableArray
, takich jak– indexesOfObjectsPassingTest:
. Rozwiązaniem z takim interfejsem API byłoby:źródło
Obecnie można używać odwróconego wyliczania opartego na blokach. Prosty przykładowy kod:
Wynik:
inna opcja z tylko jednym wierszem kodu:
źródło
W bardziej deklaratywny sposób, w zależności od kryteriów pasujących do elementów do usunięcia, możesz użyć:
@Nathan powinien być bardzo wydajny
źródło
Oto prosty i czysty sposób. Lubię duplikować moją tablicę bezpośrednio w wywołaniu szybkiego wyliczania:
W ten sposób wyliczysz kopię usuwanej tablicy, która zawiera te same obiekty. NSArray przechowuje tylko wskaźniki obiektów, więc jest to całkowicie w porządku pod względem pamięci / wydajności.
źródło
for (LineItem *item in self.lineItems.copy)
Dodaj obiekty, które chcesz usunąć, do drugiej tablicy, a po pętli użyj -removeObjectsInArray :.
źródło
powinno to zrobić:
mam nadzieję że to pomoże...
źródło
Dlaczego nie dodasz obiektów do usunięcia do innego NSMutableArray. Po zakończeniu iteracji możesz usunąć zgromadzone obiekty.
źródło
Co powiesz na zamianę elementów, które chcesz usunąć, na n-ty element, n-1 element i tak dalej?
Po zakończeniu zmieniasz rozmiar tablicy na „poprzedni rozmiar - liczba zamian”
źródło
Jeśli wszystkie obiekty w tablicy są unikalne lub chcesz usunąć wszystkie wystąpienia obiektu, gdy zostanie znaleziony, możesz szybko wyliczyć kopię tablicy i użyć [NSMutableArray removeObject:], aby usunąć obiekt z oryginału.
źródło
+arrayWithArray
wykonywania?Powyższa odpowiedź benzado jest tym, co powinieneś zrobić dla preformace. W jednej z moich aplikacji removeObjectsInArray zajęło 1 minutę, samo dodanie do nowej tablicy zajęło 0,023 sekundy.
źródło
Definiuję kategorię, która pozwala mi filtrować za pomocą bloku, jak poniżej:
które można następnie wykorzystać w następujący sposób:
źródło
Lepszą implementacją może być użycie metody kategorii poniżej na NSMutableArray.
Blok predykatów można zaimplementować w celu przetwarzania każdego obiektu w tablicy. Jeśli predykat zwróci wartość true, obiekt zostanie usunięty.
Przykład tablicy dat do usunięcia wszystkich dat, które leżą w przeszłości:
źródło
Iterowanie wstecz było moim ulubionym od lat, ale przez długi czas nigdy nie spotkałem się z przypadkiem, w którym „najgłębszy” (najwyższy wynik) obiekt został usunięty jako pierwszy. Na chwilę przed przejściem wskaźnika do następnego indeksu nie ma nic i ulega awarii.
Sposób Benzado jest najbliższy temu, co robię teraz, ale nigdy nie zdawałem sobie sprawy, że po każdym usunięciu nastąpi przetasowanie stosu.
pod Xcode 6 to działa
źródło