Come ordinare una matrice multidimensionale in PHP



4 Answers

Presentazione: una soluzione molto generalizzata per PHP 5.3+

Mi piacerebbe aggiungere la mia soluzione qui, poiché offre funzionalità che altre risposte no.

In particolare, i vantaggi di questa soluzione includono:

  1. È riutilizzabile : si specifica la colonna di ordinamento come variabile invece di codificarla.
  2. È flessibile : puoi specificare più colonne di ordinamento (quante ne vuoi) - le colonne aggiuntive vengono utilizzate come tie-break tra gli oggetti che inizialmente si confrontano in modo uguale.
  3. È reversibile : puoi specificare che l'ordinamento debba essere invertito, individualmente per ogni colonna.
  4. È estensibile : se il set di dati contiene colonne che non possono essere confrontate in modo "stupido" (ad es. Stringhe di date), puoi anche specificare come convertire questi elementi in un valore che può essere confrontato direttamente (ad esempio un'istanza DateTime ).
  5. È associativo se lo desideri : questo codice si occupa degli elementi di ordinamento, ma tu selezioni la funzione di ordinamento reale ( usort o uasort ).
  6. Infine, non usa array_multisort : mentre array_multisort è conveniente, dipende dalla creazione di una proiezione di tutti i dati di input prima dell'ordinamento. Questo consuma tempo e memoria e potrebbe 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 fornirò 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 l'ordinamento desiderato e restituisce una funzione che si suppone utilizzi come argomento per usort o uasort .

Il caso d'uso più semplice è quello di passare la chiave che desideri utilizzare per confrontare gli elementi di dati. Ad esempio, per ordinare $data dall'elemento del name si farebbe

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

Guardalo in azione .

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

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

Guardalo in azione .

Più colonne di ordinamento

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

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

Guardalo in azione .

Funzionalità avanzate

Sono disponibili funzionalità più avanzate se si specifica una colonna di ordinamento come matrice anziché come stringa semplice. Questo array dovrebbe essere indicizzato numericamente e 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 usare queste funzionalità.

Ordinamento inverso

Per ordinare per nome decrescente:

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

Guardalo in azione .

Per ordinare per numero decrescente e quindi per nome decrescente:

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

Guardalo in azione .

Proiezioni personalizzate

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

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

Va notato che mentre le proiezioni sono simili alle funzioni di confronto personalizzate utilizzate con usort e family, 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'));

Guardalo in azione .

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

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

Guardalo in azione .

Questo è l'ordine corretto che volevamo.

Ci sono molte altre cose che le proiezioni possono ottenere. Ad esempio, un modo rapido per ottenere un ordinamento senza distinzione tra maiuscole e minuscole consiste nell'usare strtolower come proiezione.

Detto questo, dovrei anche menzionare che è meglio non usare le proiezioni se il tuo set di dati è grande: in quel caso sarebbe molto più veloce proiettare tutti i dati manualmente in anticipo e poi ordinare senza usare una proiezione, anche se così facendo si scambi maggiore utilizzo della memoria per una maggiore velocità di ordinamento.

Infine, ecco un esempio che utilizza tutte le funzionalità: prima ordina per numero decrescente, poi per compleanno crescente:

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

Guardalo in azione .

Question

Questa domanda ha già una risposta qui:

Ho caricato i dati CSV 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 che riorganizzi le righe. Una delle colonne contiene informazioni sulla data nel formato di Ymd H:i:s e mi piacerebbe essere in grado di ordinare con la data più recente della prima riga.




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 a array_multisort () e usort () e nessuna ha funzionato per me. I dati diventano confusi e il codice è illeggibile. Ecco una soluzione rapida e sporca. ATTENZIONE: usa questo solo se sei sicuro che un delimitatore non può tornare a perseguitarti più tardi!

Diciamo che ogni riga del tuo multi array assomiglia a: name, 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);

Hai bisogno del tuo materiale 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 ti farà esplodere la testa.




Ordinamento di più righe utilizzando una chiusura

Ecco un altro approccio usando uasort () e una funzione di callback anonimo (chiusura). Ho usato quella funzione regolarmente. Richiesto PHP 5.3 : niente 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' ) );





Related