Come faccio a ordinare un array multidimensionale in PHP php


Answers

Introduzione: una soluzione molto generalizzata per PHP 5.3+

Vorrei aggiungere la mia soluzione qui, in quanto offre caratteristiche che altre risposte non lo fanno.

In particolare, i vantaggi di questa soluzione includono:

  1. È riutilizzabile : si specifica la colonna di ordinamento come una variabile invece di codificarlo.
  2. È flessibile : è possibile specificare più colonne di ordinamento (per quanti desiderate) - colonne aggiuntive vengono utilizzate come tiebreakers tra gli elementi che confrontano inizialmente gli elementi uguali.
  3. È reversibile : è possibile specificare che il tipo dovrebbe essere invertito - individualmente per ogni colonna.
  4. È estensibile : se il set di dati contiene colonne che non possono essere confrontate in modo "muto" (ad esempio stringhe di date) è possibile specificare come convertire tali elementi in un valore che può essere confrontato direttamente (ad esempio un'istanza DateTime ).
  5. È associativo se si desidera : questo codice si occupa di ordinare gli elementi, ma si seleziona la funzione di ordinamento effettivo ( usort o uasort ).
  6. Infine, non utilizza array_multisort : mentre array_multisort è conveniente, dipende dalla creazione di una proiezione di tutti i dati di input prima dell'ordinazione. Questo consuma tempo e memoria e può essere semplicemente proibitivo se il tuo set di dati è grande.

Il codice

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

Come usare

In questa sezione troverò i collegamenti che ordinano questo set di dati di esempio:

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

Le basi

La funzione make_comparer accetta un numero variabile di argomenti che definiscono il tipo desiderato e restituisce una funzione che si suppone per l'uso come argomento usort o uasort .

Il caso di utilizzo più semplice è quello di passare nella chiave che si desidera utilizzare per confrontare le voci di dati. Ad esempio, per ordinare $data per la voce di name si desidera fare

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

Vedere in azione .

La chiave può anche essere un numero se gli elementi sono matrici numericamente indicizzate. Per l'esempio nella domanda, questo sarebbe

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

Vedere in azione .

Colonne di ordinamento multiplo

È possibile specificare più colonne di ordinamento passando parametri aggiuntivi a make_comparer . Ad esempio, per ordinare per "numero" e quindi per la colonna indicizzata da zero:

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

Vedere in azione .

Funzionalità avanzate

Le funzioni più avanzate sono disponibili se si specifica una colonna di ordinamento come un array anziché una stringa semplice. Questa matrice dovrebbe essere indicizzata numericamente e deve contenere questi elementi:

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

Vediamo come possiamo utilizzare queste funzionalità.

Ordine inverso

Per ordinare per nome discendente:

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

Vedere in azione .

Per ordinare per numero discendente e successivamente per discendenza di nome:

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

Vedere in azione .

Proiezioni personalizzate

In alcuni scenari è necessario ordinare per una colonna i cui valori non si prestano bene all'ordinazione. La colonna "compleanno" nel set di dati di esempio si adatta a questa descrizione: non ha senso confrontare i compleanni come stringhe (perché per esempio "01/01/1980" viene prima "10/10/1970"). In questo caso vogliamo specificare come proiettare i dati effettivi in ​​una forma che può essere confrontata direttamente con la semantica desiderata.

Le proiezioni possono essere specificate come qualsiasi tipo di richiamo : come stringhe, matrici o funzioni anonime. Si suppone che una proiezione accetta un argomento e restituisca la forma proiettata.

Va notato che mentre le proiezioni sono simili alle funzioni di confronto personalizzate utilizzate con usort e la famiglia, sono più semplici (è sufficiente convertire un valore in un altro) e sfruttare tutte le funzionalità già make_comparer in make_comparer .

Ordiniamo il set di dati di esempio senza una proiezione e vediamo cosa succede:

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

Vedere in azione .

Questo non era il risultato desiderato. Ma possiamo usare date_create come una proiezione:

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

Vedere in azione .

Questo è l'ordine corretto che volevamo.

Ci sono molte altre cose che le proiezioni possono raggiungere. Ad esempio, un modo rapido per ottenere un tipo di casi insensibili è quello di utilizzare lo strtolower come una proiezione.

Detto questo, devo anche menzionare che è meglio non utilizzare proiezioni se il tuo set di dati è grande: in questo caso sarebbe molto più veloce proiettare tutti i tuoi dati manualmente prima e poi ordinare senza utilizzare una proiezione, anche se così facendo Maggiore utilizzo della memoria per una velocità di ordinamento più veloce.

Infine, qui è un esempio che utilizza tutte le funzionalità: prima ordina per numero discendente, poi per nascita ascendente:

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

Vedere in azione .

Question

Questa domanda ha già una risposta:

Ho i dati CSV caricati in un array multidimensionale. In questo modo ogni "riga" è un record e ogni "colonna" contiene lo stesso tipo di dati. Sto usando la funzione qui sotto per caricare il mio file 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;
}

Devo essere in grado di specificare una colonna da ordinare in modo da riorganizzare le righe. Una delle colonne contiene informazioni di data nel formato di Ymd H:i:s e vorrei essere in grado di ordinare con la data più recente che è la prima fila.




Ordinamento a più fila utilizzando una chiusura

Ecco un altro approccio che utilizza uasort () e una funzione di richiamata anonima (chiusura). Ho usato questa funzione regolarmente. PHP 5.3 richiesto - non più dipendenze!

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






Ecco una classe php4 / php5 che ordinerà uno o più campi:

// 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'));



Ho provato diverse risposte popolari array_multisort () e usort () e nessuno di loro ha funzionato per me. I dati vengono solo annullati e il codice è illeggibile. Ecco una soluzione veloce e sporca. AVVERTENZA: usa questo solo se sei sicuro che un delimitatore canaglia non tornerà a perseguirli più tardi!

Diciamo che ogni riga del tuo multi array sembra: nome, roba1, roba2:

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

Hai bisogno di restituire la tua roba in ordine alfabetico?

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

Sì, è sporco. Ma super facile, non farà esplodere la tua testa.