Як сортувати масив асоціативних масивів за значенням заданого ключа в PHP?


446

Враховуючи цей масив:

$inventory = array(

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

);

Я хотів би сортувати $inventoryелементи за ціною, щоб отримати:

$inventory = array(

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

);

Як я можу це зробити?


Відповіді:


606

Ви маєте рацію, шукаєте функцію array_multisort().

Ось приклад, взятий прямо з посібника та адаптований до вашого випадку:

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

З PHP 5.5.0 ви можете використовувати array_column () замість цього foreach

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

array_multisort($price, SORT_DESC, $inventory);

5
Хоча це, безумовно, дорожче, ніж альтернативи.
Метт

5
Дорожчий? Це дивно, на моїй машині (працює PHP 5.3.1-dev) array_multisort () на кілька відсотків швидше на малих масивах і в 100 разів швидше на великих масивах (100+ елементів)
Джош Девіс,

3
Для роботи з цифровими клавішами вона не потребує змін. Якщо ви потрапляєте на помилку чи дивну поведінку, пов’язану з цифровими клавішами, опублікуйте це як нове запитання.
Джош Девіс

4
array_multisort має велику проблему: він не підтримує вихідний ключ.
машинобудівний вирок

1
@machineaddict він підтримує асоціативні ключі.
Matej Svajger

318

PHP 7+

Станом на PHP 7, це можна зробити стисло за usortдопомогою анонімної функції, яка використовує оператор космічного корабля для порівняння елементів.

Ви можете зробити висхідний сорт так:

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

Або низхідний сорт, як це:

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

Щоб зрозуміти, як це працює, зауважте, що usortвикористовується функція порівняння, надана користувачем, яка повинна поводитись так (з документів):

Функція порівняння повинна повертати ціле число менше, рівне або більше нуля, якщо перший аргумент вважається відповідно меншим, рівним або більшим за другий.

І зауважте також <=>, що оператор космічного корабля,

повертає 0, якщо обидва операнди рівні, 1 якщо лівий більший, і -1, якщо правий більший

що саме те, що usortпотрібно. Насправді, майже все обґрунтування для додавання <=>до мови в https://wiki.php.net/rfc/combined-comppare-operator полягає в тому, що це

робить написання замовлення зворотного виклику для використання з usort()простіше


PHP 5.3+

PHP 5.3 ввів анонімні функції, але ще не має оператора космічного корабля. Ми все ще можемо використовувати usortдля сортування наш масив, але це трохи більш багатослівно і важче зрозуміти:

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

Зауважте, що хоча порівняння, яке займається цілими значеннями, досить часто повертає різницю значень, наприклад $item2['price'] - $item1['price'], ми не можемо це безпечно зробити в цьому випадку. Це пояснюється тим, що ціни є цифрами з плаваючою комою в прикладі запитувача, але функція порівняння, яку ми передаємо usort, повинна повертати цілі числа, usortщоб правильно працювати:

Повернення нецілих значень з функції порівняння, наприклад, float, призведе до внутрішнього відтворення цілого числа зворотного значення зворотного виклику. Таким чином, такі значення, як 0,99 та 0,1, будуть приведені до цілого значення 0, яке порівняє такі значення як рівні.

Це важлива пастка, яку слід пам’ятати при використанні usortв PHP 5.x! Моя оригінальна версія цієї відповіді зробила цю помилку, і все ж я накопичив десять результатів за тисячі переглядів, мабуть, не помічаючи серйозних помилок. Простота, з якою такі недоліки, як я, можуть накручувати функції компаратора, є саме причиною того, що простіший у користуванні оператор космічного корабля був доданий до мови в PHP 7.


8
Вибачте, але цей підхід видаляє рядові ключі з асоціативних масивів. Натомість слід використовувати функцію "uasort".
Matteo-SoftNet

8
@DotMat Цікаво - я про це не знав uasort. Однак, подивившись на документи, ця відповідь у цьому випадку все-таки правильна . У прикладі ОП, масив для сортування має послідовні числові індекси, а не рядкові індекси, тому usortє більш підходящим. Використання uasortпослідовно-індексованого масиву призведе до відсортованого масиву, який не впорядкований за своїми числовими індексами, таким чином, що перший елемент, який бачите у foreachциклі, не є $your_array[0], що навряд чи буде бажаною поведінкою.
Марк Амері

100

Хоча інші правильно запропонували використовувати array_multisort(), чомусь відповідь не підтверджує існування array_column(), що може значно спростити рішення. Тож моя пропозиція буде:

