PHP usort重新排序数组,但所有排序值都相同

7
我正在使用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,表示它们是相同的。那么为什么还会重新排序数组呢?

这是一个有趣的问题。我刚刚测试了一下,在使用usort之后,顺序被颠倒了。http://codepad.org/PRFpq8Ug - gen_Eric
他们一定没有使用稳定排序,如果元素相等,它不能保证元素的顺序。 - JoeyJ
2个回答

13

哦,这是一个使用Schwartzian Transform的案例。

它基本上包含三个步骤:

  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)

这是保持数组正确顺序的方法,而且不需要使用递归 :)


太棒了,兄弟!! - Mithun Satheesh
嗯,看起来在 PHP 5.4 上对我不起作用。 - Jens Kohl
@JensKohl,你有一个可重现的测试脚本吗?我可以看一下吗? - Ja͢ck
1
@JensKohl 啊,你不能在一个数组上执行 strcmp() ;-) 你可能想要查看我关于稳定排序的更详细的回答 - Ja͢ck
@Ja͢ck 我不得不多次阅读你的答案,但是现在我明白了,它对我有用。谢谢。 - Jens Kohl
这真是太聪明了,让我摆脱了困境 - 看起来完美无缺。谢谢! - Nate

9

来自文档

如果两个成员比较相等,则它们在排序后的数组中的相对顺序是未定义的。

您可以使用这个函数[源代码],以保持两个元素相等时的顺序:

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

有没有什么方法可以防止这种情况发生?也许使用不同的排序方法?或者改变排序实现,我想如果它们相同,我可以让权重排序返回1或-1? - Chris
我认为你应该注明出处。我发现这个方法在这里被复制了。 - Tyler Collier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接