按加权值对多维数组进行排序

时间:2012-08-06 11:49:36

标签: php arrays sorting usort

这里有很多问题,询问如何在PHP中对多维数组进行排序。答案是usort()。我知道。但是我有一个问题需要更进一步,我在这里看不到类似的答案。

我有一系列记录,每个记录都包含一个国家/地区ID(如果您愿意,还可以包含国家/地区名称;它不相关)。

我的任务是以对某些国家有利的方式对数组进行排序。这是动态的 - 即支持的国家的选择取决于用户的配置。我有一个单独的数组,它指定了前几个国家所需的排序顺序;其他国家/地区的结果只会在列表末尾未分类。

所以问题是:如何在不使用全局变量的情况下将此排序条件纳入usort()。并且最好不要将条件数组注入主数组的每个元素('如果我还要循环它,那么,使用usort()的重点是什么?)

请注意:由于它与这里的答案相关,我暂时停留在PHP 5.2上,所以我不能使用匿名函数。我们正在升级,但是现在我需要能够解决5.2的问题。 (5.3 / 5.4的答案也会受到欢迎,特别是如果它们使它变得更容易,但我将无法使用它们)

6 个答案:

答案 0 :(得分:5)

你明确地写道,你不想拥有全局变量,所以我也不会给你一个静态变量的建议,因为那些实际上是全局变量 - 根本不需要那些变量。

在PHP 5.2(及更早版本)中,如果你需要回调中的调用上下文,你可以通过使用它自己的类来创建你的上下文:

class CallContext
{
}

例如,您有sort的比较函数:

class CallContext
{
    ...
    public function compare($a, $b)
    {
         return $this->weight($a) - $this->weight($b);
    }

    public function getCallback()
    {
         return array($this, 'compare');
    }
    ...
}

该函数可以用作以下usort的回调:

$context = new CallContext();

usort($array, $context->getCallback());

非常直接。 CallContext::weight的私有实现仍然缺失,从您的问题我们知道它需要一些数据和信息。例如,每条记录中国家/地区ID的密钥名称。假设记录是Stdclass对象,因此要获得一个记录的权重,上下文类需要知道属性的名称,您自己定义的排序顺序以及未在其中定义的那些country-id的排序值。自定义排序顺序(其他,其余的)。

这些配置值使用构造函数函数(简称​​ ctor )给出,并存储为私有成员。丢失的weight函数然后根据该信息将记录转换为排序值:

class CallContext
{
    private $property, $sortOrder, $sortOther;

    public function __construct($property, $sortOrder, $sortOther = 9999)
    {
        $this->property = $property;
        $this->sortOrder = $sortOrder;
        $this->sortOther = $sortOther;
    }

    private function weight($object) {
        if (!is_object($object)) {
            throw new InvalidArgumentException(sprintf('Not an object: %s.', print_r($object, 1)));
        }
        if (!isset($object->{$this->property})) {
            throw new InvalidArgumentException(sprintf('Property "%s" not found in object: %s.', $this->property, print_r($object, 1)));
        }
        $value = $object->{$this->property};
        return isset($this->sortOrder[$value])
               ? $this->sortOrder[$value]
               : $this->sortOther;
    }
    ...

现在使用范围扩展到以下内容:

$property = 'country';
$order = array(
    # country ID => sort key (lower is first)
    46 => 1,
    45 => 2
);
$context = new CallContext('country', $order);
usort($array, $context->getCallback());

使用相同的原则,您可以经常将带有use子句的任何PHP 5.3闭包转换为PHP 5.2。 use子句中的变量成为注入构造的私有成员。

这个变体不仅可以防止静态的使用,而且还可以看出每个元素都有一些映射,并且两个元素都被视为相等,它使用了某些weight函数的私有实现这与usort非常吻合。

我希望这有用。

答案 1 :(得分:2)

你可能想要一个全局变量,但你需要一些行为的东西。您可以使用具有静态方法和参数的类。它不会对全球范围造成太大污染,它仍然可以按照您的需要运行。

class CountryCompare {
    public static $country_priorities;

    public static function compare( $a, $b ) {
        // Some custom sorting criteria
        // Work with self::country_priorities
    }

    public static function sort( $countries ) {
        return usort( $countries, array( 'CountryCompare', 'compare' ) );
    }
}

然后就这样称呼它:

CountryCompare::country_priorities = loadFromConfig();
CountryCompare::sort( $countries );

答案 2 :(得分:2)

您可以使用闭包(PHP> = 5.3):

$weights = array( ... );
usort($records, function($a, $b) use ($weights) {
    // use $weights in here as usual and perform your sort logic
});

答案 3 :(得分:1)

请参阅演示:http://codepad.org/vDI2k4n6

$arrayMonths = array(
       'jan' => array(1, 8, 5,4),
       'feb' => array(10,12,15,11),
       'mar' => array(12, 7, 4, 3),
       'apr' => array(10,16,7,17),
    );

$position = array("Foo1","Foo2","Foo3","FooN");
$set = array();

foreach($arrayMonths as $key => $value)
{
    $max = max($value);
    $pos = array_search($max, $value);
    $set[$key][$position[$pos]] = $max ;
}


function cmp($a, $b)
{
    foreach($a as $key => $value )
    {
        foreach ($b  as $bKey => $bValue)
        {
            return $bValue - $value ;
        }
    }

}

uasort($set,"cmp");
var_dump($set);

输出

array
      'apr' => 
        array
          'FooN' => int 17
      'feb' => 
        array
          'Foo3' => int 15
      'mar' => 
        array
          'Foo1' => int 12
      'jan' => 
        array
          'Foo2' => int 8

另一个例子: -

使用PHP对多维数组进行排序

http://www.firsttube.com/read/sorting-a-multi-dimensional-array-with-php/

我经常发现自己有一个多维数组,我希望按子数组中的值进行排序。我有一个可能看起来像这样的数组:

//an array of some songs I like
$songs =  array(
        '1' => array('artist'=>'The Smashing Pumpkins', 'songname'=>'Soma'),
        '2' => array('artist'=>'The Decemberists', 'songname'=>'The Island'),
        '3' => array('artist'=>'Fleetwood Mac', 'songname' =>'Second-hand News')
    );

问题在于:我想以“歌名(艺术家)”的格式回应我喜欢的歌曲,我想按艺术家的字母顺序进行。 PHP提供了许多用于排序数组的函数,但没有一个可以在这里工作ksort()允许我按键排序,但$ songs数组中的键是无关紧要的。 asort()允许我对键进行排序和保存,但它会按每个元素的值对$ song进行排序,这也是无用的,因为每个元素的值都是“array()”。 usort()是另一个可能的候选者,可以进行多维排序,但它涉及构建回调函数,并且通常很冗长。甚至PHP文档中的示例都引用了特定的键。

所以我开发了一个快速函数来按子数组中的键值进行排序。请注意,此版本不区分大小写。请参阅下面的subval_sort()。

function subval_sort($a,$subkey) {
    foreach($a as $k=>$v) {
        $b[$k] = strtolower($v[$subkey]);
    }
    asort($b);
    foreach($b as $key=>$val) {
        $c[] = $a[$key];
    }
    return $c;
}

要在上面使用它,我只需输入:

$songs = subval_sort($songs,'artist'); 
print_r($songs);

这是你应该期待的:

Array
(
    [0] => Array
        (
            [artist] => Fleetwood Mac
            [song] => Second-hand News
        )

    [1] => Array
        (
            [artist] => The Decemberists
            [song] => The Island
        )

    [2] => Array
        (
            [artist] => The Smashing Pumpkins
            [song] => Cherub Rock
        )

)

歌曲,按艺术家排序。

答案 4 :(得分:0)

您的问题的答案确实在usort()函数中。但是,您需要做的是编写传递给它的函数正确地为您进行加权。

大多数情况下,你有类似

的东西
if($a>$b)
{
    return $a;
}

但你需要做的是

if($a>$b || $someCountryID != 36)
{
    return $a;
}
else
{
    return $b;
}

答案 5 :(得分:0)

您需要使用kso​​rt按重量排序,而不是按重量排序。那将更加清洁。

$weighted_data格式将数据排列在关联数组weight => country_data_struct中。这是一种非常直观的加权数据表示形式。然后运行

krsort($weighted_data)