204 votes

Comment trier un tableau multidimensionnel en PHP ?

J'ai des données CSV chargées dans un tableau multidimensionnel. De cette façon, chaque "ligne" est un enregistrement et chaque "colonne" contient le même type de données. J'utilise la fonction ci-dessous pour charger mon fichier CSV.

function f_parse_csv($file, $longest, $delimiter)
{
  $mdarray = array();
  $file    = fopen($file, "r");
  while ($line = fgetcsv($file, $longest, $delimiter))
  {
    array_push($mdarray, $line);
  }
  fclose($file);
  return $mdarray;
}

J'ai besoin de pouvoir spécifier une colonne à trier afin de réorganiser les rangées. L'une des colonnes contient des informations sur la date au format suivant Y-m-d H:i:s et je voudrais pouvoir trier en plaçant la date la plus récente en première ligne.

7 votes

(2 ans plus tard...) Si vous triez des dates stockées sous forme de chaînes de caractères, vous devrez peut-être utiliser strtotime [1]. docs.php.net/manual/fen/function.strtotime.php

0 votes

@deceze, stackoverflow.com/q/1597736/1709587 semble être une meilleure cible de dupe pour moi. C'est une copie plus exacte et les réponses vont donc plus vite au but que les vôtres. stackoverflow.com/a/17364128/1709587 tout en ayant collectivement le même niveau de détail. Que dites-vous de changer de cible ? (Divulgation : je suis peut-être partial en tant qu'auteur d'une des réponses à la cible dupe que je propose).

0 votes

349voto

Jon Points 194296

Présentation : une solution très généralisée pour PHP 5.3+.

J'aimerais ajouter ma propre solution ici, car elle offre des fonctionnalités que les autres réponses n'offrent pas.

Plus précisément, les avantages de cette solution sont les suivants :

  1. C'est réutilisable : vous spécifiez la colonne de tri comme une variable au lieu de la coder en dur.
  2. C'est flexible Vous pouvez spécifier plusieurs colonnes de tri (autant que vous le souhaitez). Les colonnes supplémentaires sont utilisées pour départager les éléments qui se comparent initialement.
  3. C'est réversible vous pouvez spécifier que le tri doit être inversé -- individuellement pour chaque colonne.
  4. C'est extensible si l'ensemble de données contient des colonnes qui ne peuvent pas être comparées de manière "muette" (par exemple, des chaînes de dates), vous pouvez également spécifier comment convertir ces éléments en une valeur qui peut être directement comparée (par exemple, une chaîne de dates). DateTime instance).
  5. C'est associatif si vous voulez ce code s'occupe de trier les éléments, mais vous sélectionner la fonction de tri réelle ( usort o uasort ).
  6. Enfin, il n'utilise pas array_multisort : tandis que array_multisort est pratique, elle dépend de la création d'une projection de toutes vos données d'entrée avant le tri. Cela consomme du temps et de la mémoire et peut être tout simplement prohibitif si votre ensemble de données est important.

Le code

function make_comparer() {
    // Normalize criteria up front so that the comparer finds everything tidy
    $criteria = func_get_args();
    foreach ($criteria as $index => $criterion) {
        $criteria[$index] = is_array($criterion)
            ? array_pad($criterion, 3, null)
            : array($criterion, SORT_ASC, null);
    }

    return function($first, $second) use (&$criteria) {
        foreach ($criteria as $criterion) {
            // How will we compare this round?
            list($column, $sortOrder, $projection) = $criterion;
            $sortOrder = $sortOrder === SORT_DESC ? -1 : 1;

            // If a projection was defined project the values now
            if ($projection) {
                $lhs = call_user_func($projection, $first[$column]);
                $rhs = call_user_func($projection, $second[$column]);
            }
            else {
                $lhs = $first[$column];
                $rhs = $second[$column];
            }

            // Do the actual comparison; do not return if equal
            if ($lhs < $rhs) {
                return -1 * $sortOrder;
            }
            else if ($lhs > $rhs) {
                return 1 * $sortOrder;
            }
        }

        return 0; // tiebreakers exhausted, so $first == $second
    };
}

Mode d'emploi

Tout au long de cette section, je fournirai des liens permettant de trier cet ensemble de données types :

$data = array(
    array('zz', 'name' => 'Jack', 'number' => 22, 'birthday' => '12/03/1980'),
    array('xx', 'name' => 'Adam', 'number' => 16, 'birthday' => '01/12/1979'),
    array('aa', 'name' => 'Paul', 'number' => 16, 'birthday' => '03/11/1987'),
    array('cc', 'name' => 'Helen', 'number' => 44, 'birthday' => '24/06/1967'),
);

L'essentiel

La fonction make_comparer accepte un nombre variable d'arguments qui définissent le tri souhaité et renvoie une fonction que vous êtes censé utiliser comme argument de la fonction usort o uasort .

Le cas d'utilisation le plus simple consiste à transmettre la clé que vous souhaitez utiliser pour comparer des éléments de données. Par exemple, pour trier $data par le name ce que vous feriez

