Sortuj tablicę wielowymiarową według wielu kluczy

83

Próbuję posortować tablicę wielowymiarową według wielu kluczy i nie mam pojęcia, od czego zacząć. Spojrzałem na uasort, ale nie byłem pewien, jak napisać funkcję dla tego, czego potrzebuję.

Muszę posortować według stanu, następnie typ_zdarzenia, a następnie datę.

Moja tablica wygląda tak:

    Array
(
    [0] => Array
        (
            [ID] => 1
            [title] => Boring Meeting
            [date_start] => 2010-07-30
            [time_start] => 06:45:PM
            [time_end] => 
            [state] => new-york
            [event_type] => meeting
        )

    [1] => Array
        (
            [ID] => 2
            [title] => Find My Stapler
            [date_start] => 2010-07-22
            [time_start] => 10:45:AM
            [time_end] => 
            [state] => new-york
            [event_type] => meeting
        )

    [2] => Array
        (
            [ID] => 3
            [title] => Mario Party
            [date_start] => 2010-07-22
            [time_start] => 02:30:PM
            [time_end] => 07:15:PM
            [state] => new-york
            [event_type] => party
        )

    [3] => Array
        (
            [ID] => 4
            [title] => Duct Tape Party
            [date_start] => 2010-07-28
            [time_start] => 01:00:PM
            [time_end] => 
            [state] => california
            [event_type] => party
        )
...... etc
attepted_nerd
źródło
1
… I chcesz to uporządkować, jak?
deceze
możliwy duplikat Sorting wielowymiarowej tablicy w PHP
dnia

Odpowiedzi:

185

Potrzebujesz array_multisort

$mylist = array(
    array('ID' => 1, 'title' => 'Boring Meeting', 'event_type' => 'meeting'),
    array('ID' => 2, 'title' => 'Find My Stapler', 'event_type' => 'meeting'),
    array('ID' => 3, 'title' => 'Mario Party', 'event_type' => 'party'),
    array('ID' => 4, 'title' => 'Duct Tape Party', 'event_type' => 'party')
);

# get a list of sort columns and their data to pass to array_multisort
$sort = array();
foreach($mylist as $k=>$v) {
    $sort['title'][$k] = $v['title'];
    $sort['event_type'][$k] = $v['event_type'];
}
# sort by event_type desc and then title asc
array_multisort($sort['event_type'], SORT_DESC, $sort['title'], SORT_ASC,$mylist);

Od PHP 5.5.0:

array_multisort(array_column($mylist, 'event_type'), SORT_DESC,
                array_column($mylist, 'title'),      SORT_ASC,
                $mylist);

$mylist jest teraz:

array (
  0 => 
  array (
    'ID' => 4,
    'title' => 'Duct Tape Party',
    'event_type' => 'party',
  ),
  1 => 
  array (
    'ID' => 3,
    'title' => 'Mario Party',
    'event_type' => 'party',
  ),
  2 => 
  array (
    'ID' => 1,
    'title' => 'Boring Meeting',
    'event_type' => 'meeting',
  ),
  3 => 
  array (
    'ID' => 2,
    'title' => 'Find My Stapler',
    'event_type' => 'meeting',
  ),
)
Obrabować
źródło
@Rob Jestem bardzo ciekawy, jak posortowałbyś date_start
frazras
6
Co to jest „impreza z taśmą klejącą”?
gdaniel
W przypadku PHP <5.5 istnieje wypełnienie array_columnfunkcji github.com/ramsey/array_column . Dzięki temu możliwe jest użycie bardziej eleganckiego sposobu z drugiego fragmentu kodu w starszych wersjach.
userlond
15

Możesz to zrobić za pomocą usort. $cmp_functionArgumentem mogą być:

function my_sorter($a, $b) {
    $c = strcmp($a['state'], $b['state']);
    if($c != 0) {
        return $c;
    }

    $c = strcmp($a['event_type'], $b['event_type']);
    if($c != 0) {
        return $c;
    }

    return strcmp($a['date_start'], $b['date_start']);
}

W przypadku dowolnej liczby pól w PHP 5.3 możesz użyć domknięć, aby utworzyć funkcję porównawczą:

function make_cmp($fields, $fieldcmp='strcmp') {
    return function ($a, $b) use (&$fields) {
        foreach ($fields as $field) {
            $diff = $fieldcmp($a[$field], $b[$field]);
            if($diff != 0) {
                return $diff;
            }
        }
        return 0;
    }
}

usort($arr, make_cmp(array('state', 'event_type', 'date_start')))

Dla dowolnej liczby pól różnych typów w PHP 5.3:

function make_cmp($fields, $dfltcmp='strcmp') {
    # assign array in case $fields has no elements
    $fieldcmps = array();
    # assign a comparison function to fields that aren't given one
    foreach ($fields as $field => $cmp) {
        if (is_int($field) && ! is_callable($cmp)) {
            $field = $cmp;
            $cmp = $dfltcmp;
        }
        $fieldcmps[$field] = $cmp;
    }
    return function ($a, $b) use (&$fieldcmps) {
        foreach ($fieldcmps as $field => $cmp) {
            $diff = call_user_func($cmp, $a[$field], $b[$field]);
            if($diff != 0) {
                return $diff;
            }
        }
        return 0;
    }
}

function numcmp($a, $b) {
    return $a - $b;
}
function datecmp($a, $b) {
    return strtotime($a) - strtotime($b);
}
/**
 * Higher priority come first; a priority of 2 comes before 1.
 */
function make_evt_prio_cmp($priorities, $default_priority) {
    return function($a, $b) use (&$priorities) {
        if (isset($priorities[$a])) {
            $prio_a = $priorities[$a];
        } else {
            $prio_a = $default_priority;
        }
        if (isset($priorities[$b])) {
            $prio_b = $priorities[$b];
        } else {
            $prio_b = $default_priority;
        }
        return $prio_b - $prio_a;
    };
}

$event_priority_cmp = make_evt_prio_cmp(
    array('meeting' => 5, 'party' => 10, 'concert' => 7), 
    0);

usort($arr, make_cmp(array('state', 'event' => $event_priority_cmp, 'date_start' => 'datecmp', 'id' => 'numcmp')))
Stijn Leenknegt
źródło
1
Możesz nieco uprościć zagnieżdżanie i myślę, że będziesz musiał zrobić coś więcej z datą, ale jak dotąd podejście wydaje się najlepsze.
deceze
1
Zaletą formatu „% Y-% m-% d” używanego w przykładowej tablicy jest porównanie ciągów, które działa przy porównywaniu dat.
wyjazd
11

PHP7 Sprawia, że ​​sortowanie według wielu kolumn jest BARDZO łatwe dzięki operatorowi statku kosmicznego ( <=>), znanemu jako „operator porównania łączonego” lub „operator porównania trójstronnego”.

Zasób: https://wiki.php.net/rfc/combined-comparison-operator

Sortowanie według wielu kolumn jest tak proste, jak pisanie tablic zrównoważonych / relacyjnych po obu stronach operatora. To łatwe!

Nie używałem, uasort()ponieważ nie widzę potrzeby zachowywania oryginalnych indeksów.

Kod: ( Demo )

$array = [
    ['ID' => 1, 'title' => 'Boring Meeting', 'date_start' => '2010-07-30', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 2, 'title' => 'Find My Stapler', 'date_start' => '2010-07-22', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 3, 'title' => 'Mario Party', 'date_start' => '2010-07-22', 'event_type' => 'party', 'state' => 'new-york'],
    ['ID' => 4, 'title' => 'Duct Tape Party', 'date_start' => '2010-07-28', 'event_type' => 'party', 'state' => 'california']
];

usort($array, function($a, $b) {
    return [$a['state'], $a['event_type'], $a['date_start']]
           <=>
           [$b['state'], $b['event_type'], $b['date_start']];
});

var_export($array);

Wynik

array (
  0 => 
  array (
    'ID' => 4,
    'title' => 'Duct Tape Party',
    'date_start' => '2010-07-28',
    'event_type' => 'party',
    'state' => 'california',
  ),
  1 => 
  array (
    'ID' => 2,
    'title' => 'Find My Stapler',
    'date_start' => '2010-07-22',
    'event_type' => 'meeting',
    'state' => 'new-york',
  ),
  2 => 
  array (
    'ID' => 1,
    'title' => 'Boring Meeting',
    'date_start' => '2010-07-30',
    'event_type' => 'meeting',
    'state' => 'new-york',
  ),
  3 => 
  array (
    'ID' => 3,
    'title' => 'Mario Party',
    'date_start' => '2010-07-22',
    'event_type' => 'party',
    'state' => 'new-york',
  ),
)

ps Arrow składnia w PHP7.4 i nowszych ( Demo ) ...

usort($array, fn($a, $b) =>
    [$a['state'], $a['event_type'], $a['date_start']]
    <=>
    [$b['state'], $b['event_type'], $b['date_start']]
);

Równoważna technika zi array_multisort()wywołanie array_column()dla wszystkich kryteriów sortowania to: ( Demo )

array_multisort(
    array_column($array, 'state'),
    array_column($array, 'event_type'),
    array_column($array, 'date_start'),
    $array
);
mickmackusa
źródło
2
class Sort {
    private $actual_order = 'asc';
    private $actual_field = null;

    public function compare_arrays($array1, $array2) {

        if ($array1[$this->actual_field] == $array2[$this->actual_field]) {
            return 0;
        }
        elseif ($array1[$this->actual_field] > $array2[$this->actual_field]) {
            return ($this->actual_order == 'asc' ? 1 : -1);
        }
        else {
            return ($this->actual_order == 'asc' ? -1 : 1);
        }

    }


    public function order_array(&$array) {

        usort($array, array($this, 'compare_arrays'));

    }


    public function __construct ($field, $actual_order = 'asc') {
        $this->actual_field = $field;
        $this->actual_order = $actual_order;
    }

}

// use

$sort = new Sort ("state");

$sort->order_array($array);
Gabriel Sosa
źródło
W tej odpowiedzi brakuje edukacyjnego wyjaśnienia. Jak ta odpowiedź rozwiązuje pytanie PO dotyczące sortowania wielowymiarowej tablicy w trzech kolumnach?
mickmackusa
2

Próbowałem poniżej kodu i pomyślnie

kod tablicy

$songs =  array(
        '1' => array('artist'=>'Smashing Pumpkins', 'songname'=>'Soma'),
        '2' => array('artist'=>'The Decemberists', 'songname'=>'The Island'),
        '3' => array('artist'=>'Fleetwood Mac', 'songname' =>'Second-hand News')
);

wywołanie funkcji sortowania tablicy

$songs = subval_sort($songs,'artist'); 
print_r($songs);

funkcja sortowania tablicy

function subval_sort($a,$subkey) {
    foreach($a as $k=>$v) {
        $b[$k] = strtolower($v[$subkey]);
    }
    asort($b);
    foreach($b as $key=>$val) {
        $c[] = $a[$key];
    }
    return $c;
}

if tablica odwrotna funkcja sortowania

function subval_sort($a,$subkey) {
        foreach($a as $k=>$v) {
            $b[$k] = strtolower($v[$subkey]);
        }
        arsort($b);
        foreach($b as $key=>$val) {
            $c[] = $a[$key];
        }
        return $c;
    }
Patel
źródło
Sortuje się tylko według kolumn (w nieelegancki sposób), ale pytanie PO wymaga, aby kryteria sortowania obejmowały trzy wartości kolumn. W najlepszym przypadku jest to poprawna odpowiedź na inne pytanie.
mickmackusa
2

Ulepszając genialny kod @Stijn Leenknegt, oto moja pragmatyczna funkcja 2 centów:

$data[] = array('volume' => 67, 'edition' => 2);
$data[] = array('volume' => 86, 'edition' => 1);
$data[] = array('volume' => 85, 'edition' => 6);
$data[] = array('volume' => 98, 'edition' => 2);
$data[] = array('volume' => 86, 'edition' => 6);
$data[] = array('volume' => 67, 'edition' => 7);

function make_cmp(array $sortValues)
{
    return function ($a, $b) use (&$sortValues) {
        foreach ($sortValues as $column => $sortDir) {
            $diff = strcmp($a[$column], $b[$column]);
            if ($diff !== 0) {
                if ('asc' === $sortDir) {
                    return $diff;
                }
                return $diff * -1;
            }
        }
        return 0;
    };
}

usort($data, make_cmp(['volume' => "desc", 'edition' => "asc"]));
molwa
źródło
if ($diff) { return $diff * ($sortDir === 'asc' ? 1 : -1); }
mickmackusa
0

Może to komuś pomoże:

// data to sort
$output = array(
        array('ID' => 1, 'title' => 'Boring Meeting', 'event_type' => 'meeting'),
        array('ID' => 2, 'title' => 'Find My Stapler', 'event_type' => 'meeting'),
        array('ID' => 3, 'title' => 'Mario Party', 'event_type' => 'party'),
        array('ID' => 4, 'title' => 'Duct Tape Party', 'event_type' => 'party')
);

// multi column, multi direction order by
$body['order_by'] = array(
        array("field"=> "event_type", "order"=> "desc"),
        array("field"=> "title", "order"=> "asc"),
        array("field"=> "ID", "order"=> "asc"),
);

$output = $this->multiColumnMultiDirectionSort($body, $output);


public function multiColumnMultiDirectionSort(array $body, array $output)
{
    // get order fields and its direction in proper format
    $orderFieldDirection = [];
    if (!empty($body['order_by']) && is_array($body['order_by'])) {
        foreach ($body['order_by'] as $order) {
            $orderDirection = $order['order'] == "desc" ? SORT_DESC : SORT_ASC; // we need format that array_multisort supports
            $orderFieldDirection[$order['field']] = $orderDirection;
        }
    }

    if (!empty($orderFieldDirection)) {
        // get the list of sort columns and their data in the format that is required by array_multisort
        $amParams = [];
        $sort = [];
        foreach ($orderFieldDirection as $field => $order) {
            foreach ($output as $k => $v) {
                $sort[$field][$k] = $v[$field];
            }

            $amParams[] = $sort[$field];
            $amParams[] = $order;
            $amParams[] = SORT_REGULAR; // this is not needed, but we can keep as it might come handy in the future
        }

        $amParams[] = &$output; // very important to pass as reference
        call_user_func_array("array_multisort", $amParams);
    }

    return $output;
}
DoubleK
źródło
-1

jeśli chcesz posortować tablicę wielowymiarową

pierwsza tablica to:

$results['total_quote_sales_person_wise']['quote_po'];

drugi to:

$results['total_quote_sales_person_wise']['quote_count'];

ta tablica wielowymiarowa, którą chcesz posortować malejąco za jednym razem, a następnie użyj tego kodu:

array_multisort($results['total_quote_sales_person_wise']['quote_po'],SORT_DESC, $results['total_quote_sales_person_wise']['quote_count'],SORT_DESC);
pradip kor
źródło