Jak posortować tablicę tablic asocjacyjnych według wartości danego klucza w PHP?

446

Biorąc pod uwagę tę tablicę:

$inventory = array(

   array("type"=>"fruit", "price"=>3.50),
   array("type"=>"milk", "price"=>2.90),
   array("type"=>"pork", "price"=>5.43),

);

Chciałbym posortować $inventoryelementy według ceny, aby uzyskać:

$inventory = array(

   array("type"=>"pork", "price"=>5.43),
   array("type"=>"fruit", "price"=>3.50),
   array("type"=>"milk", "price"=>2.90),

);

W jaki sposób mogę to zrobić?

Matt
źródło

Odpowiedzi:

606

Masz rację, funkcja, której szukasz array_multisort().

Oto przykład zaczerpnięty bezpośrednio z instrukcji i dostosowany do twojego przypadku:

$price = array();
foreach ($inventory as $key => $row)
{
    $price[$key] = $row['price'];
}
array_multisort($price, SORT_DESC, $inventory);

Począwszy od PHP 5.5.0, możesz używać array_column () zamiast tego foreach

$price = array_column($inventory, 'price');

array_multisort($price, SORT_DESC, $inventory);
Josh Davis
źródło
5
Chociaż jest to zdecydowanie droższe niż alternatywy.
Matt
5
Droższe? To dziwne, na mojej maszynie (działającej w PHP 5.3.1-dev) array_multisort () jest kilka procent szybszy na małych tablicach i do 100 razy szybszy na dużych tablicach (ponad 100 elementów)
Josh Davis
3
Nie powinno wymagać żadnych zmian do pracy z klawiszami numerycznymi. Jeśli napotykasz błąd lub dziwne zachowanie związane z klawiszami numerycznymi, opublikuj je jako nowe pytanie.
Josh Davis
4
array_multisort ma duży problem: nie utrzymuje oryginalnego klucza.
machineaddict
1
@machineaddict utrzymuje klucze asocjacyjne.
Matej Svajger
318

PHP 7+

Począwszy od PHP 7, można tego dokonać zwięźle za usortpomocą anonimowej funkcji, która używa operatora statku kosmicznego do porównywania elementów.

Możesz wykonać sortowanie rosnące w następujący sposób:

usort($inventory, function ($item1, $item2) {
    return $item1['price'] <=> $item2['price'];
});

Lub sortowanie malejące takie jak to:

usort($inventory, function ($item1, $item2) {
    return $item2['price'] <=> $item1['price'];
});

Aby zrozumieć, jak to działa, należy pamiętać, że usortbierze podaną przez użytkownika funkcję porównywania, która musi zachowywać się w następujący sposób (z dokumentów):

Funkcja porównania musi zwracać liczbę całkowitą mniejszą, równą lub większą od zera, jeśli pierwszy argument jest uważany za odpowiednio mniejszy, równy lub większy niż drugi.

Pamiętaj też <=>, że operator statku kosmicznego

zwraca 0, jeśli oba operandy są równe, 1, jeśli lewa jest większa, a -1, jeśli prawa jest większa

dokładnie tego usortpotrzebuje. W rzeczywistości prawie całe uzasadnienie dodania <=>do języka w https://wiki.php.net/rfc/combined-comparison-operator jest takie, że

sprawia, że pisanie callbacków zamawiania do użytku z usort()łatwiejsze


PHP 5.3+

PHP 5.3 wprowadził anonimowe funkcje, ale nie ma jeszcze operatora statku kosmicznego. Nadal możemy używać usortdo sortowania naszej tablicy, ale jest to trochę bardziej szczegółowe i trudniejsze do zrozumienia:

usort($inventory, function ($item1, $item2) {
    if ($item1['price'] == $item2['price']) return 0;
    return $item1['price'] < $item2['price'] ? -1 : 1;
});

