Jak wykonać bardziej skomplikowane sortowanie tablicy.

Opis problemu

W realizowanym ostatnio przeze mnie projekcie musiałam wykonać sortowanie pewnej tablicy złożonej tablicy. Elementami tablicy były tablice, a sortowanie miało się odbywać po 2 elemencie. Nie byłoby w tym nic dziwnego gdyby nie kryteria sortowania. Drugi element każdej z tablic wchodzącej w skład głównej tablicy wyglądały mniej więcej tak:

  • Kasia07/123
  • Ania97/4376
  • Wojtek07/1298
  • Kasia07/3098
  • Basia06/109
  • Tomek02/1109
  • Andrzej99/231
  • Andrzej99/909
  • Ania07/5432

czyli ImięRok/Numer.
Gdyby chodziło o zwykłe sortowanie alfabetyczne sprawa nie warta by była nawet opisania. Sęk w tym, że sortowanie miało odbywać się wedle następujących kryteriów:
Najpierw względem roku (malejąco do dołu), następnie w obrębie jednego roku względem Imienia alfabetycznie a na końcu względem numeru rosnąco w dół. Jednym słowem lista po posortowaniu ma wyglądać tak:

  • Ania07/5432
  • Kasia07/123
  • Kasia07/3098
  • Wojtek07/1298
  • Basia06/109
  • Tomek02/1109
  • Andrzej99/231
  • Andrzej99/909
  • Ania97/4376

Prawie gotowa funkcja standardowa

Pierwszym co przyszło mi do głowy było użycie funkcji usort(). Funkcja ta przyjmuje dwa parametry. Pierwszym z nich jest tablica do posortowania. Drugim jest nazwa funkcji, która będzie określała kryteria sortowania. Tę funkcję musimy sobie sami zdefiniować i musi ona spełniać kilka warunków. Po pierwsze musi przyjmować dwa parametry (będą to dwa kolejne pola tablicy, które będą do funkcji podawane prze funkcję usort(), więc nie musimy się martwić tym mechanizmem). Ponadto funkcja musi zwracać wartość dodatnią jeśli badane pola są już w odpowiedniej kolejności i ujemną jeśli są w kolejności odwrotnej niż chcemy.

Wywołanie funkcji sortującej

A teraz konkrety. Załóżmy, że nasza tablica będzie nazywać się $dane. Zawiera ona tablice asocjacyjne, w których jest indeks o nazwie ‚osoba’ zawierający pole, względem którego będziemy sortować. Ponadto funkcja sortująca będzie nazywać się Rok_Imie_Numer(). W takim razie funkcję usort() wywołamy w następujący sposób:

usort($dane,'Rok_Imie_Numer');

Jednak póki nie zdefiniujemy funkcji Rok_Imie_Numer() sortowanie nie zadziała.

Definiowanie funkcji sortującej

Pora więc zająć się definicją funkcji sortującej. Najpierw szkielet:

function Rok_Imie_Numer($a,$b){
 $kolejnosc = 1;
 return $kolejnosc;
}

Oczywiście taka funkcja nic nie zrobi, bo najnormalniej w świecie zawsze zwraca wartość dodatnią więc żadne pole tablicy nie zostanie przesunięte. Teraz trzeba wewnątrz tej funkcji wyłuskać dane względem którym ba się odbyć sortowanie. Założyliśmy, że sortowanie ma się odbyć według pola indeksowanego kluczem ‚osoba’ . Pamiętając, że parametrami funkcji są dwa porównywane pola tablicy możemy sobie łatwo wyłuskać potrzebne dane:

function Rok_Imie_Numer($a,$b){
  $kolejnosc = 1;
  $a = $a['osoba'];
  $b = $b['osoba'];
  return $kolejnosc;
}

Teraz już mamy dane względem których odbędzie się sortowanie, ale są one w postaci dość niewygodnego ciągu znaków. Najlepiej byłoby go rozbić.

Przygotowanie danych do porównania

Żeby wygodniej analizować dane i precyzyjnie ustalić, czy są w należytej kolejności zdefiniujemy sobie dodatkową funkcję, która niewygodny string rozbije nam na wygodną tablicę.

function rozbij($sOsoba){
  	$tablica = explode('/',$sOsoba);
  	$rekord['iNumer'] = $tablica[1];
  	$rekord['iRok'] = substr($tablica[0], -2);
  	$rekord['sImie'] = substr($tablica[0],0, -2);
  	if($rekord['iRok'] > 70) $rekord['iRok'] = '19'.$rekord['iRok'];
  	else $rekord['iRok'] = '20'.$rekord['iRok'];
  	return $rekord;
	}

