web-dev-qa-db-fra.com

Tri d'un tableau de tableaux php par ordre personnalisé

J'ai un tableau de tableaux:

Array ( 
    [0] => Array (
        [id] = 7867867,
        [title] = 'Some Title'),
    [1] => Array (
        [id] = 3452342,
        [title] = 'Some Title'),
    [2] => Array (
        [id] = 1231233,
        [title] = 'Some Title'),
    [3] => Array (
        [id] = 5867867,
        [title] = 'Some Title')
)

La nécessité d'aller dans un ordre spécifique:

  1. 3452342
  2. 5867867
  3. 7867867
  4. 1231233

Comment pourrais-je m'y prendre? J'ai déjà trié des tableaux et lu beaucoup d'autres articles à ce sujet, mais ils sont toujours basés sur des comparaisons (c'est-à-dire valueA <valeurB).

L'aide est appréciée.

44
Honus Wagner

Vous pouvez utiliser usort() pour dicter avec précision la manière dont le tableau doit être trié. Dans ce cas, le tableau $order peut être utilisé dans la fonction de comparaison.

L'exemple ci-dessous utilise un closure pour vous simplifier la vie.

$order = array(3452342, 5867867, 7867867, 1231233);
$array = array(
    array('id' => 7867867, 'title' => 'Some Title'),
    array('id' => 3452342, 'title' => 'Some Title'),
    array('id' => 1231233, 'title' => 'Some Title'),
    array('id' => 5867867, 'title' => 'Some Title'),
);

usort($array, function ($a, $b) use ($order) {
    $pos_a = array_search($a['id'], $order);
    $pos_b = array_search($b['id'], $order);
    return $pos_a - $pos_b;
});

var_dump($array);

La clé de ce fonctionnement est d'avoir les valeurs qui sont comparées, soit les positions des ids dans le tableau $order.

La fonction de comparaison fonctionne en recherchant les positions des identifiants de deux éléments à comparer dans le tableau $order. Si $a['id'] vient avant $b['id'] dans le tableau $order, la valeur de retour de la fonction sera alors négative ($a est moins "flottant" vers le haut). Si $a['id'] vient après $b['id'], la fonction retourne un nombre positif ($a est supérieur, donc "puits"). 

Enfin, il n’existe aucune raison particulière d’utiliser une fermeture; c’est juste ma façon d’écrire ces sortes de fonctions à jeter rapidement. Il pourrait également utiliser une fonction nommée normale.

93
salathe

Prolonger réponse de salathe pour cette exigence supplémentaire:

Maintenant que se passe-t-il lorsque j'ajoute des éléments au tableau et non au tri? JE ne vous souciez pas de l'ordre dans lequel ils apparaissent, tant que cela vient après ceux-ci que j'ai précisé.

Vous devez ajouter deux conditions supplémentaires à la fonction de tri:

  1. Un élément "dont il faut se soucier" doit être considéré comme supérieur à un article "se soucier de"
  2. Deux éléments "ne pas déranger" doivent être considérés comme égaux

Le code révisé serait donc:

$order = array(
    3452342,
    5867867,
    7867867,
    1231233
);
$array = array(
    array("id" => 7867867, "title" => "Must Be #3"),
    array("id" => 3452342, "title" => "Must Be #1"),
    array("id" => 1231233, "title" => "Must Be #4"),
    array("id" => 5867867, "title" => "Must Be #2"),
    array("id" => 1111111, "title" => "Dont Care #1"),
    array("id" => 2222222, "title" => "Dont Care #2"),
    array("id" => 3333333, "title" => "Dont Care #3"),
    array("id" => 4444444, "title" => "Dont Care #4")
);
function custom_compare($a, $b){
    global $order;
    $a = array_search($a["id"], $order);
    $b = array_search($b["id"], $order);
    if($a === false && $b === false) { // both items are dont cares
        return 0;                      // a == b
    }
    else if ($a === false) {           // $a is a dont care item
        return 1;                      // $a > $b
    }
    else if ($b === false) {           // $b is a dont care item
        return -1;                     // $a < $b
    }
    else {
        return $a - $b;
    }
}
shuffle($array);  // for testing
var_dump($array); // before
usort($array, "custom_compare");
var_dump($array); // after

Sortie:

Before                         |  After
-------------------------------+-------------------------------
array(8) {                     |  array(8) {
  [0]=>                        |    [0]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(4444444)               |      int(3452342)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #4"  |      string(10) "Must Be #1"
  }                            |    }
  [1]=>                        |    [1]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(3333333)               |      int(5867867)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #3"  |      string(10) "Must Be #2"
  }                            |    }
  [2]=>                        |    [2]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(1231233)               |      int(7867867)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #4"    |      string(10) "Must Be #3"
  }                            |    }
  [3]=>                        |    [3]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(1111111)               |      int(1231233)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #1"  |      string(10) "Must Be #4"
  }                            |    }
  [4]=>                        |    [4]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(5867867)               |      int(2222222)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #2"    |      string(12) "Dont Care #2"
  }                            |    }
  [5]=>                        |    [5]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(2222222)               |      int(1111111)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #2"  |      string(12) "Dont Care #1"
  }                            |    }
  [6]=>                        |    [6]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(3452342)               |      int(3333333)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #1"    |      string(12) "Dont Care #3"
  }                            |    }
  [7]=>                        |    [7]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(7867867)               |      int(4444444)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #3"    |      string(12) "Dont Care #4"
  }                            |    }
}                              |  }
14
Salman A

