Minimalna i maksymalna wartość tablicy w JS

Znalezienie minimalnej lub maksymalnej wartości w tablicy nie jest trudnym zadaniem, ale nie w każdym języku programowania dostępnej jest dzięki wbudowanej funkcji. Przykładowo PHP oferuje funkcje min() oraz max(), które jako argument przyjmują tablicę bądź kolejne argumenty do znalezienia skrajnych wartości wśród nich. Niestety JS nie oferuje takich funkcji dla obiektu Array. Istnieje proste rozwiązanie.

Zacznijmy od podstawowego przykładu jakim będzie poszukiwanie maksymalnej wartości tablicy zawierającej liczby całkowite, w tym także liczby ujemnie. Tablica nie będzie posortowana.


var numbers = [5, 2, -3, 4, -1, 8, 0, -5, 11, 7];

Na pierwszy rzut oka widzimy, która wartość jest najmniejsza i największa. Minimum to -5, a maksimum 11. Jednak naszym celem jest napisanie algorytmu, który znajdzie wspomniane liczby w każdym przypadku, również bardziej skomplikowanych.

Iterowanie całej tablicy

Generalnie przeszukanie całej tablicy i porównanie jej wartości jest wymagane do znalezienia tej skrajnej. Zaczynamy od pierwszego elementu, porównujemy z inną wartością, aż sprawdzimy wszystkie i znajdziemy ten poszukiwany. Na początek znajdźmy minimum.

var min = numbers[0];
for (var i = 0; i < numbers.length; i++) {
    if (numbers[i] < min) {
        min = numbers[i];
    }
}
console.log(min); // -5

Zakładamy, że minimalna wartość to pierwszy element tablicy, co może być prawdziwe, ale nie musi. To tylko nasza lokalna wartość potrzebna do porównania. Dalej w pętli for dokonujemy porównania wartości tablicy ze zmienną min. Zasadniczo rodzaj pętli nie ma większego znaczenia, gdyż koniecznej jest iterowanie całej tablicy. W środku pętli sprawdzamy czy aktualny element tablicy jest mniejszy od naszego lokalnego minimum? Jeśli tak to zmieniamy wartość zmiennej min, ponieważ znaleźliśmy mniejszą wartość. W przeciwnym razie wartość jest większa, a szukamy przecież najmniejszej. Powtarzamy krok do momentu sprawdzenia całej tablicy. Nasze minimum to liczba -5.

Teraz pora na maksimum. Kod wymaga jedynie drobnych poprawek.

var max = numbers[0];
for (var i = 0; i < numbers.length; i++) {
    if (numbers[i] > max) {
        max = numbers[i];
    }
}
console.log(max); // 11

Ponownie ustawiamy zmienną, która będzie wykorzystywana przy porównywaniu elementów. Dla większej przejrzystości niech to będzie zmienna max, skoro szukamy maksimum. Znowu porównujemy kolejne elementy i w przypadku znalezienie większego, przypisujemy jego wartość do zmiennej max. W ten sposób znajdujemy wartość maksymalną, czyli 11.

Przydatna funkcja reduce()

Analogiczny algorytm możemy zapisać przy pomocy funkcji reduce(), która przetwarza całość tablicy do pojedynczej wartości. W naszym przypadku wcale nie musi to jedna liczba. Niech będzie nowa tablica z wartością minimalną i maksymalną.

var minmax = numbers.reduce(function(acc, curr) {
    acc[0] = curr < acc[0] ? curr : acc[0];
    acc[1] = curr > acc[1] ? curr : acc[1];
    return acc;
}, [numbers[0], numbers[0]]);
console.log(minmax); // [-5, 11]

W tym przypadku połączyliśmy poprzednie dwa algorytmy w funkcji reduce(), która ma dostęp do każdego poprzedniego (zmienna acc) i aktualnego elementu (zmienna curr) tablicy w czasie iteracji. Wartości początkowe zmiennej acc ustawiamy jako drugi opcjonalny argument wywołania funkcji reduce(). W naszym wypadku jest to nowa tablica z dwoma elementami, które są pierwszymi z tablicy.