usort($data, make_comparer('name'));

Voyez-le en action .

La clé peut également être un nombre si les éléments sont des tableaux à indexation numérique. Dans l'exemple de la question, il s'agirait de

usort($data, make_comparer(0)); // 0 = first numerically indexed column

Voyez-le en action .

Colonnes de tri multiples

Vous pouvez spécifier plusieurs colonnes de tri en passant des paramètres supplémentaires à la commande make_comparer . Par exemple, pour trier par "nombre" et ensuite par la colonne indexée zéro :

usort($data, make_comparer('number', 0));

Voyez-le en action .

Fonctions avancées

Des fonctionnalités plus avancées sont disponibles si vous spécifiez une colonne de tri sous forme de tableau au lieu d'une simple chaîne. Ce tableau doit être indexé numériquement, et doit contenir ces éléments :

0 => the column name to sort on (mandatory)
1 => either SORT_ASC or SORT_DESC (optional)
2 => a projection function (optional)

Voyons comment nous pouvons utiliser ces fonctionnalités.

Triage inverse

Pour trier par nom en ordre décroissant :

usort($data, make_comparer(['name', SORT_DESC]));

Voir en action .

Pour trier par numéro en ordre décroissant, puis par nom en ordre décroissant :

usort($data, make_comparer(['number', SORT_DESC], ['name', SORT_DESC]));

Voyez-le en action .

Projections personnalisées

Dans certains scénarios, vous pouvez avoir besoin de trier par une colonne dont les valeurs ne se prêtent pas bien au tri. La colonne "anniversaire" de l'ensemble de données d'exemple correspond à cette description : il n'est pas logique de comparer les anniversaires sous forme de chaînes de caractères (parce que, par exemple, "01/01/1980" vient avant "10/10/1970"). Dans ce cas, nous voulons spécifier comment projet les données réelles dans un formulaire qui puede être comparé directement avec la sémantique souhaitée.

Les projections peuvent être spécifiées comme n'importe quel type de Appelable comme des chaînes de caractères, des tableaux, ou des fonctions anonymes. Une projection est supposée accepter un argument et retourner sa forme projetée.

Il convient de noter que, bien que les projections soient similaires aux fonctions de comparaison personnalisées utilisées avec le logiciel usort et de la famille, elles sont plus simples (il suffit de convertir une valeur en une autre) et tirent parti de toutes les fonctionnalités déjà intégrées dans le module make_comparer .

Trions l'ensemble de données de l'exemple sans projection et voyons ce qui se passe :

usort($data, make_comparer('birthday'));

Voyez-le en action .

Ce n'était pas le résultat souhaité. Mais nous pouvons utiliser date_create comme une projection :

usort($data, make_comparer(['birthday', SORT_ASC, 'date_create']));

Voyez-le en action .

C'est l'ordre correct que nous voulions.

Il y a beaucoup plus de choses que les projections peuvent réaliser. Par exemple, un moyen rapide d'obtenir un tri insensible à la casse est d'utiliser strtolower comme une projection.

Cela dit, je dois également mentionner qu'il est préférable de ne pas utiliser de projections si votre ensemble de données est volumineux : dans ce cas, il serait beaucoup plus rapide de projeter toutes vos données manuellement dès le départ, puis de trier sans utiliser de projection, bien que cela implique une utilisation accrue de la mémoire pour une vitesse de tri plus rapide.

Enfin, voici un exemple qui utilise toutes les fonctionnalités : il trie d'abord par numéro en ordre décroissant, puis par date d'anniversaire en ordre croissant :

usort($data, make_comparer(
    ['number', SORT_DESC],
    ['birthday', SORT_ASC, 'date_create']
));

Voyez-le en action .

0 votes

@Jon Lorsque je vérifie votre dernière action (avec le callback date_create ), les anniversaires sont dans le mauvais ordre. Pouvez-vous confirmer ? Je reçois 01/12/1979 , 03/11/1987 , 12/03/1980 y 24/06/1967 . Si j'utilise strtotime Au lieu de cela, j'obtiens un bon résultat. Je suppose que c'est DateTime qui est cassé.

0 votes

@DavidBélanger : Quelle URL exactement ? Tous les exemples fonctionnent correctement à la fois sur ideone.com et sur ma machine locale.

0 votes

Hey, @Jon, je ne sais pas comment vous contacter mais par le biais d'un commentaire. Je vois que vous avez créé le tag "tableau multidimensionnel", mais nous avons également besoin de "données multidimensionnelles" pour un terme similaire dans la visualisation de données et les disciplines connexes. Je ne peux pas créer de tags, puis-je vous demander d'en créer un, si vous trouvez qu'il a du sens ?

219voto

Shinhan Points 1417

Vous pouvez utiliser array_multisort()

Essayez quelque chose comme ça :

foreach ($mdarray as $key => $row) {
    // replace 0 with the field's index/key
    $dates[$key]  = $row[0];
}