Vous devez définir votre propre fonction de comparaison et utiliser usort ou uasort si vous souhaitez conserver l'association d'index.

4
Nev Stokes

Les autres réponses qui utilisent des méthodes avec des appels itérés de array_search() ne sont pas aussi efficaces qu'elles peuvent l'être. En restructurant/retournant le tableau de recherche "order", vous pouvez complètement omettre tous les appels array_search() - ce qui rend votre tâche beaucoup plus efficace et brève. Je vais utiliser "l'opérateur de vaisseau spatial" le plus moderne (<=>), mais les techniques antérieures fonctionneront de la même manière pour la ligne de comparaison.

Méthode n ° 1 - usort lorsque toutes les valeurs id existent dans $order ( Demo )

$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use($order){
    return $order[$a['id']]<=>$order[$b['id']];
    // when comparing ids 3452342 & 1231233, the actual comparison is 0 vs 3
});
// uasort() if you want to preserve keys

var_export($array);

Méthode n ° 2 - usort lorsque certaines valeurs id n'existent pas dans $order ( Demo )
* remarque, isset() est un appel moins coûteux que array_search()

$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$outlier=1+max($order);
// generating $outlier=4
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>'foo','title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>'bar','title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use(&$order,$outlier){  // make $order modifiable with &
    if(!isset($order[$a['id']])){$order[$a['id']]=$outlier;}  // update lookup array with [id]=>[outlier number]
    if(!isset($order[$b['id']])){$order[$b['id']]=$outlier;}  // and again
    return $order[$a['id']]<=>$order[$b['id']];
});

var_export($array);

Méthode alternative n ° 2 - usort lorsque certaines valeurs id n'existent pas dans $order

... J'aimerais également mentionner que dans certains cas, éviter le double appel itéré de isset() peut être moins attrayant que de préparer complètement le tableau $order avant d'appeler usort().

Cette ligne unique garantira qu'il ne manque aucune valeur id, éliminant ainsi le besoin de rien d'autre que la ligne de comparaison à l'intérieur de la fonction de tri. ( Full Snippet Demo )

$order=array_replace(array_fill_keys(array_column($array,'id'),$outlier),$order);
3
mickmackusa

@salathe Pour ceux d'entre vous qui ont du mal à comprendre ce que fait l'usager de Salathe: 

Chaque élément de $ array est un "champion" dans un tournoi qui doit être au début d'un nouveau tableau (sauf qu'au lieu d'être numéro un, ils veulent être numéro 0).

$ a est le champion local et $ b le champion adverse dans un match. 

$ pos_a et $ pos_b du rappel sont les attributs qui seront utilisés dans la lutte pour les champions a et b. Dans ce cas, cet attribut est l'indice de l'id des champions dans l'ordre $. 

Ensuite, il y a la bagarre au retour. Nous regardons maintenant pour voir s'il est préférable d'avoir plus ou moins d'attribut. Lors d'une bataille d'usurpation, le champion local veut un nombre négatif pour pouvoir être plus tôt dans le tableau. Le champion à l'extérieur veut un nombre positif. Et devrait-il y avoir un 0 c'est une cravate. 

Donc, en suivant cette analogie lorsque l'attribut champions à l'extérieur (index par ordre) est soustrait de l'attribut équipes à domicile, plus l'attribut champions à l'extérieur est grand, moins il est probable qu'il obtiendra un nombre positif. Cependant, si vous inversiez la manière dont les attributs sont utilisés, l'attribut du champion local est maintenant soustrait de celui du champion absent. Dans ce cas, un nombre plus élevé pour le champion en déplacement est plus susceptible de lui faire terminer le match par un nombre positif.

Le code ressemblerait à ceci:

remarque: le code est exécuté plusieurs fois, comme dans un vrai tournoi qui compte beaucoup de batailles pour décider qui sera le premier (c.-à-d. 0/début du tableau).

//tournament with goal to be first in array
    usort($champions, function ($home, $away) use ($order) {
        $home_attribute = array_search($a['id'], $order);
        $away_attribute = array_search($b['id'], $order);
        //fight with desired outcome for home being negative and away desiring positive
        return $home_attribute - $away_attribute;
    });
1
Jason Basanese

Sans sorte, vous pouvez aussi l'obtenir.

S'il n'y a pas d'identifiant en double;

<?php

    $order = array(3452342, 5867867, 7867867, 1231233);
    $array = array(
        array('id' => 7867867, 'title' => 'Some Title'),
        array('id' => 3452342, 'title' => 'Some Title'),
        array('id' => 1231233, 'title' => 'Some Title'),
        array('id' => 5867867, 'title' => 'Some Title'),
    );

    $order = array_flip($order);
    $array = array_column($array,null,"id");
    $result = array_replace($order,$array);
    var_dump(array_values($result));

Avec un identifiant en double,

<?php

    $order = array(3452342, 5867867, 7867867, 1231233);
    $array = array(
        array('id' => 7867867, 'title' => 'Some Title'),
        array('id' => 3452342, 'title' => 'Some Title'),
        array('id' => 1231233, 'title' => 'Some Title'),
        array('id' => 5867867, 'title' => 'Some Title'),
    );

    $order_dict = array_flip($order);
    $order_dict = array_combine($order,array_fill(0,count($order),[]));
    foreach($array as $item){
        $order_dict[$item["id"]][] = $item;
    }
    //$order_dict = array_filter($order_dict);  // if there is empty item on some id in $order array
    $result = [];
    foreach($order_dict as $items){
        foreach($items as $item){
            $result[] = $item;
        }
    }
    var_dump($result);
0
Kris Roofe