Dwuwymiarowe tablice dynamiczne

tech • 668 słów • 4 minuty czytania

Potrzebuje jakiejś ciekawej, prostej i optymalnej implementacji tablicy dwuwymiarowej do przechowywania prostych elementów lub wskaźników. Najprostszym rozwiązaniem byłoby wykorzystanie kontenerów z STL:

std::vector<std::vector<Element*> > array

Szybko i prosto, tylko używać.

Ale pojawia się problem, bo chciałbym, aby pojedyncza linia zachowywała się jak czysta tablicą w stylu C (wymagania API).

Także można by bez problemu zastosować vectora w vectorze, ale… ale o tym później.

Moja obecna i pierwsza implementacja takiego kontenera oparta jest na vektorze, który zawiera wskaźnik na alokowana dynamicznie tablice wskaźników do elementów. Wstawianie elementów do nowego wiersza wyglądałoby tak:

// dodanie elementu do nowego wiersza
void push_back_row(Element* item) {
	Element** row = new Element*[m_RowsSize];
	row[0] = item;
	m_lines.push_back(row);
}

Jako tako działa to dobrze. Zrobiłem sobie nawet prosta strukturę typu index, do łatwiejszego operowania na indeksach, a także poziome wstawianie elementów wierszami.

Tylko jedne problem, jakby popatrzeć, standardowa implementacja operatora new nie jest wydajna i optymalna dla alokowania małych bloków pamięci (< 64 bajty). A jak wspomniałem, nasza tablica zawierałaby tylko kilka wskaźników, przyjmijmy, że defaultowo 4 (4 elementy na wiersz) co daje 16 bajtów. Alokowanie 16 bajtów przy hurtowym wstawianiu elementów i wiersza będzie bardzo mało wydajne.

Tak, wiec przechodzimy do kolejnego rozwiązania.

Alokujemy sobie 400 elementową jednowymiarową tablicę dla 100 wierszy po 4 elementy (wskaźniki), co daje nam 400 bajtów. Tworzymy sobie vektora na wskaźniki do wskaźników elementów (Element**) i rezerwujemy na 100 elementów. Każda wiersz, czyli element vectora będzie wskazywał na odpowiednią pozycję w zaalokowanej jednowymiarowej tablicy (co 4 element). Oczywiście wskaźniki te będą dodawane automatycznie przy dodawaniu linii, a przy przekroczeniu 100 linii możemy sobie przealokować tablice, albo dorzucić kolejną i na niej operować.

// dodanie elementu do nowego wiersza
void push_back_row(Element* item) {
	Element** row = m_array + 4 * m_rows.size();
	row[0] = item;
	m_lines.push_back(row);
}

Przydałby się rysunek do zobrazowania tego, ale moje predyspozycje graficzne są nikłe :P

Póki, co jest to najlepsze rozwiązanie. W porównaniu do STL-owej wersji (vector w vectorze) przy 100 liniach po 4 elementy zaoszczędziliśmy 1200 bajtów (100 x sizeof(vector), w implementacji STL-a dołączanej w g++ 3.4.x rozmiar vectora wynosił 12 bajtów), czyli prawie 300 wskaźników, co odpowiada 75 nowym wierszom :P Nie rekompensuje to nawet tego, że w nie każdym wierszu musimy używać wszystkich 4 pozycji.

W sumie każdy powie, co to ~1.2KB przy dzisiejszych GB pamięciach :P Ale jakby każdy tak uważał, to czy niektóre systemy, czy programy działałyby tak jak działają, gdyby potrzebowały trochę więcej pamięci o 1 rząd więcej? Pewnie nie byłoby tak wesoło i niektórym nie podobałoby się to ;)

No tak, ale jak wielokrotnie wspominałem, mam małe zboczenie do optymalności i wydajności, ale lepsze to niż olanie lub założenie, że mamy nieskończoną ilość pamięci, czy zasobów :P

Wracając do tematu, wykorzystam rozwiązanie z jednowymiarową tablicą na dane, chyba ze znajdę lub wymyślę cos lepszego. Szkoda tylko ze przy operatorze [] na wierszu nie bedę miał sprawdzania zakresu, no chyba, że dla wersji debug opakuję ten wskaźnik do wiersza w jakąś prostą klasę z przeciążonym operatorem [] z assertem w środku ;) Do tego wypadałoby jeszcze napisać jakiś prosty iterator, który nie wiele musi mieć wspólnego z STL-owymi.

15/04/2008 @ 01:46

Zapomnijcie o powyższych rozwiązaniach, wymyśliłem coś lepszego. Ciekawe pomysły wpadają do głowy w najmniej oczekiwanych momentach ;)

Po co się babrać różnymi tablicami jednowymiarowymi i problemami? W moim przypadku tablicy dwuwymiarowej najłatwiej będzie użyć zwykłej tablicy lub wektora i jedynie troszkę rozszerzyć jego szablon i ewentualnie iteratora. Nic więcej. Każde n-elementów tablicy będzie traktowane jako jeden wiersz. Jest to typowe i powszechne rozwiązanie problemu z prostymi tablicami dwuwymiarowymi. Dziwię się, że dopiero teraz na to wpadłem.

Wtedy można będzie używać różnych algorytmów STL-owych na tym kontenerze, kontenerze dzięki małym modyfikacjom, na elementach różnych kolumn etc, wierszowo na kolejnych elementach będzie zapewniał defaultowy iterator vectora.

Dla elementów o małych rozmiarach będzie to idealna tablica wielowymiarowa. Dla nieco większych nie będzie się zbytnio nadawać, bo wymagałoby to dużej jednolitej przestrzeni pamięciowej, aby uniknąć zabaw z różnymi blokami alokowanej pamięci miedzy wierszami.

Za kilka dni się tym pobawię i zobaczymy co wyjdzie ;)

Komentarze (0)

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/