array_multisort(array_column($inventory, 'price'), SORT_DESC, $inventory);

1
Я чомусь не зміг змусити його працювати з рядками, що мають нижню / верхню літери. Навіть використовуючи SORT_FLAG_CASE. Для порівняння рядків для мене працювали наступні: array_multisort (array_map (strtolower, array_column ($ ipr_projects, 'Name')), SORT_ASC, $ ipr_projects);
Пабамато

8
Це найелегантніша відповідь. Слід оцінювати набагато вище!
Армін Гірстеттер

3
найкоротший і найпростіший, прийнятий на мій погляд
StudioX

1
Хороші речі тут. Для мене прекрасно працювали!
Funk Doc

Працював як шарм, ти
Leif_Lundberg

42

Оскільки елементи масиву - це самі масиви за допомогою рядкових клавіш, найкраще зробити визначення налаштованої функції порівняння. Це досить швидко і просто зробити. Спробуйте це:

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

Випускає наступне:

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

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

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

)

4
Поєднуючись із деякими іншими коментарями тут (uasort та inline anonimno), ви отримуєте цей однокласник:uasort( $inventory, function ($a, $b) { if ( $a==$b ) return 0; else return ($a > $b) ? -1 : 1; });
Alan Porter,

@AlanPorter usortвидається більш доцільним, ніж uasortдля сортування масиву за допомогою послідовних цифрових клавіш. Закінчення масиву, де перший елемент знаходиться в індексі, 1а другий - в індексі, 0- дивна поведінка і вірний пастка для людей, які не знайомі з деталями масивів PHP; usortдає результат, який ви інтуїтивно очікували.
Марк Амері

25

Я закінчився на цьому:

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

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

Просто викличте функцію, передаючи масив та ім'я поля масиву другого рівня. Подібно до:

sort_array_of_array($inventory, 'price');

1
Га! Я просто зробив майже-таки абсолютно те саме, і збирався відправити повідомлення, але побачив ваше ... прихильне.
Роб Еванс

1
Відхилення, тому що це саме те рішення, яке Джош Девіс виклав роками раніше.
Марк Амері

Не погоджуюсь ... Я не казав, що це інше рішення, я просто сказав, що закінчив це рішення і даю повноцінну функціональну функцію.
Даніельц

1
@MarkAmery Я віддаю перевагу відповідям, що містяться у функціях. Це заохочує пастери копіювання використовувати функції та сподіваємось писати менше коду спагетті.
Гусак

19

Ви можете використовувати usortз анонімною функцією, наприклад

usort($inventory, function ($a, $b) { return strnatcmp($a['price'], $b['price']); });

Версії PHP 5> = 5.5.0, PHP 7 для тих з вас, як я, які дуже хотіли, щоб це працювало на них ..
Мет П

1
Примітно, що strnatcmpдля порівняння рядків, здається, тут добре працює. Мабуть, "природний порядок", який він реалізує, включає сортування числових рядків чисельно, а не лексично.
Марк Амері

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");
}

Виходи:

first: pork

pork: 5.43
fruit: 3.5
milk: 2.9

6

From Сортувати масив асоціативних масивів за значенням заданого ключа у php :

uasort ( http://php.net/uasort ) дозволяє сортувати масив за власною визначеною функцією. У вашому випадку все просто:

$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");

1
Ця відповідь з'явилася в черзі на огляд низької якості, імовірно, тому що ви не надаєте жодного пояснення коду. Якщо цей код відповідає на питання, спробуйте додати текст, який пояснює код у вашій відповіді. Таким чином, ви набагато більше шансів отримати більше грошей - і допомогти запитувачу дізнатися щось нове.
lmo

1
hmpf. це найкраща відповідь Тхо.
commonpike

1
-1; cmpфункція тут не так. Він повинен повернути "ціле число менше, рівне або більше нуля, якщо перший аргумент вважається відповідно меншим, рівним або більшим за другий", але замість цього повертає trueабо false. Але, напевно, все-таки це працює - можливо, тому, що поточна реалізація usortта друзі трактують випадки "менше, ніж" та "рівні" випадків, - але не розраховуйте на те, що він продовжує працювати в майбутніх версіях PHP. Якщо вони спробують зробити сорти стійкими (тобто не рухатись навколо рівних елементів без необхідності), це порушиться.
Марк Амері

Крім того, usortбуло б доцільніше, ніж uasortтут, оскільки uasortзберігається асоціація між ключами і значеннями, яка є заплутаною і несподіваною при наборі даних із послідовним числовим масивом. Наприклад, індекси $arrayвище після виклику uasortскладають 2, 0 та 1 у такому порядку. Якщо ви з якихось причин цього не хочете, вам, ймовірно, буде зручніше використовувати usort, що переробляє масив, а також переназначає його.
Марк Амері

5

Було випробувано на 100 000 записів: Час у секундах (обчислюється функціональним мікрочасом). Тільки для унікальних значень для сортування ключових позицій.

Рішення функції @Josh Davis: витрачений час : 1.5768740177155

Розчин шахти: витрачений час : 0,094044923782349

Рішення:

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*
}