Zauważ, że chociaż komparatory zajmujące się wartościami całkowitymi dość często zwracają różnicę wartości $item2['price'] - $item1['price'], nie możemy tego bezpiecznie zrobić w tym przypadku. Wynika to z faktu, że ceny są liczbami zmiennoprzecinkowymi w przykładzie osoby zadającej pytanie, ale funkcja porównania, do której przekazujemy, usortmusi zwracać liczby całkowite, usortaby działała poprawnie:

Zwracanie wartości niecałkowitych z funkcji porównania, takich jak liczba zmiennoprzecinkowa, spowoduje wewnętrzne rzutowanie na liczbę całkowitą zwracanej wartości wywołania zwrotnego. Tak więc wartości takie jak 0,99 i 0,1 zostaną rzutowane na liczbę całkowitą 0, która porówna takie wartości jako równe.

Jest to ważna pułapka, o której należy pamiętać podczas korzystania usortz PHP 5.x! Moja oryginalna wersja tej odpowiedzi popełniła ten błąd, a mimo to zebrałem dziesięć głosów pozytywnych na tysiące wyświetleń, najwyraźniej nikogo nie zauważając poważnego błędu. Łatwość, z jaką takie niedorzeczne osoby jak ja mogą zepsuć funkcje komparatora, jest właśnie przyczyną, dla której łatwiejszy w obsłudze operator statku kosmicznego został dodany do języka w PHP 7.

Mark Amery
źródło
8
Przepraszamy, ale to podejście usuwa klucze łańcuchowe z tablic asocjacyjnych. Zamiast tego należy użyć funkcji „uasort”.
Matteo-SoftNet
8
@DotMat Ciekawe - nie wiedziałem o uasort. Jednak po przejrzeniu dokumentów ta odpowiedź jest nadal poprawna w tym przypadku . W przykładzie OP, tablica do sortowania ma sekwencyjne indeksy numeryczne zamiast indeksów łańcuchowych, więc usortjest bardziej odpowiednie. Użycie uasortna tablicy indeksowanej sekwencyjnie spowoduje posortowanie tablicy, która nie jest uporządkowana według indeksów numerycznych, tak że pierwszy element widziany w foreachpętli nie jest $your_array[0], co jest mało prawdopodobne.
Mark Amery
100

Podczas gdy inni poprawnie zasugerowali użycie array_multisort(), z jakiegoś powodu wydaje się, że żadna odpowiedź nie potwierdza istnienia array_column(), co może znacznie uprościć rozwiązanie. Tak więc moja sugestia brzmiałaby:

array_multisort(array_column($inventory, 'price'), SORT_DESC, $inventory);
Mariano Iglesias
źródło
1
Z jakiegoś powodu nie byłem w stanie sprawić, by działał z łańcuchami mającymi małe / górne litery. Nawet używając SORT_FLAG_CASE. Dla porównania działało dla mnie: array_multisort (array_map (strtolower, array_column ($ ipr_projects, 'Name')), SORT_ASC, $ ipr_projects);
Pabamato,
8
To najbardziej elegancka odpowiedź. Powinien być oceniony znacznie wyżej!
Armin Hierstetter,
3
najkrótszy i najłatwiejszy, zaakceptowany moim zdaniem
StudioX
1
Dobre rzeczy tutaj. Działa dla mnie idealnie!
Funk Doc
Działa jak urok, ty
Leif_Lundberg
42

Ponieważ elementy tablicy same w sobie są tablicami z kluczami łańcuchowymi, najlepszym rozwiązaniem jest zdefiniowanie niestandardowej funkcji porównania. Jest to dość szybkie i łatwe do zrobienia. Spróbuj tego:

function invenDescSort($item1,$item2)
{
    if ($item1['price'] == $item2['price']) return 0;
    return ($item1['price'] < $item2['price']) ? 1 : -1;
}
usort($inventory,'invenDescSort');
print_r($inventory);

Wytwarza następujące:

