Как отсортировать многомерный массив по нескольким столбцам?

Я пытаюсь сделать то же самое, что и запрос MySQL

SELECT * FROM table ORDER BY field1, field2, ...

но с php и многомерным массивом:

$Test = array(
    array("a"=>"004", "n"=>"03"),
    array("a"=>"003", "n"=>"02"),
    array("a"=>"001", "n"=>"02"),
    array("a"=>"005", "n"=>"01"),
    array("a"=>"001", "n"=>"01"),
    array("a"=>"004", "n"=>"02"),
    array("a"=>"003", "n"=>"01"),
    array("a"=>"004", "n"=>"01")
);
function msort(&$array, $keys){
    array_reverse($keys);
    foreach($keys as $key){
        uasort($array, sortByKey);
    }
    //
    function sortByKey($A, $B){

        global $key;

        $a = $A[$key];
        $b = $B[$key];
        if($a==$b) return 0;
        return ($a < $b)? -1 : 1 ;
    }
}
//
msort($Test, array("a","n"));
//
foreach($Test as $t){
    echo('<p>'.$t["a"].'-'.$t["n"].'</p>');
}

Моя теория такова: если я несколько раз отсортирую столбцы с «меньшей важностью», а затем столбцы с «большей важностью», я получу порядок, подобный приведенному выше запросу MySQL.

К сожалению, php возвращает:

Предупреждение: uasort() ожидает, что параметр 2 будет допустимым обратным вызовом, функция sortByKey не найдена или недопустимое имя функции в /Library/WebServer/Documents/www/teste.array_sort.php в строке 23" (строка uasort)

Это простая функция заказа. Что мне не хватает?


person Gustavo    schedule 02.04.2014    source источник
comment
возможный дубликат Reference: все основные способы сортировки массивы и данные в PHP   -  person deceze♦    schedule 02.04.2014
comment
Может быть, посмотрю.   -  person Gustavo    schedule 02.04.2014
comment
Ссылка получила другой подход. Кроме того, прежде чем закрыть этот вопрос, который на самом деле не является дубликатом, я думаю, следует ответить, почему код не работает.   -  person Gustavo    schedule 02.04.2014
comment
Это не работает, потому что вы сортируете по a, а затем снова сортируете с нуля по b. Вы не уточняете сорт, вы прибегаете к чему-то другому. Кроме того, вы не передаете функции в качестве обратного вызова. Кроме того, объявление функций внутри функций на самом деле не работает [как вы можете подумать].   -  person deceze♦    schedule 02.04.2014
comment
Извините, я не получил вашего объяснения а-б, но вы дали мне подсказку, чтобы я мог решить ее выше, используя все материалы здесь и из ссылки. И если вы посмотрите на это с некоторым терпением, вы поймете, что этот пост не является дубликатом, мой ответ отличается.   -  person Gustavo    schedule 03.04.2014
comment
Я говорю, что если вы сначала сделаете usort по ключу a, а затем снова usort по ключу n, вы не получите ORDER BY a, n, вы просто получите эквивалент ORDER BY n. Один сорт не уточняет предыдущий.   -  person deceze♦    schedule 03.04.2014


Ответы (4)


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

/**
 * Returns a comparison function to sort by $cmp
 * over multiple keys. First argument is the comparison
 * function, all following arguments are the keys to
 * sort by.
 */
function createMultiKeyCmpFunc($cmp, $key /* , keys... */) {
    $keys = func_get_args();
    array_shift($keys);

    return function (array $a, array $b) use ($cmp, $keys) {
        return array_reduce($keys, function ($result, $key) use ($cmp, $a, $b) {
            return $result ?: call_user_func($cmp, $a[$key], $b[$key]);
        });
    };
}

usort($array, createMultiKeyCmpFunc('strcmp', 'foo', 'bar', 'baz'));
// or
usort($array, createMultiKeyCmpFunc(function ($a, $b) { return $a - $b; }, 'foo', 'bar', 'baz'));

Это примерно эквивалентно SQL ORDER BY foo, bar, baz.

Если, конечно, для каждого ключа требуется различная логика сравнения, и вы не можете использовать общие strcmp или - для всех ключей, вы вернетесь к тому же коду, как описано здесь.

