【问题标题】:Sorting multi-dimensional array by weighted value按加权值对多维数组进行排序
【发布时间】:2012-08-03 09:18:09
【问题描述】:

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

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

我的任务是以有利于某些国家/地区的方式对数组进行排序。这是动态的——也就是说,偏好的国家/地区的选择是由用户的配置决定的。我有一个单独的数组,它指定前几个国家所需的排序顺序;来自其他国家/地区的结果将在列表末尾未排序。

所以问题是:如何在不使用全局变量的情况下将这种排序标准放入usort()。最好不要将标准数组注入主数组的每个元素('因为如果我无论如何都要循环它,那么使用usort() 有什么意义?)

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

【问题讨论】:

  • 仍然希望得到一个不涉及全局变量或任何尴尬构造的答案,所以我为此悬赏。
  • 不确定这是否相关,但如果您要从数据库中获取记录,您可以简单地向表中添加另一列,例如 relativeOrder 并执行类似 SELECT name FROM countries ORDER BY relativeOrder 的操作

标签: php arrays sorting usort


【解决方案1】:

你明确写到你不想拥有全局变量,所以我也不建议你使用静态变量,因为它们实际上是全局变量——而且根本不需要。

在 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 then:

$context = new CallContext();

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

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

这些配置值由 constructor 函数(简称​​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: 当然可以,但是由于这需要支付高额的版税,我不建议这样做:D
  • 比起@Grampa 的回答,我更喜欢这个选项。它们是相似的,但正如你所说,这个避免了任何类型的全局,即使作为静态隐藏在一个类中也是如此。这很好。不过,我不知道您对版税的评论是什么意思???
  • @SDC:这是我朋友 Leigh 和我之间的一个玩笑,你可以通过这张关于超静态银弹本质的深入图表了解更多关于超静态的概念:@987654322 @ 或这个学习图像:i.imgur.com/RJEsz.png - 基本上是一个关于当一些用户询问 SO 如何防止全局变量然后一些其他用户建议使用静态变量而不是“因为它不是全局的”(叹气)的笑话。这种态度的代价是高昂的,所以我们认为您需要使用Super::$tatic 支付高额版税——仅此而已;)
  • 赏金是你的。谢谢您的帮助。如果不出意外,这一集给了我一些弹药来升级我们的 PHP 版本,这只能是一件好事。
【解决方案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;如果没有其他更好的情况,稍后将接受并奖励赏金。谢谢您的帮助。 :)
  • 另一个不是全局变量但行为类似的全局变量是所谓的超全局变量。例如$GLOBALS。您可以只使用类似于CountryCompare::$country_priorities$GLOBALS['country_priorities'] - 一个全局变量,其名称不是全局变量,但行为类似于它。 ^^
  • 嗯,我现在更喜欢@hakra 的回答,所以他现在正在寻求赏金。但杰克的答案是我真正想要的……太糟糕了,我无法使用它。卡在旧版本的 PHP 上真是令人沮丧。
  • 赏金去了 hakra,但感谢您的帮助。如果不出意外,这一集给了我一些弹药来升级我们的 PHP 版本,这只能是一件好事。
【解决方案3】:

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

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

【讨论】:

  • 如果我可以使用 5.3 就完美了。我相信我们很快就会升级。没有赏金给你,但是 +1 因为我确实说过 5.3 个答案也会受到欢迎。
  • 是的,将带有 use 子句的闭包转换为在 PHP 5.2 中工作的东西是很复杂的。另一个技巧是也封装细节(我可能会在稍后将其添加到答案中)以便您可以在将来更改 PHP 版本时轻松更改,而不会与代码库的其余部分有太多问题。
  • @SDC 太糟糕了,你还不能切换到 5.3 :) 当然,还有模拟...stackoverflow.com/questions/2209327/…
  • 我猜这个问题和我问的差不多。赏金去哈克拉。但是谢谢你的回答。如果不出意外,这一集给了我一些坚实的弹药来升级我们的 PHP 版本,这只能是一件好事。
  • @Jack:这是一个很好的参考,但我想说还有一些改进的余地。然而,到目前为止,公认的答案确实显示了需要规避的问题领域。
【解决方案4】:

查看演示: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
        )

)

歌曲,按艺术家排序。

【讨论】:

    【解决方案5】:

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

    大多数时候,你有类似的东西

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

    但是你需要做的是类似于

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

    【讨论】:

    • 是的,确实如此。如果加权国家是一个固定值,那就太好了,但如果它是动态的,如何将那个 countryID 放入 usort 函数中?
    • @SDC 你知道哪个CountryIDs是你关心的,把它们硬编码成函数来处理排序?
    • 重点是它们不是固定的;它不能被硬编码。用户可以决定要加权的国家/地区。
    • @SDC 既然您有用户偏好,为什么不将它们弹出到一个数组中(比如 SESSION 或 STATIC)并让函数访问该变量以生成正确的返回值?
    • 是的。我试图避免使用全局。如果这就是答案,那就这样吧,但我希望有更好的东西。
    【解决方案6】:

    您需要使用 ksort 按重量排序,而不是 usort。这样会干净得多。

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

    krsort($weighted_data)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-15
      • 2013-04-03
      • 1970-01-01
      • 2021-07-11
      相关资源
      最近更新 更多