Нежелательное поведение PHP usort

У меня проблема с PHP usort(), Предположим, у меня есть такой массив (это упрощение, я не работаю с именами, у меня есть массив объектов, а не массивов):

$data = array(
    array('name' => 'Albert',      'last' => 'Einstein'),
    array('name' => 'Lieserl',     'last' => 'Einstein'),
    array('name' => 'Alan',        'last' => 'Turing'  ),
    array('name' => 'Mileva',      'last' => 'Einstein'),
    array('name' => 'Hans Albert', 'last' => 'Einstein')
);

Как видите, массив отсортирован произвольно.

Теперь, если хотите отсортировать по last, Я делаю:

function sort_some_people($a, $b) { return strcmp($a['last'], $b['last']); }
usort($data, 'sort_some_people');

И я имею:

Array (
    [0] => Array ( [name] => Mileva       [last] => Einstein )
    [3] => Array ( [name] => Albert       [last] => Einstein )
    [1] => Array ( [name] => Lieserl      [last] => Einstein )
    [2] => Array ( [name] => Hans Albert  [last] => Einstein )
    [4] => Array ( [name] => Alan         [last] => Turing   )
)

Это нормально, теперь они отсортированы по last, Но, как вы видите, я только что полностью потерял предыдущую сортировку. Что я говорю? Я хочу сохранить сортировку массива, как это было раньше, но в качестве вторичной сортировки. Надеюсь, мне было ясно. Практически я хочу отсортировать данные, используя что-то как usort() (так, полностью настраиваемая сортировка), но если поле сортировки между двумя элементами идентично, я хочу сохранить их относительное положение, как это было раньше. Учитывая приведенный пример, хочу Lieserl Einstein появляться раньше Mileva Einstein потому что это было так в начале.

4 ответа

Решение

Алгоритмы сортировки, используемые в PHP, имеют это свойство, где порядок не определен, если элементы совпадают.

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

К счастью, кто-то уже имеет: http://www.php.net/manual/en/function.usort.php

$data = array(
    array('name' => 'Albert',      'last' => 'Einstein'),
    array('name' => 'Lieserl',     'last' => 'Einstein'),
    array('name' => 'Alan',        'last' => 'Turing'  ),
    array('name' => 'Mileva',      'last' => 'Einstein'),
    array('name' => 'Hans Albert', 'last' => 'Einstein')
);

function sort_some_people($a, $b) {
        return strcmp($a['last'], $b['last']);
}

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

mergesort($data, 'sort_some_people');

print_r($data);

Выход:

Array
(
    [0] => Array
        (
            [name] => Albert
            [last] => Einstein
        )

    [1] => Array
        (
            [name] => Lieserl
            [last] => Einstein
        )

    [2] => Array
        (
            [name] => Mileva
            [last] => Einstein
        )

    [3] => Array
        (
            [name] => Hans Albert
            [last] => Einstein
        )

    [4] => Array
        (
            [name] => Alan
            [last] => Turing
        )

)

Вуаля!

Вы просите стабильный алгоритм сортировки, который не предлагает php. Имейте в виду, что даже если иногда это может показаться стабильным, это не гарантируется, и поэтому, вероятно, будет неправильно вести себя при определенных входных данных.

Если вам известны критерии сортировки других столбцов, вы можете применить их все сразу, чтобы получить желаемое поведение. http://www.php.net/array_multisort делает это. Следующее является более мощным способом, в котором логика сравнения полностью определяется пользователем.

// should behave similar to sql "order by last, first"
$comparatorSequence = array(
    function($a, $b) {
        return strcmp($a['last'], $b['last']);
    }
  , function($a, $b) {
        return strcmp($a['first'], $b['first']);
    }
  // more functions as needed
);

usort($theArray, function($a, $b) use ($comparatorSequence) {
    foreach ($comparatorSequence as $cmpFn) {
        $diff = call_user_func($cmpFn, $a, $b);
        if ($diff !== 0) {
            return $diff;
        }
    }
    return 0;
});

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

$compare = strcmp($a['last'], $b['last']);
if ($compare == 0)
    $compare = strcmp($a['name'], $b['name']);
return $compare

Попробуй это:

function sort_some_people($a, $b)
{
    $compareValue = 10 * strcmp($a['last'], $b['last']);
    $compareValue += 1 * strcmp($a['name'], $b['name']);
    return $compareValue;
}

Пример: http://codepad.org/zkHviVBM

Эта функция использует каждую цифру десятичной системы для критериев сортировки. Тот, который идет первым, имеет самую высокую цифру. Может быть, не самый умный, но работает на меня.

Другие вопросы по тегам