【问题标题】:Unwanted behavior of PHP usortPHP usort 的不良行为
【发布时间】:2012-02-09 14:26:39
【问题描述】:

我对 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 排序。但是,正如你所看到的,我刚刚完全失去了之前的排序。我在说什么呢?我想像以前一样保留数组排序,但作为 secondary 排序。我希望我很清楚。 实际上我想使用 usort() 之类的东西对数据进行排序(因此,完全自定义排序),但如果两个项目之间的排序字段相同,我想保持它们的 相对位置 em> 和以前一样。考虑到给定的示例,我希望Lieserl Einstein 出现在Mileva Einstein 之前,因为一开始就是这样。

【问题讨论】:

    标签: php sorting usort


    【解决方案1】:

    PHP 中使用的排序算法具有此属性,如果项目匹配,则顺序未定义。

    如果您需要保持订单,那么您必须滚动自己的代码。

    幸运的是,有人已经拥有: http://www.php.net/manual/en/function.usort.php#38827

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

    瞧!

    【讨论】:

    • 已接受。无论如何,我以更简洁的方式解决了。在usort() 之前,我在每个对象(即$data[$i]-&gt;original_index = $i)中存储了原始数组位置(索引),然后当第一次比较返回0(即相同的排序值)时,我在比较函数中检查了这个值。很简单很聪明,不是吗? :) 谢谢大家!
    • 好主意@lorenzo-s。我也做了同样的事情,而且效果很好,自定义代码少了很多。
    【解决方案2】:
    $compare = strcmp($a['last'], $b['last']);
    if ($compare == 0)
        $compare = strcmp($a['name'], $b['name']);
    return $compare
    

    【讨论】:

    • 查看我在 Tim Cooper 回答中留下的评论。
    • 你能举一个你想要的结果的例子,而不是你得到的结果吗
    【解决方案3】:

    您要的是stable sorting algorithm,这是 php 不提供的。请注意,即使有时它看起来很稳定,也无法保证它的稳定性,因此在某些输入下可能会出现行为不端。

    如果您知道其他列的排序标准,您可以一次全部使用它以获得所需的行为。 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;
    });
    

    如果您真的需要一个稳定的排序,因为元素的现有顺序没有很好地定义,请尝试编写自己的排序。 bubble sort 例如很容易写。只要列表中的元素数量相对较少,这是一个很好的解决方案,否则请寻求实现其他稳定的排序算法之一。

    【讨论】:

    • 不幸的是,我不知道原始的排序标准(没有)。我不知道对于像我所说的那样运行的排序算法有一个精确的定义。我看到冒泡排序和归并排序(我最了解的那些)是稳定排序...所以... [OT] PHP 内部使用什么算法?!?
    • php 使用一个版本的快速排序
    • 我明白了。他用 PHP 重写了合并排序,这(现在我可以说)它是稳定的。目前最好的答案,但我会稍等片刻,看看是否有人会有更简洁的想法。
    【解决方案4】:

    试试这个:

    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

    此函数使用十进制系统的每个数字作为排序标准。最先出现的数字最高。可能不是最聪明的,但对我有用。

    【讨论】:

    • 与马克贝克和蒂姆库珀相同。对不起,我问问题的方式很老套。我现在更新了。
    猜你喜欢
    • 2011-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-08
    • 2015-02-17
    • 1970-01-01
    相关资源
    最近更新 更多