Array
(
    [0] => Array
        (
            [type] => pork
            [price] => 5.43
        )

    [1] => Array
        (
            [type] => fruit
            [price] => 3.5
        )

    [2] => Array
        (
            [type] => milk
            [price] => 2.9
        )

)
zombat
źródło
4
W połączeniu z niektórymi innymi komentarzami tutaj (funkcje anonimowe i wbudowane) otrzymujesz ten linijka:uasort( $inventory, function ($a, $b) { if ( $a==$b ) return 0; else return ($a > $b) ? -1 : 1; });
Alan Porter
@AlanPorter usortwydaje się bardziej odpowiednie niż uasortdo sortowania tablicy z sekwencyjnymi klawiszami numerycznymi. Skończyło się na tablicy, w której pierwszy element znajduje się w indeksie, 1a drugi w indeksie 0to dziwne zachowanie i pewna pułapka dla osób, które nie są zaznajomione ze szczegółami tablic PHP; usortdaje wynik, którego intuicyjnie oczekujesz.
Mark Amery
25

Skończyłem na tym:

function sort_array_of_array(&$array, $subfield)
{
    $sortarray = array();
    foreach ($array as $key => $row)
    {
        $sortarray[$key] = $row[$subfield];
    }

    array_multisort($sortarray, SORT_ASC, $array);
}

Wystarczy wywołać funkcję, przekazując tablicę i nazwę pola tablicy drugiego poziomu. Lubić:

sort_array_of_array($inventory, 'price');
Danielzt
źródło
1
Ha! Po prostu zrobiłem prawie dokładnie to samo i zamierzałem opublikować, ale widziałem twoje ... pochwalone.
Rob Evans,
1
Oddawanie głosu, ponieważ jest to dokładnie to samo rozwiązanie, które Josh Davis opublikował wiele lat wcześniej.
Mark Amery
Nie zgadzam się ... Nie powiedziałem, że to inne rozwiązanie, po prostu powiedziałem, że skończyłem z tym rozwiązaniem i zapewniłem pełną funkcjonalność.
Danielzt
1
@MarkAmery Wolę odpowiedzi zawarte w funkcjach. Zachęca pastery do kopiowania do korzystania z funkcji i, mam nadzieję, napisania mniejszego kodu spaghetti.
Goose
19

Możesz używać usortz funkcją anonimową, np

usort($inventory, function ($a, $b) { return strnatcmp($a['price'], $b['price']); });
kenorb
źródło
Wersje PHP 5> = 5.5.0, PHP 7 dla tych z was takich jak ja, którzy naprawdę chcieli, aby to działało dla nich ..
Matt P
1
Zadziwiające, że strnatcmp, przeznaczone do porównywania ciągów, wydaje się tutaj dobrze działać. Najwyraźniej realizowany przez niego „naturalny porządek” obejmuje sortowanie ciągów liczbowych numerycznie, a nie leksykalnie.
Mark Amery
10
$inventory = 
    array(array("type"=>"fruit", "price"=>3.50),
          array("type"=>"milk", "price"=>2.90),
          array("type"=>"pork", "price"=>5.43),
          );

function pricesort($a, $b) {
  $a = $a['price'];
  $b = $b['price'];
  if ($a == $b)
    return 0;
  return ($a > $b) ? -1 : 1;
}

usort($inventory, "pricesort");
// uksort($inventory, "pricesort");

print("first: ".$inventory[0]['type']."\n\n");
// for usort(): prints milk (item with lowest price)
// for uksort(): prints fruit (item with key 0 in the original $inventory)

// foreach prints the same for usort and uksort.
foreach($inventory as $i){
  print($i['type'].": ".$i['price']."\n");
}

wyjścia:

first: pork

pork: 5.43
fruit: 3.5
milk: 2.9
Danamlund
źródło
6

Od Sortuj tablicę tablic asocjacyjnych według wartości danego klucza w php :