array_multisort($dates, SORT_DESC, $mdarray);

Pour PHP >= 5.5.0, il suffit d'extraire la colonne à trier. La boucle n'est pas nécessaire :

array_multisort(array_column($mdarray, 0), SORT_DESC, $mdarray);

9 votes

Ainsi, dans cet exemple, $mdarray pourrait être un tableau à deux dimensions, comme un tableau d'enregistrements de base de données. Dans cet exemple, 0 est l'index de la colonne 'date' dans chaque enregistrement (ou ligne). Vous construisez donc le tableau $dates (en fait le même tableau, mais avec seulement cette colonne), et vous demandez à la fonction array_multisort de trier $mdarray en fonction des valeurs de cette colonne particulière.

9 votes

Pour plus de clarté, vous pourriez ajouter au début de cet exemple $dates = array();

1 votes

Array_multisort doit-il fonctionner avec des tableaux associatifs (en changeant $row[0] a $row['whatever'] ? C'est impossible. Après avoir changé mon tableau en numérique, la fonction a fonctionné comme prévu.

34voto

troelskn Points 51966

Con usort . Voici une solution générique, que vous pouvez utiliser pour différentes colonnes :

class TableSorter {
  protected $column;
  function __construct($column) {
    $this->column = $column;
  }
  function sort($table) {
    usort($table, array($this, 'compare'));
    return $table;
  }
  function compare($a, $b) {
    if ($a[$this->column] == $b[$this->column]) {
      return 0;
    }
    return ($a[$this->column] < $b[$this->column]) ? -1 : 1;
  }
}

Pour trier par la première colonne :

$sorter = new TableSorter(0); // sort by first column
$mdarray = $sorter->sort($mdarray);

0 votes

Je reçois Parse error : parse error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' sur la deuxième ligne de cette classe.

3 votes

Remplacez "protrected" par "var" et "__construct" par "TableSorter", et cela fonctionnera en PHP4. Notez cependant que PHP4 n'est plus utilisé.

0 votes

J'ai configuré PHP en v5, je ne savais pas qu'il fonctionnait en v4 par défaut. Après l'avoir examiné pendant un certain temps, je pense que je comprends comment le modifier pour différents types d'applications.

11voto

feeela Points 9901

Triage de plusieurs lignes à l'aide d'une fermeture

Voici une autre approche utilisant uasort() et une fonction de rappel anonyme (closure). J'ai utilisé cette fonction régulièrement. PHP 5.3 requis - plus de dépendances !

/**
 * Sorting array of associative arrays - multiple row sorting using a closure.
 * See also: http://the-art-of-web.com/php/sortarray/
 *
 * @param array $data input-array
 * @param string|array $fields array-keys
 * @license Public Domain
 * @return array
 */
function sortArray( $data, $field ) {
    $field = (array) $field;
    uasort( $data, function($a, $b) use($field) {
        $retval = 0;
        foreach( $field as $fieldname ) {
            if( $retval == 0 ) $retval = strnatcmp( $a[$fieldname], $b[$fieldname] );
        }
        return $retval;
    } );
    return $data;
}

/* example */
$data = array(
    array( "firstname" => "Mary", "lastname" => "Johnson", "age" => 25 ),
    array( "firstname" => "Amanda", "lastname" => "Miller", "age" => 18 ),
    array( "firstname" => "James", "lastname" => "Brown", "age" => 31 ),
    array( "firstname" => "Patricia", "lastname" => "Williams", "age" => 7 ),
    array( "firstname" => "Michael", "lastname" => "Davis", "age" => 43 ),
    array( "firstname" => "Sarah", "lastname" => "Miller", "age" => 24 ),
    array( "firstname" => "Patrick", "lastname" => "Miller", "age" => 27 )
);

$data = sortArray( $data, 'age' );
$data = sortArray( $data, array( 'lastname', 'firstname' ) );

8voto

Mike C Points 1010

Je sais qu'il y a deux ans que cette question a été posée et qu'on y a répondu, mais voici une autre fonction qui trie un tableau à deux dimensions. Elle accepte un nombre variable d'arguments, ce qui vous permet de passer plus d'une clé (c'est-à-dire le nom de la colonne) pour trier. PHP 5.3 requis.

function sort_multi_array ($array, $key)
{
  $keys = array();
  for ($i=1;$i<func_num_args();$i++) {
    $keys[$i-1] = func_get_arg($i);
  }

  // create a custom search function to pass to usort
  $func = function ($a, $b) use ($keys) {
    for ($i=0;$i<count($keys);$i++) {
      if ($a[$keys[$i]] != $b[$keys[$i]]) {
        return ($a[$keys[$i]] < $b[$keys[$i]]) ? -1 : 1;
      }
    }
    return 0;
  };

  usort($array, $func);

  return $array;
}

Essayez-le ici : http://www.exorithm.com/algorithm/view/sort_multi_array

2 votes

Les 3 premières lignes de la fonction pourraient-elles être remplacées par $keys = func_get_args(); array_unshift($keys); ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X