Przetwarzanie tablicy ponownie sprowadza się do porównania argumentów acc i curr w taki sposób, by znaleźć minimum i maksimum. Jeśli bieżący element tablicy jest mniejszy lub większy od dotychczasowym ekstremów, wówczas te są ustawiany nowymi wartościami.

Ten sam kod można zapisać przy pomocy funkcji strzałkowych (ang. arrow functions) oraz użycia funkcji min() i max() obiektu Math.

var minmax = numbers.reduce((acc, curr) => {
    return [
        Math.min(acc[0], curr),
        Math.max(acc[1], curr)
    ];
}, [numbers[0], numbers[0]]);
console.log(minmax); // [-5, 11];

Kod nieco krótszy, a efekt taki sam. Różnica w wykorzystaniu wbudowanych funkcji min() i max() nie zmienia działania, bo w każdej iteracji akumulator (poprzednia wartość) i aktualna wartość tablicy są porównywane i zwracane odpowiednio mniejsza oraz większa.

Sortowanie tablicy

Pewną alternatywą do znalezienia minimum i maksimum w tablicy będzie jej posortowanie. Wtedy elementy skrajne będą na początki i końcu.

numbers.sort(); // [-1, -3, -5, 11, 2, 4, 5, 7, 8]

Należy tylko pamiętać, że domyślne sortowanie wartości tablicy w JS działa jak sortowanie alfabetyczne, zatem liczby nie będą uporządkowane rosnąco jak byśmy tego oczekiwali. Rozwiązanie tego problemu wymaga napisania funkcji sortującej liczby, która zostania przekazana w wywołaniu sort();

function sortNumber(a, b) {
    return a - b;
}

numbers.sort(sortNumber); // [-5, -3, -1, 2, 4, 5, 7, 8, 11]

Teraz sortowanie tablicy uporządkuje wszystkie liczby w porządku rosnącym z matematycznego punku widzenia. Wówczas minimum i maksimum będą na początku i końcu naszej tablicy.

numbers[0]; // -5
numbers[numbers.length-1]; // 11

Wystarczy odwołać się do elementów tablicy o właściwych indeksach, a otrzymamy poszukiwane wartości.

Operator spread

ES6 to nowe możliwości niedostępne wcześniej do wykorzystania. Operator spread umożliwia rozwinięcie wyrażenia w miejscu gdzie potrzeba wielu argumentów. Jak to działa?

Math.min(...numbers); // -5

W momencie, kiedy widzimy takie zastosowanie wspomnianego operatora wszystko wydaje się bajecznie proste, ale tak wyjaśnijmy działanie tego kodu.

Funkcja Math.min(), choć podobnie jak Math.max(), które zwraca najmniejszą oraz największą wartość z przekazanych w argumentach działa normalnie. Tutaj nie ma żadnej magii, dlatego wywołanie Math.min() z tablicą jako argument zakończy się błędem, gdyż jest nieobsługiwane. Jednak kiedy przekażemy wiele argumentów, a to sprawia operator spread sprawa przybiera inny obrót.

Math.min(numbers); // NaN
Math.min(...numbers); // -5
Math.min(-5, -3, -1, 2, 4, 5, 7, 8, 11); // -5

Wówczas cała tablica jest rozwijana do kolejnych argumentów rozdzielonych przecinkami. Jest to równoznaczne z przekazanie wszystkich elementów tablicy jako kolejnych argumentów, a takie wywołanie jest wspierane przez funkcje Math.min() oraz Math.max(). Sami przyznacie, że bardzo użyteczne.

Są jednak pewne zastrzeżenia, co do wykorzystania tego rozwiązania, ponieważ nie zadziała przy dużych tablicach. Otóż implementacja JS ma limit w postaci 65536 argumentów przekazanych do funkcji. Mimo tego, warto wiedzieć jakie są możliwości znalezienia minimalnej i maksymalnej wartości w tablicy. Znacie jeszcze inne ciekawe metody? Piszcie w komentarzach.