Comment trier un tableau de tableaux associatifs par valeur d'une clé donnée en PHP?

446

Compte tenu de ce tableau:

$inventory = array(

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

);

Je voudrais trier $inventoryles éléments par prix pour obtenir:

$inventory = array(

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

);

Comment puis-je faire ceci?

Mat
la source

Réponses:

605

Vous avez raison, la fonction que vous recherchez est array_multisort().

Voici un exemple tiré directement du manuel et adapté à votre cas:

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

Depuis PHP 5.5.0, vous pouvez utiliser array_column () à la place de foreach

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

array_multisort($price, SORT_DESC, $inventory);
Josh Davis
la source
5
Bien que ce soit certainement plus cher que les alternatives.
Matt
5
Plus cher? C'est bizarre, sur ma machine (exécutant PHP 5.3.1-dev) array_multisort () est quelques pour cent plus rapide sur les petits tableaux et jusqu'à 100 fois plus rapide sur les grands tableaux (100+ éléments)
Josh Davis
3
Il ne devrait pas nécessiter de modification pour fonctionner avec les touches numériques. Si vous rencontrez un bug ou un comportement étrange lié aux touches numériques, postez-le comme une nouvelle question.
Josh Davis
4
array_multisort a un gros problème: il ne conserve pas la clé d'origine.
machineaddict
1
@machineaddict il maintient les clés associatives.
Matej Svajger
318

PHP 7+

Depuis PHP 7, cela peut être fait de manière concise en utilisant usortune fonction anonyme qui utilise l' opérateur du vaisseau spatial pour comparer les éléments.

Vous pouvez faire un tri croissant comme ceci:

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

Ou un tri décroissant comme celui-ci:

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

Pour comprendre comment cela fonctionne, notez que usortprend une fonction de comparaison fournie par l'utilisateur qui doit se comporter comme suit (à partir des documents):

La fonction de comparaison doit renvoyer un entier inférieur, égal ou supérieur à zéro si le premier argument est considéré respectivement inférieur, égal ou supérieur au second.

Et notez également que <=>, l'opérateur du vaisseau spatial,

renvoie 0 si les deux opérandes sont égaux, 1 si la gauche est supérieure et -1 si la droite est supérieure

c'est exactement ce qui a usortbesoin. En fait, presque toute la justification donnée pour ajouter <=>au langage dans https://wiki.php.net/rfc/combined-comparison-operator est qu'il

rend l' écriture callbacks de commande pour une utilisation avec usort()plus facile


PHP 5.3+

PHP 5.3 a introduit des fonctions anonymes, mais n'a pas encore d'opérateur de vaisseau spatial. Nous pouvons toujours utiliser usortpour trier notre tableau, mais c'est un peu plus détaillé et plus difficile à comprendre:

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

Notez que bien qu'il soit assez courant que les comparateurs traitant des valeurs entières renvoient simplement la différence des valeurs, comme $item2['price'] - $item1['price'], nous ne pouvons pas le faire en toute sécurité dans ce cas. C'est parce que les prix sont des nombres à virgule flottante dans l'exemple du poseur de questions, mais la fonction de comparaison à laquelle nous passons usortdoit renvoyer des entiers pour usortfonctionner correctement:

Revenant non entiers des valeurs de la fonction de comparaison, tel que le flotteur, se traduira par une distribution interne à nombre entier de la valeur de retour de la fonction de rappel. Ainsi, des valeurs telles que 0,99 et 0,1 seront toutes deux converties en une valeur entière de 0, qui comparera ces valeurs comme égales.

C'est un piège important à garder à l'esprit lors de l'utilisation usorten PHP 5.x! Ma version originale de cette réponse a fait cette erreur et pourtant j'ai accumulé dix votes positifs sur des milliers de vues apparemment sans que personne ne remarque le bug sérieux. La facilité avec laquelle les astucieux comme moi peuvent bousiller les fonctions du comparateur est précisément la raison pour laquelle l'opérateur de vaisseau spatial plus facile à utiliser a été ajouté au langage en PHP 7.

Mark Amery
la source
8
Désolé, mais cette approche supprime les clés de chaîne des tableaux associatifs. La fonction "uasort" doit être utilisée à la place.
Matteo-SoftNet
8
@DotMat Intéressant - je ne savais pas uasort. Après avoir regardé les documents, cependant, cette réponse est toujours correcte dans ce cas . Dans l'exemple de l'OP, le tableau à trier a des index numériques séquentiels plutôt que des index de chaîne, c'est donc usortplus approprié. L'utilisation uasortsur un tableau indexé séquentiellement entraînera un tableau trié qui n'est pas ordonné par ses index numériques, de sorte que le premier élément vu dans une foreachboucle ne l'est pas $your_array[0], ce qui est peu susceptible d'être un comportement souhaitable.
Mark Amery
100

Alors que d'autres ont correctement suggéré l'utilisation de array_multisort(), pour une raison quelconque, aucune réponse ne semble reconnaître l'existence de array_column()ce qui peut grandement simplifier la solution. Donc, ma suggestion serait:

