按加权值排序多维数组

7
这里有很多关于如何在PHP中对多维数组进行排序的问题,答案是使用usort()函数。我知道这一点。但我有一个更进一步的问题,并且在这里没有看到类似的答案。
我有一个记录数组,每个记录包括一个国家ID(或国家名称,如果您愿意的话;这并不相关)。
我的任务是以某种方式对数组进行排序,以支持某些国家。这是动态的——即优选国家的选择由用户配置确定。我有一个单独的数组,指定前几个国家所需的排序顺序;来自其他国家的结果将被留在列表末尾未排序。
所以问题是:如何将此排序标准输入usort()而不必使用全局变量?最好不要将标准数组注入主数组的每个元素中(因为如果我仍然要循环它,那么使用usort()的意义是什么?)
请注意:由于这将与此处的答案相关,因此暂时我无法使用匿名函数,因为我们正处于升级过程中,但我需要适用于5.2的解决方案(可适用于5.3 / 5.4的解决方案也将受到欢迎,特别是如果它们显着简化了解决方案,但我将无法使用它们)。

1
仍然希望有一个不涉及全局变量或任何尴尬结构的答案,因此我为此设置了赏金。 - SDC
2
不确定这是否相关,但如果您正在从数据库中获取记录,您可以简单地向表格添加另一列,例如relativeOrder,然后执行类似于SELECT name FROM countries ORDER BY relativeOrder的操作。 - Pateman
6个回答

6

您明确表示不想使用全局变量,因此我不会建议您使用静态变量,因为它们实际上是全局变量,而且根本不需要。

在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对象,则要获取一个记录的权重,上下文类需要知道属性名称、自定义的排序顺序和那些未在自定义排序顺序(即其余部分)中定义的国家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非常配合使用。
希望这对您有所帮助。

我更喜欢这种方法,而不是 Super::$tatic :) - Leigh
@Leigh:当然,但是由于需要支付高额的版权费用,所以我并没有建议这样做:D - hakre
我更喜欢这个选项,而不是@Grampa的答案。它们很相似,但正如你所说,这个选项避免了任何类型的全局变量,即使将其隐藏在类中作为静态变量也是如此。这很好。不过,我不知道你在评论中提到的版税费是什么意思? - SDC
1
@SDC:这是我和我的朋友 Leigh 之间的一个笑话,你可以通过这个深入的图表了解超级静态的银弹性质:http://i.stack.imgur.com/WhPyr.png 或者这个学习图片:http://i.imgur.com/RJEsz.png - 基本上是一个关于当一些用户在 SO 上询问如何防止全局变量时,其他用户建议使用静态变量而不是“因为它不是全局的”(叹气)的笑话。这种态度的代价很高,所以我们称之为需要使用 Super::$tatic 支付高昂的版税费用 - 就是这样 ;) - hakre
赏金归你了。感谢你的帮助。如果没有其他事情,这一集为我们升级PHP版本提供了一些有力的支持,这只会是一件好事。 - SDC
@SDC:谢谢,没错,与PHP 5.2相比,PHP 5.3速度快得多。我真的很后悔我转换得太晚了。我的意思是,快得多。而且使用PHP 5.4应该会再次加快速度,但我还没有深入研究它。不过,自从大约一两周前,我更直接地评估它了。 - hakre

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

这似乎是我能得到的最接近我想要答案的地方。暂时+1;如果没有更好的东西出现,稍后会接受并授予赏金。谢谢你的帮助。 :) - SDC
另一个不是全局变量但表现得像全局变量的变量被称为超全局变量,例如$GLOBALS。你可以使用$GLOBALS['country_priorities']来代替CountryCompare::$country_priorities,后者是一个名字上不是全局变量但表现得像全局变量的变量。^^ - hakre
哎呀,我现在更喜欢@hakra的答案,所以他现在有机会获得赏金。但是我真正想要的是Jack的答案……太糟糕了,我不能使用它。被困在旧版本的PHP上真是令人沮丧。 - SDC
赏金归hakra所有,但感谢您的帮助。如果没有其他事情,这一事件为我提供了一些升级我们的PHP版本的弹药,这只会是一件好事。 - SDC

2
今日免费次数已满, 请开通会员/明日再来
$weights = array( ... );
usort($records, function($a, $b) use ($weights) {
    // use $weights in here as usual and perform your sort logic
});

如果我能使用5.3,那就太完美了。我相信我们很快会升级的。虽然没有赏金给你,但是我会点个赞,因为我确实说过欢迎5.3的答案。 - SDC
是的,在PHP 5.2中将带有use子句的闭包转换为可工作的内容确实很复杂。另一个技巧是封装细节(我可能稍后会将其添加到答案中),这样您就可以在将来更改PHP版本时轻松更改,而不会对代码库的其余部分产生太多问题。 - hakre
@SDC 真遗憾你还不能切换到5.3 :) 当然,有模拟器... https://dev59.com/EUvSa4cB1Zd3GeqPhMzQ - Ja͢ck
我猜那个问题基本上问的和我的一样。赏金给hakra。但还是谢谢你的回答。如果没有别的,这一集为升级我们的PHP版本提供了一些坚实的弹药,这只能是好事。 - SDC
是的,还有所有静态的东西。我想说这可以做得更好,但每个解决方案都是为了它自己的情况而存在的。如果它确实解决了某些问题,那为什么不呢?但我不会说它特别灵活。 - hakre
显示剩余2条评论

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() 允许保留键并对 $songs 进行排序,但它将按每个元素的值排序,这也是无用的,因为每个元素的值都是“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
        )

)

按艺术家排序的歌曲。


0

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

大多数情况下,你会有类似于

if($a>$b)
{
    return $a;
}

但是你需要做的是类似于以下内容:

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

是的,确实如此。如果加权国家是一个固定值,那么这将非常好,但如果它是动态的,如何将该countryID传递到usort函数中呢? - SDC
@SDC 你知道哪些CountryIDs是你关心的,将它们硬编码到函数中以处理排序? - Fluffeh
整个意义在于它们并不是固定的;不能硬编码。用户可以决定权重放在哪个或哪些国家上。 - SDC
@SDC 既然你已经有了用户偏好设置,为什么不将它们放入一个数组(比如SESSION或STATIC),让函数访问该变量以生成正确的返回值呢? - Fluffeh
你也可以将这个逻辑封装在一个类中,并将国家数组作为该类的成员?这与之前的方法相同,但它会限制数组的“全局”范围。 - Carlos Campderrós
显示剩余2条评论

0
你需要使用ksort按权重排序,而不是usort。这样会更加简洁。
将数据以关联数组的形式排列在$weighted_data中,格式为weight => country_data_struct。这是一种非常直观的加权数据表示形式。然后运行
krsort($weighted_data)

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