uasort ( http://php.net/uasort ) pozwala sortować tablicę według własnej zdefiniowanej funkcji. W twoim przypadku to proste:

$array = array(
  array('price'=>'1000.50','product'=>'test1'),
  array('price'=>'8800.50','product'=>'test2'),
  array('price'=>'200.0','product'=>'test3')
);

function cmp($a, $b) {
  return $a['price'] > $b['price'];
}

uasort($array, "cmp");
Kamal
źródło
1
Ta odpowiedź pojawiła się w kolejce przeglądu niskiej jakości, prawdopodobnie dlatego, że nie podałeś żadnego wyjaśnienia kodu. Jeśli ten kod odpowiada na pytanie, rozważ dodanie tekstu wyjaśniającego kod w swojej odpowiedzi. W ten sposób znacznie częściej zyskujesz więcej głosów pozytywnych - i pomagasz pytającemu dowiedzieć się czegoś nowego.
Lmo
1
hmpf. to najlepsza odpowiedź.
commonpike,
1
-1; cmpfunkcja tu jest nie tak. Ma zwracać „liczbę całkowitą mniejszą, równą lub większą od zera, jeśli pierwszy argument jest uważany za odpowiednio mniejszy, równy lub większy od drugiego”, ale zamiast tego zwraca truelub false. Co ciekawe, wydaje się, że mimo to działa - być może dlatego, że obecna implementacja usorti znajomi traktują przypadki „mniej niż” i „równa się” identycznie - ale nie licz na to, że będzie działać w przyszłych wersjach PHP. Jeśli spróbują uczynić sortowania stabilnymi (tj. Nie będą niepotrzebnie poruszać się po równych elementach), to się zepsuje.
Mark Amery
Ponadto, usortbyłaby bardziej odpowiednia niż w uasorttym wypadku, ponieważ uasortzachowuje związek pomiędzy przyciskami i wartości, które są kłopotliwe i nieoczekiwane, gdy deaing z sekwencyjnym tablicy numerycznej. Na przykład powyższe indeksy $arraypo wywołaniu uasortwynoszą 2, 0 i 1, w tej kolejności. Chyba że z jakiegoś powodu tego chcesz, prawdopodobnie będziesz wygodniejszy w użyciu usort, co ponownie zindeksuje tablicę, a także zmieni ją.
Mark Amery
5

Przetestowano na 100 000 rekordach: Czas w sekundach (obliczony przez funciton microtime). Tylko dla unikalnych wartości przy sortowaniu kluczowych pozycji.

Rozwiązanie funkcji @Josh Davis: Czas spędzony: 1.5768740177155

Moje rozwiązanie: spędzony czas : 0,094044923782349

Rozwiązanie:

function SortByKeyValue($data, $sortKey, $sort_flags=SORT_ASC)
{
    if (empty($data) or empty($sortKey)) return $data;

    $ordered = array();
    foreach ($data as $key => $value)
        $ordered[$value[$sortKey]] = $value;

    ksort($ordered, $sort_flags);

    return array_values($ordered); *// array_values() added for identical result with multisort*
}
Nefelim
źródło
7
Wymóg posiadania unikatowych kluczy sortowania jest jednak rodzajem rozłamu. Jeśli masz unikalne wartości sortowania, które mogą być kluczami, nasuwa się pytanie: dlaczego po prostu nie zbudować tablicy z tymi kluczami na początek? W scenariuszu PO trudno sobie wyobrazić, że dwa przedmioty o tej samej cenie byłyby niemożliwe . Mając to na uwadze, użycie tego rozwiązania spowoduje, że elementy z tablicy tajemniczo i cicho znikną z posortowanego zestawu wyników.
Chris Baker,
@Chris Baker, masz rację. Działa to tylko w przypadku unikalnych wartości. Ale to rozwiązanie działa bardzo szybko, więc prędkość była przyczyną jego stworzenia i użycia. W tej chwili może to nie być rzeczywiste, musisz przetestować go w PHP 7.1.x.
Nefelim,
4

Używam uasorttakiego

<?php
$users = [
    [
        'username' => 'joe',
        'age' => 11
    ],
    [
        'username' => 'rakoto',
        'age' => 21
    ],
    [
        'username' => 'rabe',
        'age' => 17
    ],
    [
        'username' => 'fy',
        'age' => 19
    ],    
];


uasort($users, function ($item, $compare) {
    return $item['username'] >= $compare['username']; 
});

var_dump($users);
mirado
źródło
3

Ta funkcja jest wielokrotnego użytku:

function usortarr(&$array, $key, $callback = 'strnatcasecmp') {
    uasort($array, function($a, $b) use($key, $callback) {
        return call_user_func($callback, $a[$key], $b[$key]);
    });
}

Domyślnie działa dobrze na wartościach ciągów, ale jeśli wszystkie wartości są liczbami, konieczne będzie wywołanie zwrotne funkcji porównywania liczb.

mpen
źródło
Dzwonisz, usortarrale potem dzwonisz uasortzamiast usort; może trochę mylące. To ostatnie - w przypadku tablicy sekwencyjnej z indeksami numerycznymi, jak ta przedstawiona w pytaniu - prawdopodobnie tego, czego naprawdę chcesz.
Mark Amery
2

Możesz spróbować zdefiniować własną funkcję porównania, a następnie użyć usort .

Alex Sexton
źródło
Tak. Zrobię to, jeśli nie mogę znaleźć rozwiązania. Jestem pewien, że istnieją pewne dziwne parametry, które można dodać do jednego z tych rodzajów, aby to osiągnąć. Dziękuję za twoje przemyślenia!
Matt
2

Oto metoda, którą znalazłem dawno temu i trochę posprzątałem. Działa to świetnie i można je szybko zmienić, aby akceptować również obiekty.

/**
 * A method for sorting arrays by a certain key:value.
 * SortByKey is the key you wish to sort by
 * Direction can be ASC or DESC.
 *
 * @param $array
 * @param $sortByKey
 * @param $sortDirection
 * @return array
 */
private function sortArray($array, $sortByKey, $sortDirection) {

    $sortArray = array();
    $tempArray = array();

    foreach ( $array as $key => $value ) {
        $tempArray[] = strtolower( $value[ $sortByKey ] );
    }

    if($sortDirection=='ASC'){ asort($tempArray ); }
        else{ arsort($tempArray ); }

    foreach ( $tempArray as $key => $temp ){
        $sortArray[] = $array[ $key ];
    }

    return $sortArray;

}

aby zmienić metodę sortowania obiektów, wystarczy zmienić następujący wiersz:

$tempArray[] = strtolower( $value[ $sortByKey ] ); do $tempArray[] = strtolower( $value->$sortByKey );

Aby uruchomić metodę, po prostu zrób

sortArray($inventory,'price','ASC');

Lzoesch
źródło
To podejście działa, ale jest nieco mniej zwięzłe niż odpowiedź Josha Davisa (z array_multisort) lub moja (z usort) i wydaje się, że nie daje w zamian żadnych korzyści.
Mark Amery
1
//Just in one line custom function
function cmp($a, $b)
{
return (float) $a['price'] < (float)$b['price'];
}
@uasort($inventory, "cmp");
print_r($inventory);

//result

Array
(
[2] => Array
    (
        [type] => pork
        [price] => 5.43
    )

[0] => Array
    (
        [type] => fruit
        [price] => 3.5
    )

[1] => Array
    (
        [type] => milk
        [price] => 2.9
    )

)
Kamal
źródło
1

Spróbuj tego:

$prices = array_column($inventory, 'price');
array_multisort($prices, SORT_DESC, $inventory);
print_r($inventory);
jamshid
źródło
Cześć, witamy w Stackoverflow i dziękujemy za odpowiedź. Chociaż ten kod może odpowiedzieć na pytanie, czy możesz rozważyć dodanie wyjaśnienia problemu, który rozwiązałeś i jak go rozwiązać? Pomoże to przyszłym czytelnikom lepiej zrozumieć twoją odpowiedź i wyciągnąć z niej wnioski.
Plutian
0

Kompletna funkcja dynamiczna Przeskoczyłem tutaj, aby sortować tablice asocjacyjne i znalazłem tę niesamowitą funkcję na stronie http://php.net/manual/en/function.sort.php . Ta funkcja jest bardzo dynamiczna i sortuje rosnąco i malejąco według określonego klucza.

Prosta funkcja do sortowania tablicy według określonego klucza. Utrzymuje powiązanie indeksu

<?php

function array_sort($array, $on, $order=SORT_ASC)
{
    $new_array = array();
    $sortable_array = array();

    if (count($array) > 0) {
        foreach ($array as $k => $v) {
            if (is_array($v)) {
                foreach ($v as $k2 => $v2) {
                    if ($k2 == $on) {
                        $sortable_array[$k] = $v2;
                    }
                }
            } else {
                $sortable_array[$k] = $v;
            }
        }

        switch ($order) {
            case SORT_ASC:
                asort($sortable_array);
            break;
            case SORT_DESC:
                arsort($sortable_array);
            break;
        }

        foreach ($sortable_array as $k => $v) {
            $new_array[$k] = $array[$k];
        }
    }

    return $new_array;
}

$people = array(
    12345 => array(
        'id' => 12345,
        'first_name' => 'Joe',
        'surname' => 'Bloggs',
        'age' => 23,
        'sex' => 'm'
    ),
    12346 => array(
        'id' => 12346,
        'first_name' => 'Adam',
        'surname' => 'Smith',
        'age' => 18,
        'sex' => 'm'
    ),
    12347 => array(
        'id' => 12347,
        'first_name' => 'Amy',
        'surname' => 'Jones',
        'age' => 21,
        'sex' => 'f'
    )
);

print_r(array_sort($people, 'age', SORT_DESC)); // Sort by oldest first
print_r(array_sort($people, 'surname', SORT_ASC)); // Sort by surname
Ahmad Sayeed
źródło
-1
$arr1 = array(

    array('id'=>1,'name'=>'aA','cat'=>'cc'),
    array('id'=>2,'name'=>'aa','cat'=>'dd'),
    array('id'=>3,'name'=>'bb','cat'=>'cc'),
    array('id'=>4,'name'=>'bb','cat'=>'dd')
);

$result1 = array_msort($arr1, array('name'=>SORT_DESC);

$result2 = array_msort($arr1, array('cat'=>SORT_ASC);

$result3 = array_msort($arr1, array('name'=>SORT_DESC, 'cat'=>SORT_ASC));


function array_msort($array, $cols)
{
    $colarr = array();
    foreach ($cols as $col => $order) {
    $colarr[$col] = array();
    foreach ($array as $k => $row) { $colarr[$col]['_'.$k] = strtolower($row[$col]); }
}

$eval = 'array_multisort(';

foreach ($cols as $col => $order) {
    $eval .= '$colarr[\''.$col.'\'],'.$order.',';
}

$eval = substr($eval,0,-1).');';
eval($eval);
$ret = array();
foreach ($colarr as $col => $arr) {
    foreach ($arr as $k => $v) {
        $k = substr($k,1);
        if (!isset($ret[$k])) $ret[$k] = $array[$k];
        $ret[$k][$col] = $array[$k][$col];
    }
}
return $ret;


} 
Chirag Pipariya
źródło
Ten fragment kodu może rozwiązać pytanie, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu. Staraj się również nie tłoczyć kodu objaśniającymi komentarzami, ponieważ zmniejsza to czytelność zarówno kodu, jak i objaśnień!
Do widzenia StackExchange
-5

Spróbuj tego:

asort($array_to_sort, SORT_NUMERIC);

w celach informacyjnych patrz: http://php.net/manual/en/function.asort.php

zobacz różne flagi sortowania tutaj: http://www.php.net/manual/en/function.sort.php

sarsnake
źródło
to nie zadziała w przypadku tablic wielowymiarowych, ale pomogło mi w rozwiązaniu innego problemu, dziękuję :)
schellmax
4
Nie można tego użyć do sortowania listy słowników według określonego klucza słownika, a zatem nie odpowiada na postawione pytanie.
Mark Amery