person deceze♦    schedule 03.04.2014
comment
Что ж, ваш ответ элегантнее моего, если не сказать больше. Но пока вы говорите, что это один и тот же подход, вы используете много разных методов для решения проблемы, может быть, у меня другое понимание подхода, но ваш ответ действительно хороший вклад в этот форум. Спасибо! - person Gustavo; 03.04.2014
comment
Тот же подход, что и в примере, мы создадим одну функцию сравнения, с помощью которой вы будете выполнять ровно одну сортировку. В отличие от вашего первоначального подхода к выполнению нескольких последовательных видов. Этот ответ здесь просто демонстрирует технику динамического создания этой функции сравнения, не более того. :) - person deceze♦; 03.04.2014

Вот код, который я написал, чтобы сделать что-то подобное:

uasort($array,function($a,$b) {
    return strcmp($a['launch'],$b['launch'])
        ?: strcmp($a['tld'],$b['tld'])
        ?: strcmp($a['sld'],$b['sld']);
});

Он как бы злоупотребляет тем фактом, что отрицательные числа верны (ложным является только ноль), чтобы сначала сравнить launch, затем tld, затем sld. Вы должны быть в состоянии адаптировать это к вашим потребностям достаточно легко.

person Niet the Dark Absol    schedule 02.04.2014
comment
Аккуратный! Я связался с ним из stackoverflow.com/questions/17364127/ :) - person deceze♦; 02.04.2014
comment
Конечно, вы можете использовать разные *cmp функции (strcasecmp, strncmp, strncasecmp, strnatcmp, strnatcasecmp) в зависимости от желаемого результата. - person Niet the Dark Absol; 02.04.2014
comment
Или любая операция сравнения. return $a['foo'] - $b['foo'] ?: $a['bar'] - $b['bar'] - person deceze♦; 02.04.2014
comment
@deceze Верно! Пока функция возвращает ноль, потому что они равны, у вас все хорошо. - person Niet the Dark Absol; 02.04.2014
comment
Ссылка получила аналогичное решение. Но используя функцию с неограниченным количеством ключей, мы получим решение для большего количества случаев (более общее решение). Поэтому я поставил sql запрос, у вас есть база данных в строке и вы можете сортировать, где вам нужно. - person Gustavo; 02.04.2014

Это сделает работу, спасибо за вклад!

function mdsort(&$array, $keys){

    global $KeyOrder;

    $KeyOrder = $keys;
    uasort($array, cmp);    
}
function cmp(array $a, array $b) {

    global $KeyOrder;

    foreach($KeyOrder as $key){
        $res = strcmp($a[$key], $b[$key]);
        if($res!=0) break;
    }
    return $res;
}
//
mdsort($Test, array("a","n"));

Этот код немного уродлив, однако, я считаю, что он может быть лучше - может быть, класс для решения проблемы передачи массива с ключами в функцию "cmp". Но это отправная точка, вы можете использовать ее с любым количеством ключей для сортировки.

person Gustavo    schedule 02.04.2014

Синтаксис стрелки PHP7.4 устраняет большую часть раздувания кода, который ранее был необходим для приведения ваших порядков столбцов в область usort().

Код: (Демо)

$orderBy = ['a', 'n'];
usort($Test, fn($a, $b) =>
    array_map(fn($v) => $a[$v], $orderBy)
    <=>
    array_map(fn($v) => $b[$v], $orderBy)
);
var_export($Test);

Я считаю, что это очень элегантный и лаконичный способ написания сценария задачи. Вы генерируете назначенные значения столбца из $a и $b в виде отдельных массивов и записываете оператор космического корабля между ними.

Без синтаксиса стрелки фрагмент становится немного более коротким.

Код: (Демо)

$orderBy = ['a', 'n'];
usort($Test, function($a, $b) use ($orderBy) {
    return 
        array_map(function($v) use ($a){
            return $a[$v];
        }, $orderBy)
        <=>
        array_map(function($v) use ($b){
            return $b[$v];
        }, $orderBy);
});
var_export($Test);

Оператор космического корабля будет проходить через соответствующие пары данных ([0] против [0], затем [1] против [1] и т. д.), пока не достигнет ненулевой оценки или пока не исчерпает массивы сравнения.

person mickmackusa    schedule 04.12.2019