使用匿名函数按两个对象属性对数组进行排序

21

我有以下数组:

Array
(
    [0] => stdClass Object
        (
            [timestamp] => 1
            [id] => 10
        )

    [1] => stdClass Object
        (
            [timestamp] => 123
            [id] => 1
        )

    [2] => stdClass Object
        (
            [timestamp] => 123
            [id] => 2
        )

) 

我目前正在使用以下代码根据时间戳属性对数组进行排序:

function sort_comments_by_timestamp(&$comments, $prop)
{
    usort($comments, function($a, $b) use ($prop) {
        return $a->$prop < $b->$prop ? 1 : -1;
    });
}

当时间戳相同时,我如何按照id降序排序?


1
你为什么要称其为“_by_timestamp”,但又有一个“$prop”参数呢? - Matthew
1
@Matthew 除了“我正在做一些测试”和糟糕的命名之外,还有其他问题吗?没有 :) 顺便说一下,已经修复了。 - PeeHaa
8个回答

23
建议使用带有 $props 的数组进行发送。
function sort_comments_by_timestamp(&$comments, $props)
{
    usort($comments, function($a, $b) use ($props) {
        if($a->$props[0] == $b->$props[0])
            return $a->$props[1] < $b->$props[1] ? 1 : -1;
        return $a->$props[0] < $b->$props[0] ? 1 : -1;
    });
}

然后使用以下方式调用

sort_comments_by_timestamp($unsorted_array,array("timestamp","id"));

如果你想让它适用于X个$props,你可以在usort内部制作一个循环,始终将属性与数组中其前面的属性进行比较,就像这样:

function sort_comments_by_timestamp(&$comments, $props)
{
    usort($comments, function($a, $b) use ($props) {
        for($i = 1; $i < count($props); $i++) {
            if($a->$props[$i-1] == $b->$props[$i-1])
                return $a->$props[$i] < $b->$props[$i] ? 1 : -1;
        }
        return $a->$props[0] < $b->$props[0] ? 1 : -1;
    });
}

干杯!


1
如果你遇到了错误:Array to string conversion,那么你需要将所有的 $a->$props[X] 替换为 $a->{$props[X]} - Marek Skiba

4
function sort_comments_by_timestamp(&$comments, $prop)
{
    usort($comments, function($a, $b) use ($prop) {
        if ($a->$prop == $b->$prop)
          return $b->id - $a->id;
        else
          return $a->$prop < $b->$prop ? 1 : -1;
    });
}

以上排序首先按$prop参数排序,然后再按id排序。


3

你可以使用ouzo goodies来完成(我知道你已经听说过它:P)。

$result = Arrays::sort($array,
    Comparator::compound(
        Comparator::compareBy('timestamp'),
        Comparator::compareBy('id')
    )
);

3
我知道这是一个相当老的问题,然而并没有找到太多关于如何按照多个属性进行排序的信息,有点像MySQL中的ORDERBY,所以这里提供了一个来自个人框架的函数。
选项1:order($key, $direction='asc'),其中$key是对象的属性,$direction是'asc'或'desc'。
选项2:order(array($key => $direction, $key => $direction))。这与选项1类似,但当两个对象具有相同的$key值时,将使用传递的数组中的第二个排序选项。
public function order($arr, $key=null, $direction='ASC'){
    if(!is_string($key) && !is_array($key))
        throw new InvalidArgumentException("order() expects the first parameter to be a valid key or array");

    $props = array();

    if(is_string($key)) {
        $props[$key] = strtolower($direction) == 'asc' ? 1 : -1;
    }else{
        $i = count($key);
        foreach($key as $k => $dir){
            $props[$k] = strtolower($dir) == 'asc' ? $i : -($i);
            $i--;
        }
    }

    usort($arr, function($a, $b) use ($props){
        foreach( $props as $key => $val ){
            if( $a->$key == $b->$key ) continue;
            return $a->$key > $b->$key ? $val : -($val);
        }
        return 0;
    });

    return $arr;

}

非常好的小函数。我一直在使用接受的答案来排序我的需求(加上一些个人调整),但在网站CMS更新后它决定停止工作。这个函数节省了我大量时间,试图调试问题是什么。 - Victor D.
很高兴能帮上忙 :) - Dylan Grech

2

被接受的答案有一个循环变量,它并不总是有效。

if($a->$props[$i-1] == $b->$props[$i-1])
     return $a->$props[$i] < $b->$props[$i] ? 1 : -1;

obj1 属性1:foo,属性2:bar,属性3:test

obj2 属性1:foo,属性2:bar,属性3:apple

当 $i = 1 时,第一个比较为真,由于 bar 不小于 bar,它将返回 -1 而不是继续循环来评估属性3。

只是提供一下信息。

我有一个变量,还可以按升序或降序使用 -1 和 1 来对每个属性进行排序。

function sortByProps(&$anArray, $props) {
    usort($anArray, function($a, $b) use ($props) {
        for($i = 0; $i < count($props); $i++) {
            if( $a->{$props[$i][0]} < $b->{$props[$i][0]}) {
                return $props[$i][1];
            }
            else if( $a->{$props[$i][0]} > $b->{$props[$i][0]}) {
                return -1* $props[$i][1];
            }
        }
        return 0;
    });
}

sortByProps($someArray,(array(['name',-1],['address',-1],['occupation',-1],['favorite_food',-1])));

2
      function compare_city($a, $b)
      {
        // sort by state
        $retval = strnatcmp($a->state, $b->state);
        // if identical, sort by city
        if(!$retval) $retval = strnatcmp($a->city, $b->city);
        return $retval;
      }

      // sort alphabetically by state and city
      usort($sortable, __NAMESPACE__ . '\compare_city');

这个函数对我很有帮助!答案在此处找到:对SimpleXMLElement对象数组进行排序


上面的一些答案(包括被接受的答案!)似乎忽略了比较中的“相等”结果。很高兴看到使用strnatcmp来正确处理这个问题,特别是对于字符串属性。还要记得使用strnatcasecmp来进行不区分大小写的比较。 - John Rix

2
$result = -1;
if ($a->timestamp < $b->timestamp) {
   $result = 1;
} else if ($a->timestamp === $b->timestamp) {
   if ($a->id < $b->id) $result = 1;
}
return $result;

将此放入usort闭包中。您还可以摆脱$prop参数和use ($prop)部分。

1

以下是如何按任意数量的标准进行排序,一个接一个地打破关系。它的工作方式很像一个 SQL order by col1, col2 子句。

我总是忘记 $a - $b$b - $a 是升序还是降序排序。根据需要进行调整。

$comparatorSequence = array(
    function($a, $b) { return $a->timestamp - $b->timestamp; }
  , function($a, $b) { return $a->id - $b->id; }
);

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

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