7
Однак вимога до унікальних ключів сортування - це свого роду вимикач угод. Якщо у вас є унікальні значення сортування, які можуть бути ключами, виникає питання: а чому б просто не побудувати масив з цих клавіш для початку? За сценарієм ОП важко уявити, що два пункти з однаковою ціною були б неможливими . Маючи на увазі, використання цього рішення призведе до того, що елементи з масиву загадково та мовчки зникнуть із відсортованого набору результатів.
Кріс Бейкер

@Chris Baker, ти маєш рацію. Це працює лише для унікальних значень. Але це рішення працює дуже швидко, тому швидкість була причиною його виготовлення та використання. На даний момент це може бути не актуально, потрібно перевірити його за допомогою PHP 7.1.x.
Нефелім

4

Я використовую uasortтак

<?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);

3

Цю функцію можна використовувати повторно:

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

За замовчуванням він добре працює на рядкових значеннях, але вам доведеться подати зворотний виклик для функції порівняння чисел, якщо всі ваші значення - це числа.


Ви дзвоните це, usortarrале потім дзвоните uasortзамість usort; можливо трохи заплутано. Останнє - у випадку послідовного масиву з числовими індексами, як той, що виставлений у питанні, - ймовірно, що ви насправді хочете.
Марк Амері

2

Ви можете спробувати визначити свою власну функцію порівняння, а потім скористатися usort .


Так. Я зроблю це, якщо не зможу знайти рішення. Я майже впевнений, що є кілька дивних параметрів, які ви можете додати до одного з видів для цього. Дякуємо за ваші думки!
Метт

2

Ось метод, який я знайшов давно і трохи очистив. Це чудово працює, і його можна швидко змінити, щоб прийняти також об’єкти.

/**
 * 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;

}

щоб змінити метод сортування об'єктів, просто змініть наступний рядок:

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

Щоб запустити метод, просто зробіть

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


Цей підхід працює, але є досить менш стислим, ніж відповідь Джоша Девіса (з array_multisort) або моя (з usort) і, здається, не дає переваг перед ними в обмін.
Марк

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
    )

)

1

спробуйте це:

$prices = array_column($inventory, 'price');
array_multisort($prices, SORT_DESC, $inventory);
print_r($inventory);

Привіт, ласкаво просимо в stackoverflow, і дякую за відповідь. Хоча цей код може відповісти на питання, чи можете ви розглянути можливість пояснення того, яку проблему ви вирішили та як її вирішили? Це допоможе майбутнім читачам краще зрозуміти вашу відповідь та дізнатися її.
Плутіян

0

Повна динамічна функція Я стрибнув сюди для асоціативного сортування масиву і знайшов цю дивовижну функцію на http://php.net/manual/en/function.sort.php . Ця функція дуже динамічна, їх сортують у порядку зростання та спадання за вказаним ключем.

Проста функція сортування масиву за певною клавішею. Підтримує асоціацію індексів

<?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

-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;


} 

Хоча цей фрагмент коду може вирішити питання, зокрема пояснення дійсно допомагає покращити якість вашої публікації. Пам’ятайте, що ви відповідаєте на запитання читачів у майбутньому, і ці люди можуть не знати причини вашої пропозиції щодо коду. Будь ласка, намагайтеся не переповнювати свій код пояснювальними коментарями, оскільки це зменшує читабельність і коду, і пояснень!
Goodbye StackExchange

-5

спробуйте це:

asort($array_to_sort, SORT_NUMERIC);

Для довідки див. це: http://php.net/manual/en/function.asort.php

дивіться різні прапори сортування тут: http://www.php.net/manual/en/function.sort.php


це не буде працювати для багатовимірних масивів, але просто допомогло мені вирішити ще одну проблему, дякую :)
schellmax

4
Це не можна використовувати для сортування списку словників за певною клавішею словника, а значить, не відповідає на поставлене запитання.
Марк Амері
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.