web-dev-qa-db-fra.com

Comment trier un tableau multidimensionnel dans PHP

J'ai des données CSV chargées dans un tableau multidimensionnel. De cette manière, 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;
}

Je dois pouvoir spécifier une colonne à trier pour qu'elle réorganise les lignes. Une des colonnes contient des informations sur la date au format Y-m-d H:i:s et je voudrais pouvoir effectuer un tri avec la date la plus récente à la première ligne.

197
Melikoth

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, extrayez simplement la colonne à trier. Pas besoin de la boucle:

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

Introduction à: une solution très générale pour PHP 5.3+

Je voudrais ajouter ici ma propre solution, car elle offre des fonctionnalités que d’autres réponses ne proposent pas.

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

  1. C'est réutilisable : vous spécifiez la colonne de tri sous forme de 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). Des colonnes supplémentaires sont utilisées comme séparateurs entre 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 date), vous pouvez également spécifier comment les convertir. éléments à une valeur pouvant être directement comparée (par exemple, une instance DateTime.).
  5. C'est associatif si vous voulez : ce code prend en charge le tri des éléments, mais vous sélectionnez le tri réel fonction (usort ou uasort).
  6. Enfin, il n’utilise pas _array_multisort_: alors que _array_multisort_ est pratique, cela 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 simplement prohibitif si votre ensemble de données est volumineux.

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

Comment utiliser

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

_$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'),
);
_

Les bases

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 pour usort ou 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 l’élément name que vous feriez

_usort($data, make_comparer('name'));
_

Voir en action.

La clé peut également être un nombre si les éléments sont des tableaux indexés numériquement. Pour l'exemple dans la question, ce serait

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

Voir en action.

Plusieurs colonnes de tri

Vous pouvez spécifier plusieurs colonnes de tri en transmettant des paramètres supplémentaires à _make_comparer_. Par exemple, pour trier par "numéro" puis par la colonne indexée à zéro:

_usort($data, make_comparer('number', 0));
_

Voir en action.

Fonctionnalités avancées

Des fonctionnalités plus avancées sont disponibles si vous spécifiez une colonne de tri sous la forme d'un tableau plutôt que d'une simple chaîne. Ce tableau doit être indexé numériquement et doit contenir les éléments suivants:

_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.

Tri inversé

Pour trier par nom décroissant:

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

Voir en action.

Pour trier par numéro décroissant puis par nom décroissant:

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

Voir en action.

Projections personnalisées

Dans certains cas, vous devrez peut-être trier en fonction d'une colonne dont les valeurs ne permettent pas le tri. La colonne "anniversaire" de l'exemple de jeu de données correspond à cette description: il est illogique de comparer les anniversaires sous forme de chaînes (car, par exemple, "01/01/1980" vient avant "10/10/1970"). Dans ce cas, nous voulons spécifier comment projeter les données réelles dans un formulaire qui peut être comparé directement à la sémantique souhaitée.

Les projections peuvent être spécifiées sous n'importe quel type de appelable : sous forme de chaînes, de tableaux ou de fonctions anonymes. Une projection est supposée accepter un argument et renvoyer sa forme projetée.

Il convient de noter que si les projections ressemblent aux fonctions de comparaison personnalisées utilisées avec usort et à 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 à _make_comparer_.

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

_usort($data, make_comparer('birthday'));
_

Voir en action.

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

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

Voir en action.

C'est le bon ordre que nous voulions.

Les projections peuvent réaliser beaucoup plus de choses. Par exemple, un moyen rapide d'obtenir un tri insensible à la casse consiste à utiliser strtolower comme projection.

Cela dit, je devrais é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 à l'avance, puis de trier sans utiliser de projection, bien 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 décroissant, puis par ordre croissant d’anniversaire:

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

Voir en action.

341
Jon

Avec sort . 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 première colonne:

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

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

Voici une autre approche utilisant uasort () et une fonction de rappel anonyme (fermeture). 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' ) );
10
feeela

Je sais que cela fait 2 ans que cette question a été posée et répondue, mais voici une autre fonction qui trie un tableau à deux dimensions. Il accepte un nombre variable d'arguments, ce qui vous permet de transmettre plus d'une clé (c.-à-d. Un nom de colonne) à 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

7
Mike C
function cmp($a, $b)
{
$p1 = $a['price'];
$p2 = $b['price'];
return (float)$p1 > (float)$p2;
}
uasort($my_array, "cmp");