array_multisort(array_column($inventory, 'price'), SORT_DESC, $inventory);
Mariano Iglesias
la source
1
Pour une raison quelconque, je n'ai pas pu le faire fonctionner avec des chaînes ayant des lettres inférieures / supérieures. Même en utilisant SORT_FLAG_CASE. Les éléments suivants ont fonctionné pour la comparaison de chaînes pour moi: array_multisort (array_map (strtolower, array_column ($ ipr_projects, 'Name')), SORT_ASC, $ ipr_projects);
Pabamato
8
C'est la réponse la plus élégante. Devrait être évalué beaucoup plus haut!
Armin Hierstetter
3
la plus courte et la plus simple, acceptée à mon avis
StudioX
1
De bonnes choses ici. A parfaitement fonctionné pour moi!
Funk Doc
A travaillé comme un charme, ty
Leif_Lundberg
42

Étant donné que vos éléments de tableau sont des tableaux eux-mêmes avec des clés de chaîne, votre meilleur pari est de définir une fonction de comparaison personnalisée. C'est assez rapide et facile à faire. Essaye ça:

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

Produit les éléments suivants:

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

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

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

)
zombat
la source
4
Combinant avec certains des autres commentaires ici (uasort et fonctions anonymes en ligne), vous obtenez ce one-liner:uasort( $inventory, function ($a, $b) { if ( $a==$b ) return 0; else return ($a > $b) ? -1 : 1; });
Alan Porter
@AlanPorter usortsemble plus approprié que uasortpour trier un tableau avec des touches numériques séquentielles. Se retrouver avec un tableau où le premier élément est à l'index 1et le deuxième élément à l'index 0est un comportement étrange et un piège sûr pour les personnes qui ne sont pas familières avec les détails des tableaux de PHP; usortvous donne la sortie que vous attendez intuitivement.
Mark Amery
25

Je termine sur ceci:

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

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

Appelez simplement la fonction en passant le tableau et le nom du champ du tableau de deuxième niveau. Comme:

sort_array_of_array($inventory, 'price');
Danielzt
la source
1
Ha! Je fais à peu près exactement la même chose et j'allais poster mais j'ai vu le vôtre ... voté.
Rob Evans
1
Downvoting parce que c'est exactement la même solution que Josh Davis avait publiée des années plus tôt.
Mark Amery du
Pas d'accord ... Je n'ai pas dit que c'était une solution différente, je viens de dire que j'ai fini avec cette solution et que je donne une fonction de travail complète.
Danielzt
1
@MarkAmery Je préfère les réponses contenues dans les fonctions. Il encourage les copier-coller à utiliser des fonctions et, espérons-le, à écrire moins de code spaghetti.
Goose
19

Vous pouvez utiliser usortavec une fonction anonyme, par exemple

usort($inventory, function ($a, $b) { return strnatcmp($a['price'], $b['price']); });
kenorb
la source
Versions PHP 5> = 5.5.0, PHP 7 pour ceux d'entre vous comme moi qui voulaient vraiment que cela fonctionne pour eux ..
Matt P
1
Remarquable qui strnatcmp, destiné à comparer des chaînes, semble bien fonctionner ici. Apparemment, l '«ordre naturel» qu'il met en œuvre comprend le tri numérique des chaînes numériques plutôt que lexical.
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");
}

les sorties:

first: pork

pork: 5.43
fruit: 3.5
milk: 2.9
danamlund
la source
6

De Trier un tableau de tableaux associatifs par valeur de clé donnée en php :

