PHP usort переупорядочивает массив, значение сортировки одинаково для всех

Я использую usort для сортировки массива с ассоциативным массивом внутри каждого элемента.

Когда все значения, которые я сортирую в массиве, одинаковы, они все равно изменяют положение элементов в массиве, есть ли способ предотвратить это?

Например это:

array(
    array('name' => 'Ben', 'authn_weight' => 85.3),
    array('name' => 'Josh', 'authn_weight' => 85.3),
    array('name' => 'Fred', 'authn_weight' => 85.3)
);

Можно изменить на это:

array(
    array('name' => 'Josh', 'authn_weight' => 85.3),
    array('name' => 'Ben', 'authn_weight' => 85.3),
    array('name' => 'Fred', 'authn_weight' => 85.3)
);

Это функция сортировки:

private function weightSortImplementation($a, $b){ 
    $aWeight = $a['autn_weight'];
    $bWeight = $b['autn_weight'];

    if ($aWeight == $bWeight) {
        return 0;
    }
    return ($aWeight < $bWeight) ? 1 : -1;
}

Я проверил, что функция weightSortImplementation всегда возвращает 0, показывая, что они одинаковы. Итак, почему это все еще переупорядочивает массив?


person Chris    schedule 28.08.2012    source источник
comment
Это интересный вопрос. Я только что проверил это, и после использования usort порядок был обратным. codepad.org/PRFpq8Ug   -  person Rocket Hazmat    schedule 28.08.2012
comment
Они не должны использовать стабильную сортировку, которая не гарантирует порядок элементов. если они равны.   -  person JoeyJ    schedule 28.08.2012


Ответы (2)


Ага, дело в преобразовании Шварца.

В основном он состоит из трех шагов:

  1. украшать; вы превращаете каждое значение в массив со значением в качестве первого элемента и ключом/индексом в качестве второго
  2. сортировать (как обычно)
  3. неукрашенный; вы отменяете шаг 1

Вот он (я настроил его для вашего конкретного варианта использования):

function decorate(&$v, $k)
{
    $v['authn_weight'] = array($v['authn_weight'], $k);
}

function undecorate(&$v, $k)
{
    $v['authn_weight'] = $v['authn_weight'][0];
}

array_walk($a, 'decorate');
usort($a, 'weightSortImplementation');
array_walk($a, 'undecorate');

Хитрость заключается в следующем утверждении:

array($x, 0) < array($x, 1)

Это то, что поддерживает правильный порядок вашего массива. И рекурсия не требуется :)

person Ja͢ck    schedule 28.08.2012
comment
супер штука бро..!! - person Mithun Satheesh; 21.11.2013
comment
Хм, кажется, у меня это не работает на PHP 5.4. - person Jens Kohl; 05.02.2015
comment
@JensKohl У вас есть воспроизводимый тестовый сценарий, на который я мог бы взглянуть? - person Ja͢ck; 05.02.2015
comment
@JensKohl А, вы не можете сделать strcmp() в массиве ;-) вы можете проверить мой более подробный ответ на тему стабильной сортировки. - person Ja͢ck; 05.02.2015
comment
@Ja͢ck Мне пришлось перечитать твой ответ несколько раз, но он сделал щелчок, и теперь он работает для меня. Спасибо. - person Jens Kohl; 05.02.2015
comment
Это гениально и действительно вытащило меня из затруднительного положения - кажется, работает отлично. ТЮ - person Nate; 08.07.2019

Из документации:

Если два элемента сравниваются как равные, их относительный порядок в отсортированном массиве не определен.

Вы можете использовать эту функцию [source], которая сохраняет порядок в случае равенства двух элементов:

function mergesort(&$array, $cmp_function = 'strcmp') {
    // Arrays of size < 2 require no action.
    if (count($array) < 2) return;
    // Split the array in half
    $halfway = count($array) / 2;
    $array1 = array_slice($array, 0, $halfway);
    $array2 = array_slice($array, $halfway);
    // Recurse to sort the two halves
    mergesort($array1, $cmp_function);
    mergesort($array2, $cmp_function);
    // If all of $array1 is <= all of $array2, just append them.
    if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) {
        $array = array_merge($array1, $array2);
        return;
    }
    // Merge the two sorted arrays into a single sorted array
    $array = array();
    $ptr1 = $ptr2 = 0;
    while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
        if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) {
            $array[] = $array1[$ptr1++];
        }
        else {
            $array[] = $array2[$ptr2++];
        }
    }
    // Merge the remainder
    while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];
    while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];
    return;
} 
person Tim Cooper    schedule 28.08.2012
comment
Есть ли способ предотвратить это? Может, использовать разные методы сортировки? или изменение реализации сортировки, я полагаю, могу ли я заставить сортировку по весу возвращать либо 1, либо -1, если они одинаковы? - person Chris; 28.08.2012
comment
Я думаю, вы должны указать свой источник. Я нашел этот метод дублированным здесь. - person Tyler Collier; 02.03.2015