Najpierw rozbijamy string względem znaku ‚/’ funkcją explode() . Drugie pole tak wygenerowanej tablicy to numer. Przypiszemy więc go do pola o indeksie ‚iNumer’ tablicy $rekord. Pierwsze pole zmiennej $tablica trzeba jeszcze rozbić. Dwa ostatnie znaki symbolizują rok pozostałe to imię. Podział można wygodnie zrealizować za pomocą funkcji substr() , która z odpowiednimi parametrami najpierw pobierze tylko dwa ostatnie znaki ( substr($tablica[0], -2) ) a potem wszystko oprócz dwóch ostatnich znaków ( substr($tablica[0],0, -2) ). Uzyskane wartości przypisujemy odpowiednio do zmiennych $rekord[‚iRok’] i $rekord[‚sImię’]. Na koniec jeszcze robimy porządek ze zmienną $rekord[‚iRok’]. Jeśli bowiem zostawimy ją w postaci dwucyfrowej może się okazać, ze rok 1997 jest większy niż 2008, a tego byśmy nie chcieli. Zakładamy, że wartości powyżej 70 odnoszą się to dwudziestego wieku, więc dopisujemy na początku 19. W pozostałych wypadkach dopisujemy 20. Tak spreparowaną tablicę zawierającą trzy pola zwracamy jako wynik funkcji. Wykorzystać tę funkcję w naszej funkcji sortującej:

function Rok_Imie_Numer($a,$b){
  $kolejnosc = 1;
  $a = rozbij($a['osoba']);
  $b = rozbij($b['osoba']);
  return $kolejnosc;
}

Kryteria sortowania

Pora ustalić kryteria sortowania. Pierwszym kryterium miał być rok. Funkcja ma więc zwracać wartość dodatnia lub ujemną w zależności od tego jakie wartość przyjmuje ten parametr w obu polach sortowanej tablicy. W sytuacji gdy są jednakowe funkcja będzie przechodzić do kolejnego kryterium sortowania.

function Rok_Imie_Numer($a,$b){
 $kolejnosc = 1;
 $a = rozbij($a['osoba']);
 $b = rozbij($b['osoba']);
 if($a['iRok'] > $b['iRok']){
  $kolejnosc = -1;
 }elseif($a['iRok'] < $b&#91;'iRok'&#93;){
  $kolejnosc = 1;
 }else{
 }
 return $kolejnosc;
}
&#91;/code&#93;

Postępując podobnie z pozostałymi kryteriami sortowania otrzymujemy następujący kod:
&#91;code='php'&#93;
function Rok_Imie_Numer($a,$b){
 $kolejnosc = 1;
 $a = rozbij($a&#91;'osoba'&#93;);
 $b = rozbij($b&#91;'osoba'&#93;);
 if($a&#91;'iRok'&#93; > $b['iRok']){
  $kolejnosc = -1;
 }elseif($a['iRok'] < $b&#91;'iRok'&#93;){
  $kolejnosc = 1;
 }else{
  if($a&#91;'sImie'&#93; > $b['sImie']){
   $kolejnosc = 1;
  }elseif($a['sImie'] < $b&#91;'sImie'&#93;){
   $kolejnosc = -1;
  }else{
   if($a&#91;'iNumer'&#93; >= $b['iNumer']){
    $kolejnosc = 1;
   }else{
    $kolejnosc = -1;
   }
  }
 }
 return $kolejnosc;
}

I to już właściwie wszystko. Uruchamiając ten skrypt możesz zobaczyć, że to naprawdę działa.

6 komentarzy do wpisu „Jak wykonać bardziej skomplikowane sortowanie tablicy.”

  1. Widzę, ze od słowa pisanego nie stronisz i sklecenie dłuższego niż trzy wyrazy zdanie problemem dla Ciebie nie jest :) Może na php.pl czasami coś napiszesz?
    Ogolnie notka całkiem zgrabnie napisana – na temat i bez udziwnień – podoba mi się.

  2. na forum, to jesteś zauważalna oczywiście, co mnie bardzo cieszy. Ale jakbyś znalazła czas na napisanie czegoś na wortal, byłbym jeszcze bardziej ukontentowany :)

  3. Artykuł przyznaje bardzo przydatny dla tych którzy nie wiedzą jak się zabrać za coś o czym nie mają pojęcia (a niestety tak jest coraz częściej). Jednak podejście do optymalizacji mnie zadziwiło. Mianowicie brak jakiejkolwiek. Rozbicie stringu nie było by szybsze i lepsze za pomocą preg_match ? (Zrobione w jednej linii) Reszcie się tak bardzo nie przyglądałem ale patrząc na te ify i elsy sądzę, że też by było do czego się przyczepić ;) Mimo wszystko gratuluje. Prowadzenie takiego bloga to wyzwanie, w dodatku jak ktoś już wcześniej napisał pióro lekkim Ci dzięki czemu się to znacznie przyjemniej czyta.

  4. To prawda, operacje na stringach można by zoptymalizować. Ifów i elsów w głównej procedurze sortującej nie da się raczej uniknąć. Szczególnie, że jest to wielostopniowe sortowanie.

    W każdym razie dziękuję za sugestię. Postaram się w przyszłości zwracać więcej uwagi na optymalizację.

Dodaj komentarz

%d bloggers like this: