2012-12-29 23 views
5

Jeśli dobrze zrozumiałem (i proszę mnie poprawić, jeśli się mylę), lista jest zaimplementowana przez tablicę w .NET, co oznacza, że ​​każde usunięcie pozycji na liście spowoduje ponowne przydzielenie całej listy (która w obrót oznacza O(n)).Jak skutecznie usunąć z listy <T> (C#)?

Rozwijam grę, w grze mam wiele pocisków lecących w powietrzu w dowolnym momencie, powiedzmy 100 pocisków, każda klatka poruszam je o kilka pikseli i sprawdzam kolizję z obiektami w grze, Muszę usunąć z listy każdy trafiony punkt.

Więc zebrać zderzył kulę w innej listy tymczasowego, a następnie wykonaj następujące czynności:

foreach (Bullet bullet in bulletsForDeletion) 
    mBullets.Remove(bullet); 

Ponieważ pętla jest O(n) i usuń to O(n), spędzam O(n^2) czas usunięcia.

Czy istnieje lepszy sposób na usunięcie go lub bardziej odpowiedni do użycia?

+2

Nie mów przepraszam. Wszyscy jesteśmy tutaj, aby się uczyć. –

+1

Czy jesteś pewien, że masz rzeczywisty problem, czy też optymalizujesz go przedwcześnie? – Oded

+0

Nie mam rzeczywistego problemu, działa z prędkością 60 klatek na sekundę, po prostu "czułem", jakbym pisał coś, co jest nie tak, ponieważ taka operacja nie powinna być O (n^2). – OopsUser

Odpowiedz

4

Utwórz nową listę:

var newList = `oldList.Except(deleteItems).ToList()`. 

próbować używać idiomów funkcjonalne, jeśli możliwe. Nie modyfikuj istniejących struktur danych, twórz nowe.

Algorytm ten to O (N) dzięki hashu.

+0

Dlaczego jest O (n)? kiedy kopiuje zmienną na inną listę, musi sprawdzić każdy element, jeśli istnieje na liście "deleteItems", wygląda O (n^2) – OopsUser

+0

@OopsUser 'Z wyjątkiem' buduje wewnętrznie tablicę asocjacyjną, więc istnieje sprawdzenie to O (1) na element w 'oldList' .To powoduje' O (oldList.Count + deleteItems.Count) ~ O (N) ' – usr

+0

+1. @OopsUser, metoda rozszerzenia "Except" (http://msdn.microsoft.com/en-us/library/system.linq.enumerable.except.aspx) odczytuje każdy element z wyliczenia źródłowego i wypisuje te, które są nie w 'deleteItems'. Wewnętrznie buduje hash 'deleteItems' i testuje obecność każdego wejścia przeciw temu hashowi. Tylko elementy, których nie ma w haszdzie, są przekazywane do wyjścia. To jest O (N), jednak spowoduje to przydzielenie nowych skrótów i nowych tablic (utworzonych przez 'ToList'). Możliwe, że nie musisz martwić się o obciążenie pamięci, ale powinieneś wiedzieć, że tam jest. –

2

Nie możesz po prostu zmienić z List (odpowiednik Javy ArrayList, aby to zaznaczyć) na LinkedList? LinkedList pobiera O (1), aby usunąć konkretny element, ale O (n) do usunięcia według indeksu

1

Sposób, w jaki lista jest wdrażana wewnętrznie, nie jest czymś, o czym powinieneś się zastanowić. Powinieneś wchodzić w interakcję z listą na tym poziomie abstrakcji.

Jeśli masz problemy z wydajnością i wskazałeś je na liście, czas sprawdzić, jak jest zaimplementowany.

Jeśli chodzi o usuwanie pozycji z listy - można użyć mBullets.RemoveAll(predicate), gdzie predicate jest wyrażeniem identyfikującym przedmioty, które uległy kolizji.

6

Zestawy i połączone listy mają stały czas usuwania. Czy możesz użyć którejś z tych struktur danych?

Nie można uniknąć kosztu usunięcia (O) z List<T>. Będziesz musiał użyć innej struktury danych, jeśli jest to dla ciebie problem. Może sprawić, że kod, który wylicza pociski, również sprawi, że poczujesz się lepiej.

ISet<T> ma ładne metody obliczania różnic i przecięć między zestawami obiektów.

Tracisz porządek, używając zestawów, ale biorąc pod uwagę, że bierzesz kule, domyślam się, że to nie jest problem. Nadal można go wyliczyć w stałym czasie.


W twoim przypadku, można napisać:

mBullets.ExceptWith(bulletsForDeletion); 
+0

Jak "set" został wywołany w .net? Nie widzę generycznego zwanego zestawem. Problem z połączoną listą (i zestawami) polega na tym, że dane nie znajdują się w tym samym miejscu w pamięci, więc podczas odczytu z listy mam zaletę buforowania, ale gdy używam zestawów/połączonych list, stracić tę korzyść. Chociaż muszę usunąć pozycje z listy około raz na sekundę, muszę przeczytać z listy 100 razy więcej (sprawdzanie kolizji itp.) – OopsUser

+0

@OopsUser, szukasz 'ISet ', które możesz [ przeczytaj tutaj] (http://msdn.microsoft.com/en-us/library/dd412081.aspx). –

+0

@OopsUser, również byłoby przydatne, aby zobaczyć, jak obliczyć 'bulletsForDeletion'. Być może istnieje sposób na uczynienie tego bardziej optymalnym. –

1

jest szybszy niż Remove(T item), ponieważ później korzysta z pierwszego wewnątrz, wykonując odbicie, jest to kod wewnątrz każdej funkcji.

Ponadto, Remove(T) ma funkcję IndexOf, która ma w sobie drugą pętlę do oceny indeksu każdego elementu.

public bool Remove(T item) 
{ 
    int index = this.IndexOf(item); 
    if (index < 0) 
    return false; 
    this.RemoveAt(index); 
    return true; 
} 


public void RemoveAt(int index) 
{ 
    if ((uint) index >= (uint) this._size) 
    ThrowHelper.ThrowArgumentOutOfRangeException(); 
    --this._size; 
    if (index < this._size) 
    Array.Copy((Array) this._items, index + 1, (Array) this._items, index, this._size - index); 
    this._items[this._size] = default (T); 
    ++this._version; 
} 

chciałbym zrobić pętlę tak:

for (int i=MyList.Count-1; i>=0; i--) 
{ 
    // add some code here 
    if (needtodelete == true) 
    MyList.RemoveAt(i); 
} 
+0

Twój ostatni blok kodu jest nieefektywną wersją 'MyList.Clear()'. Czy chcesz napisać coś jeszcze? –

+0

@DrewNoakes, tak, zakładam, że pytający użytkownik doda kilka warunków do usunięcia niektórych elementów, nie będzie faktycznie usuwał wszystkich elementów, edytował mój post. – sharp12345

0

w duchu idiomu funkcjonalnej sugerowanej przez „usr”, można rozważyć nie usuwając z listy w ogóle. Zamiast tego rutyna aktualizacji powinna mieć listę i zwrócić listę. Zwrócona lista zawiera tylko obiekty "wciąż żywe". Następnie możesz zamienić listy na końcu pętli gry (lub natychmiast, jeśli to konieczne). Zrobiłem to sam.

Powiązane problemy