Sortowanie w PHP: Analiza i optymalizacja

Wstęp

W dzisiejszym artykule omówimy sortowanie danych w PHP, przyglądając się różnym podejściom oraz optymalizacjom, które można zastosować. Zacznę od analizy przykładowego kodu, który stosowałem na początku mojej drogi Developera PHP, a następnie przejdę do omówienia dwóch zoptymalizowanych wersji tego kodu: jednej dla starszych wersji PHP i drugiej dla PHP 8 i nowszych.

Analiza kodu wyjściowego

Zacznijmy od kody wyjściowego:

<?php
setlocale(LC_ALL, 'pl_PL');

$data = [
    ['name' => 'Ąan', 'surname' => 'Kowalski', 'age' => 30],
    ['name' => 'Andrzej', 'surname' => 'Nowak', 'age' => 23],
    ['name' => 'Ąenon', 'surname' => 'Sikora', 'age' => 34],
    ['name' => 'ąndrzej', 'surname' => 'Nowak', 'age' => 23],
    ['name' => 'Aenon', 'surname' => 'sruba', 'age' => 45],
    ['name' => 'Ącki', 'surname' => 'śruba', 'age' => 34],
    ['name' => 'andrzej', 'surname' => 'Nowak', 'age' => 23],
    ['name' => 'jan', 'surname' => 'kowalski', 'age' => 34],
    ['name' => 'Ąndrzej', 'surname' => 'Nowak', 'age' => 46],
    ['name' => 'Ęcki', 'surname' => 'Zima', 'age' => 65],
    ['name' => 'Ącki', 'surname' => 'Sato', 'age' => 34],
];

foreach($data as $key => $rec) {
    echo $key."\t=>\t".$rec['name'].', '.$rec['surname'].': '.$rec['age']."\n";
}

echo "\n\n";

sort_field($data, 'name', false);

foreach($data as $key => $rec) {
    echo $key."\t=>\t".$rec['name'].', '.$rec['surname'].': '.$rec['age']."\n";
}

function sort_field(&$data, $field, $reverse)
{
    $replaceCharsArray = [
        'ą' => 'azzz',
        'ć' => 'czzz',
        'ę' => 'ezzz',
        'ł' => 'lzzz',
        'ń' => 'nzzz',
        'ó' => 'ozzz',
        'ś' => 'szzz',
        'ż' => 'zxzzz',
        'ź' => 'zyzzz',
        'Ą' => 'Azzz',
        'Ć' => 'Czzz',
        'Ę' => 'Ezzz',
        'Ł' => 'Lzzz',
        'Ń' => 'Nzzz',
        'Ó' => 'Ozzz',
        'Ś' => 'Szzz',
        'Ż' => 'Zxzzz',
        'Ź' => 'Zyzzz'
    ];

    foreach($data as $key => $rec) {
        $data[$key]['name'] = strtr($rec['name'], $replaceCharsArray);
        $data[$key]['surname'] = strtr($rec['surname'], $replaceCharsArray);
    }

    usort($data, function ($a, $b) use ($field, $reverse) {
        $aa = iconv('UTF-8', 'ASCII//TRANSLIT', $a[$field]);
        $bb = iconv('UTF-8', 'ASCII//TRANSLIT', $b[$field]);
        $compare = strcasecmp($aa, $bb);
        return $reverse ? -$compare : $compare;
    });

    foreach($data as $key => $rec) {
        $data[$key]['name'] = strtr($rec['name'], array_flip($replaceCharsArray));
        $data[$key]['surname'] = strtr($rec['surname'], array_flip($replaceCharsArray));
    }
}

W podanym kodzie mamy do czynienia z tablicą asocjacyjną $data, która przechowuje dane o osobach, takie jak imię, nazwisko i wiek. Tablice asocjacyjne, reprezentujące poszczególne osoby, zawierają klucze 'name', 'surname' i 'age'. Wartości imion i nazwisk mogą zawierać polskie znaki diakrytyczne, co może wpłynąć na proces sortowania danych.

W pierwszej części kodu, za pomocą pętli foreach, wyświetlane są wszystkie rekordy z tablicy $data wraz z indeksami.