uasort ( http://php.net/uasort ) vous permet de trier un tableau selon votre propre fonction définie. Dans votre cas, c'est simple:

$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
la source
1
Cette réponse est apparue dans la file d'attente d'examen de faible qualité, probablement parce que vous ne fournissez aucune explication du code. Si ce code répond à la question, envisagez d'ajouter un texte expliquant le code dans votre réponse. De cette façon, vous êtes beaucoup plus susceptible d'obtenir plus de votes positifs - et d'aider le questionneur à apprendre quelque chose de nouveau.
lmo
1
hmpf. c'est la meilleure réponse.
commonpike
1
-1; la cmpfonction ici est fausse. Il est censé renvoyer "un entier inférieur, égal ou supérieur à zéro si le premier argument est considéré respectivement inférieur, égal ou supérieur au second", mais renvoie à la place trueou false. Cela semble, remarquablement, fonctionner néanmoins - peut-être parce que l'implémentation actuelle de usortand friends traite les cas "inférieur à" et "égal à" à l'identique - mais ne comptez pas sur le fait qu'il continue à fonctionner dans les futures versions de PHP. S'ils essaient de rendre les tris stables (c'est-à-dire de ne pas déplacer inutilement des éléments égaux), cela se cassera.
Mark Amery
En outre, usortserait plus approprié uasortqu'ici, car uasortpréserve l'association entre les clés et les valeurs, ce qui est déroutant et inattendu lors de la désactivation avec un tableau numérique séquentiel. Par exemple, les index $arrayci - dessus après l'appel uasortsont 2, 0 et 1, dans cet ordre. À moins que vous ne le souhaitiez pour une raison quelconque, vous serez probablement plus à l'aise pour utiliser usort, ce qui réindexera le tableau ainsi que le réordonnancera.
Mark Amery
5

A été testé sur 100 000 enregistrements: Temps en secondes (calculé par microtime funciton). Uniquement pour les valeurs uniques sur le tri des positions clés.

Solution de la fonction de @Josh Davis: Temps passé : 1.5768740177155

Solution minière: Temps passé : 0,094044923782349

Solution:

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
la source
7
Cependant, l'exigence de clés de tri uniques est en quelque sorte un facteur de rupture. Si vous avez des valeurs de tri uniques qui peuvent être des clés, cela pose la question: pourquoi ne pas simplement construire le tableau avec ces clés pour commencer? Dans le scénario du PO, il est difficile d'imaginer que deux articles avec le même prix seraient impossibles . Cela étant, l'utilisation de cette solution entraînerait la disparition mystérieuse et silencieuse des éléments du tableau de l'ensemble de résultats trié.
Chris Baker
@Chris Baker, vous avez raison. Cela ne fonctionne que pour des valeurs uniques. Mais cette solution fonctionne très rapidement, donc la vitesse était la raison de sa fabrication et de son utilisation. Pour le moment, ce n'est peut-être pas réel, il faut le tester avec PHP 7.1.x.
Nefelim
4

J'utilise uasortcomme ça

<?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
la source
3

Cette fonction est réutilisable:

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

Il fonctionne bien sur les valeurs de chaîne par défaut, mais vous devrez subvoquer le rappel pour une fonction de comparaison de nombres si toutes vos valeurs sont des nombres.

mpen
la source
Vous appelez cela usortarrmais appelez ensuite uasortau lieu de usort; peut-être un peu déroutant. Ce dernier est - dans le cas d'un tableau séquentiel avec des index numériques, comme celui présenté dans la question - probablement ce que vous voulez réellement.
Mark Amery
2

Vous pouvez essayer de définir votre propre fonction de comparaison, puis utiliser usort .

Alex Sexton
la source
Oui. Je le ferai si je ne trouve pas de solution. Je suis presque sûr qu'il existe des paramètres étranges que vous pouvez ajouter à l'un des types pour y parvenir. Merci pour vos pensées!
Matt
2

Voici une méthode que j'ai trouvée il y a longtemps et que j'ai un peu nettoyée. Cela fonctionne très bien et peut être rapidement modifié pour accepter également les objets.

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

}

pour changer la méthode de tri des objets, changez simplement la ligne suivante:

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

Pour exécuter la méthode, faites simplement

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

lzoesch
la source
Cette approche fonctionne, mais est un peu moins concise que la réponse de Josh Davis (avec array_multisort) ou la mienne (avec usort) et ne semble offrir aucun avantage sur eux en échange.
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
la source
1

essaye ça:

$prices = array_column($inventory, 'price');
array_multisort($prices, SORT_DESC, $inventory);
print_r($inventory);
jamshid
la source
Bonjour et bienvenue sur stackoverflow, et merci d'avoir répondu. Bien que ce code puisse répondre à la question, pouvez-vous envisager d'ajouter des explications sur le problème que vous avez résolu et comment vous l'avez résolu? Cela aidera les futurs lecteurs à mieux comprendre votre réponse et à en tirer des enseignements.
Plutian
0

Fonction dynamique complète J'ai sauté ici pour le tri associatif de tableaux et j'ai trouvé cette fonction étonnante sur http://php.net/manual/en/function.sort.php . Cette fonction est très dynamique qui trie dans l'ordre croissant et décroissant avec la clé spécifiée.

Fonction simple pour trier un tableau par une clé spécifique. Maintient l'association d'index

<?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
la source
-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
la source
Bien que cet extrait de code puisse résoudre la question, y compris une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question des lecteurs à l'avenir et que ces personnes ne connaissent peut-être pas les raisons de votre suggestion de code. Essayez également de ne pas surcharger votre code avec des commentaires explicatifs, car cela réduit la lisibilité du code et des explications!
Au revoir StackExchange
-5

essaye ça:

asort($array_to_sort, SORT_NUMERIC);

pour référence voir ceci: http://php.net/manual/en/function.asort.php

voir divers drapeaux de tri ici: http://www.php.net/manual/en/function.sort.php

serpent
la source
cela ne fonctionnera pas pour les tableaux multidimensionnels, mais m'a juste aidé pour un autre problème, merci :)
schellmax
4
Cela ne peut pas être utilisé pour trier une liste de dictionnaires par une clé de dictionnaire particulière, et ne répond donc pas à la question posée.
Mark Amery