Сортировка многомерного массива по нескольким ключам


83

Я пытаюсь отсортировать многомерный массив по нескольким ключам и понятия не имею, с чего начать. Я посмотрел на uasort, но не совсем понял, как написать функцию для того, что мне нужно.

Мне нужно отсортировать по состоянию, затем по типу события, затем по дате.

Мой массив выглядит так:

    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

1
… И вы хотите как это отсортировать?
deceze

возможный дубликат Sorting
multidimensional

Ответы:


185

Тебе нужно 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);

Начиная с PHP 5.5.0:

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

$mylist сейчас:

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

@Rob Мне очень любопытно, как бы вы отсортировали date_start
frazras

6
Что такое «вечеринка из клейкой ленты»?
gdaniel

Для PHP <5.5 есть полифил для array_columnфункции github.com/ramsey/array_column . Таким образом, можно использовать более элегантный способ из второго фрагмента кода в устаревших версиях.
userlond 03

15

Вы можете сделать это с помощью usort. $cmp_functionАргумент может быть:

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']);
}

Для произвольного количества полей в PHP 5.3 вы можете использовать замыкания для создания функции сравнения:

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

Для произвольного количества полей разных типов в 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')))

1
Вы могли бы немного упростить вложение, и я думаю, вам нужно сделать что-то еще с датой, но пока этот подход кажется лучшим.
deceze

1
Хорошая особенность формата "% Y-% m-% d", используемого в массиве примеров, заключается в том, что сравнение строк работает для сравнения дат.
outis

11

PHP7 СУПЕР упрощает сортировку по нескольким столбцам с помощью оператора космического корабля ( <=>), известного как «Комбинированный оператор сравнения» или «Оператор трехстороннего сравнения».

Ресурс: https://wiki.php.net/rfc/combined-comparison-operator

Сортировка по нескольким столбцам так же проста, как запись сбалансированных / реляционных массивов по обе стороны от оператора. Сделано легко!

Я не использовал, uasort()потому что не вижу необходимости сохранять исходные индексы.

Код: ( Демо )

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

Вывод

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 Синтаксис стрелок в PHP7.4 и выше ( Демо ) ...

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

Эквивалентная техника с array_multisort()вызовом array_column()каждого критерия сортировки: ( Демо )

array_multisort(
    array_column($array, 'state'),
    array_column($array, 'event_type'),
    array_column($array, 'date_start'),
    $array
);

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

В этом ответе отсутствует образовательное объяснение. Как этот ответ решает вопрос OP о сортировке многомерного массива по трем столбцам?
mickmackusa

2

Я попытался ввести код ниже, и я успешно

код массива

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

вызов функции сортировки массива

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

функция сортировки массивов

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

если функция обратной сортировки массива

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

Это сортируется только по столбцу (неизящным образом), но вопрос OP требует, чтобы критерии сортировки включали три значения столбца. В лучшем случае это правильный ответ на другой вопрос.
mickmackusa

2

Улучшение гениального кода @Stijn Leenknegt, вот моя прагматическая функция за 2 цента:

$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"]));

if ($diff) { return $diff * ($sortDir === 'asc' ? 1 : -1); }
mickmackusa

0

Может кому поможет:

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

-1

если вы хотите отсортировать многомерный массив

первый массив:

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

второй:

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

этот многомерный массив, который вы хотите отсортировать по убыванию за один раз, затем используйте этот код:

array_multisort($results['total_quote_sales_person_wise']['quote_po'],SORT_DESC, $results['total_quote_sales_person_wise']['quote_count'],SORT_DESC);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.