http://qaify.com/sort-an-arar-of-associative-arrays-by-value-of-given-key-in-php/

6
Kamal

La fonction "Usort" est votre réponse.
http://php.net/usort

3
Jan Hančič

Voici une classe php4/php5 qui va trier un ou plusieurs champs:

// a sorter class
//  php4 and php5 compatible
class Sorter {

  var $sort_fields;
  var $backwards = false;
  var $numeric = false;

  function sort() {
    $args = func_get_args();
    $array = $args[0];
    if (!$array) return array();
    $this->sort_fields = array_slice($args, 1);
    if (!$this->sort_fields) return $array();

    if ($this->numeric) {
      usort($array, array($this, 'numericCompare'));
    } else {
      usort($array, array($this, 'stringCompare'));
    }
    return $array;
  }

  function numericCompare($a, $b) {
    foreach($this->sort_fields as $sort_field) {
      if ($a[$sort_field] == $b[$sort_field]) {
        continue;
      }
      return ($a[$sort_field] < $b[$sort_field]) ? ($this->backwards ? 1 : -1) : ($this->backwards ? -1 : 1);
    }
    return 0;
  }

  function stringCompare($a, $b) {
    foreach($this->sort_fields as $sort_field) {
      $cmp_result = strcasecmp($a[$sort_field], $b[$sort_field]);
      if ($cmp_result == 0) continue;

      return ($this->backwards ? -$cmp_result : $cmp_result);
    }
    return 0;
  }
}

/////////////////////
// usage examples

// some starting data
$start_data = array(
  array('first_name' => 'John', 'last_name' => 'Smith', 'age' => 10),
  array('first_name' => 'Joe', 'last_name' => 'Smith', 'age' => 11),
  array('first_name' => 'Jake', 'last_name' => 'Xample', 'age' => 9),
);

// sort by last_name, then first_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'last_name', 'first_name'));

// sort by first_name, then last_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'first_name', 'last_name'));

// sort by last_name, then first_name (backwards)
$sorter = new Sorter();
$sorter->backwards = true;
print_r($sorter->sort($start_data, 'last_name', 'first_name'));

// sort numerically by age
$sorter = new Sorter();
$sorter->numeric = true;
print_r($sorter->sort($start_data, 'age'));
2
Devon

Avant de pouvoir exécuter la classe TableSorter, j'avais créé une fonction basée sur ce que Shinhan avait fourni.

function sort2d_bycolumn($array, $column, $method, $has_header)
  {
  if ($has_header)  $header = array_shift($array);
  foreach ($array as $key => $row) {
    $narray[$key]  = $row[$column]; 
    }
  array_multisort($narray, $method, $array);
  if ($has_header) array_unshift($array, $header);
  return $array;
  }
  • $ array est le tableau MD que vous souhaitez trier.
  • $ colonne est la colonne que vous souhaitez trier.
  • La méthode $ indique comment vous souhaitez effectuer le tri, par exemple SORT_DESC.
  • $ has_header est défini sur true si la première ligne contient des valeurs d'en-tête que vous ne souhaitez pas trier.
0
Melikoth

J'ai essayé plusieurs réponses populaires array_multisort () et usort () et aucune d'entre elles n'a fonctionné pour moi. Les données sont simplement mélangées et le code est illisible. Voici une solution rapide et sale. AVERTISSEMENT: Utilisez-le uniquement si vous êtes certain qu'un délimiteur non autorisé ne reviendra pas vous hanter plus tard!

Disons que chaque ligne de votre tableau multi ressemble à: nom, stuff1, stuff2:

// Sort by name, pull the other stuff along for the ride
foreach ($names_stuff as $name_stuff) {
    // To sort by stuff1, that would be first in the contatenation
    $sorted_names[] = $name_stuff[0] .','. name_stuff[1] .','. $name_stuff[2];
}
sort($sorted_names, SORT_STRING);

Vous avez besoin de vos affaires en ordre alphabétique?

foreach ($sorted_names as $sorted_name) {
    $name_stuff = explode(',',$sorted_name);
    // use your $name_stuff[0] 
    // use your $name_stuff[1] 
    // ... 
}

Oui, c'est sale. Mais super facile, vous ne ferez pas exploser votre tête.

0
PJ Brunet