Wstępna funkcja sortująca

Następnie, funkcja sort_field() jest wywoływana, aby posortować tablicę $data według podanego kryterium sortowania (w tym przypadku 'name’) oraz zgodnie z określonym kierunkiem sortowania (rosnąco lub malejąco). W funkcji sort_field() dzieje się kilka rzeczy:

  1. Zdefiniowany jest tablica $replaceCharsArray, która służy do zamiany polskich znaków na ciągi ASCII. Dzięki temu, sortowanie będzie działało poprawnie dla polskich znaków.
  2. Przed sortowaniem, polskie znaki w imionach i nazwiskach są zamieniane na odpowiednie ciągi ASCII za pomocą funkcji strtr().
  3. Wykorzystując funkcję usort(), tablica $data jest sortowana według wartości kolumny wskazanej przez argument $field. Do porównywania wartości używamy funkcji anonimowej, która korzysta z funkcji iconv() do konwersji wartości na odpowiednie ciągi ASCII, a następnie porównuje je za pomocą funkcji strcasecmp(). Kierunek sortowania zależy od wartości argumentu $reverse. Jeśli $reverse ma wartość true, sortowanie będzie odbywać się w porządku malejącym, w przeciwnym razie sortowanie będzie rosnące.
  4. Po posortowaniu tablicy, zamiana ciągów ASCII z powrotem na polskie znaki diakrytyczne zostaje przeprowadzona za pomocą funkcji strtr() i przekształconej tablicy $replaceCharsArray.

Na końcu, pętla foreach wyświetla posortowane dane z tablicy $data.

Podsumowując, ten kod składa się z trzech głównych części:

  1. Wyświetlenie danych z tablicy $data przed sortowaniem.
  2. Sortowanie danych w tablicy $data za pomocą funkcji sort_field(), która uwzględnia polskie znaki diakrytyczne poprzez zamianę ich na ciągi ASCII, sortowanie za pomocą funkcji usort() i anonimowej funkcji porównującej, a następnie przywrócenie polskich znaków diakrytycznych.
  3. Wyświetlenie danych z tablicy $data po sortowaniu.

Kod pokazuje, jak można posortować tablicę z danymi zawierającymi polskie znaki diakrytyczne, biorąc pod uwagę specyfikę tych znaków w trakcie sortowania. Dzięki temu, uzyskujemy poprawne wyniki sortowania dla różnych przypadków danych wejściowych.

Refaktoryzacja kodu

Kod ma teraz taką postać:

<?php
setlocale(LC_ALL, 'pl_PL');

$data = [
    ['name' => 'Ąan', 'surname' => 'Kowalski', 'age' => 30],
    // ...
    ['name' => 'Ącki', 'surname' => 'Sato', 'age' => 34],
];

display_data($data);
echo "\n\n";

sort_field($data, 'name');
display_data($data);

function display_data($data) {
    foreach($data as $key => $rec) {
        echo $key."\t=>\t".$rec['name'].', '.$rec['surname'].': '.$rec['age']."\n";
    }
}

function sort_field(&$data, $field, $reverse = false) {
    usort($data, function ($a, $b) use ($field, $reverse) {
        $collator = new Collator('pl_PL');
        $compare = $collator->compare($a[$field], $b[$field]);
        return $reverse ? -$compare : $compare;
    });
}

Podczas pierwszej refaktoryzacji kodu skorzystaliśmy z obiektowej funkcjonalności języka PHP, a konkretnie z klasy Collator. Dzięki niej możemy sortować tekst z uwzględnieniem lokalizacji, co zapewnia poprawne traktowanie polskich znaków diakrytycznych podczas sortowania. Dzięki temu nie musimy już definiować niestandardowych funkcji do zamiany polskich znaków diakrytycznych na ciągi ASCII.

W nowej wersji kodu, funkcja sort_field używa funkcji usort do posortowania tablicy $data na podstawie porównania elementów. Funkcja usort wymaga podania dwóch argumentów: posortowanej tablicy oraz funkcji porównującej, która określi kolejność elementów w tablicy.

Funkcja porównująca przyjmuje dwa argumenty, $a i $b, które są elementami tablicy $data. W przypadku tej funkcji, używamy słowa kluczowego use do przekazania wartości $field i $reverse do funkcji porównującej. W funkcji porównującej tworzymy obiekt $collator, który jest instancją klasy Collator z rozszerzenia intl. Tworzymy obiekt klasy Collator z użyciem polskiego locale ('pl_PL'), aby porównywać łańcuchy znaków zgodnie z polskimi zasadami sortowania.

Następnie wywołujemy metodę compare na obiekcie $collator, porównując wartości $a[$field] i $b[$field]. Metoda compare zwraca wartość ujemną, gdy pierwszy argument jest mniejszy niż drugi, wartość dodatnią, gdy pierwszy argument jest większy niż drugi, oraz zero, gdy oba argumenty są równe.

Jeśli wartość $reverse jest ustawiona na true, funkcja porównująca zwraca wartość przeciwną do wyniku porównania (czyli -$compare). W przeciwnym razie zwraca wynik porównania ($compare). Dzięki temu tablica $data zostaje posortowana według wartości klucza $field w rosnącej lub malejącej kolejności, w zależności od wartości $reverse.

Wykorzystywaliśmy funkcję display_data() do wyświetlania danych przed i po sortowaniu, tak jak w kodzie wyjściowym. Niestety, używaliśmy dwukrotnej pętli foreach. Wersja refaktoryzowana zamiast tego wykorzystuje tę samą funkcję do wyświetlenia danych przed i po sortowaniu, co prowadzi do krótszego i bardziej czytelnego kodu.

Dostosowanie kodu do PHP wersji 8

Po wprowadzeniu PHP w wersji 8 możemy kod funkcji skrócić do takiej postaci:

function sort_field(&$data, $field, $reverse = false): void {
    $collator = new Collator('pl_PL');
    usort($data, fn($a, $b) => $reverse ? -$collator->compare($a[$field], $b[$field]) : $collator->compare($a[$field], $b[$field]));
}

Wprowadzone zmiany:

  1. Zamiast tworzyć obiekt Collator wewnątrz funkcji porównującej, tworzymy go przed wywołaniem funkcji usort. Dzięki temu tworzymy tylko jeden obiekt Collator, a nie jeden dla każdego porównania.
  2. Zamieniliśmy anonimową funkcję na arrow function (fn($a, $b) => …), która pozwala na krótszy i bardziej czytelny zapis.
  3. Dodaliśmy typ zwracany : void, który wskazuje, że funkcja sort_field nie zwraca żadnej wartości. Jest to dobra praktyka, aby wskazywać oczekiwane typy zwracane przez funkcje, ponieważ zwiększa to czytelność i może pomóc w wykrywaniu błędów.

W wyniku tych zmian, funkcja sort_field jest teraz krótsza i bardziej wydajna, gdyż tworzy tylko jeden obiekt Collator zamiast wielu. Ponadto, dzięki użyciu arrow function, kod jest bardziej zwięzły i czytelny.

Podsumowanie

W artykule omówiliśmy sortowanie tablic asocjacyjnych w PHP. Skupiliśmy się na optymalizacji kodu dla starszych wersji PHP i PHP 8, wykorzystując przykłady kodu. Pokazaliśmy, jak zoptymalizować obsługę polskich znaków oraz zmniejszyć liczbę obiektów klasy Collator. Przedstawiliśmy dwie wersje optymalizacji, jedną dla starszych wersji PHP i drugą dla PHP 8, wykorzystując arrow functions. Optymalizacje te są szczególnie istotne dla dużych zbiorów danych. Zrozumienie dostępnych funkcji i narzędzi w PHP oraz śledzenie nowości w kolejnych wersjach języka pozwoli Ci pisać bardziej wydajny i czytelny kod. Niezależnie od wersji PHP, istnieją techniki i podejścia, które możesz zastosować, aby ulepszyć swój kod. Kontynuuj naukę i eksperymentowanie, aby stać się jeszcze lepszym programistą PHP.

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *


Scroll to Top