【问题标题】:Sorting multidim array: prioritize if column contains substring, then order by a second column排序多维数组:如果列包含子字符串,则优先排序,然后按第二列排序
【发布时间】:2011-01-04 18:43:28
【问题描述】:

我目前正在创建一个由 mysql 查询中的值组成的排序方法。

这是数组的简要视图:

    Array
    (
        [0] => Array
            (
                ['id'] = 1;
                ['countries'] = 'EN,CH,SP';
            )
        [1] => Array
            (
                ['id'] = 2;
                ['countries'] = 'GE,SP,SV';
            )
    )

我已经成功地根据数字 id 值进行了正常的排序,但我更想按“国家”字段的内容对数组进行排序(如果它包含一个设置的字符串,在这种情况下是一个国家代码) ,然后是 id 字段。

下面的 sn-p 是我第一次想到怎么做,但我不知道如何将它合并到一个工作函数中:

in_array('EN', explode(",",$a['countries']) );

你会怎么做?

谢谢!


不幸的是,我真的无能为力。

这是我目前拥有的,它只给我错误:uasort() [function.uasort]: Invalid comparison function

function compare($a, $b) {
    global $usercountry;

        if ( in_array($usercountry, $a['countries']) && in_array($usercountry, $a['countries']) ) {
            $return = 0;
        }

        else if (in_array($usercountry, $a['countries'])) {
            $return = 1;
        }

        else {
            $return = -1;
        }

        return $return;


        }

        $array= usort($array, "compare");

有没有人可以给我提示如何继续它?

【问题讨论】:

标签: php arrays multidimensional-array usort


【解决方案1】:

您可以考虑array_walkarray_walk_recursivearray_map,将它们组合在一起可能会做您想做的事情。

【讨论】:

  • 这个“提示”可能是问题下的评论。
【解决方案2】:

就我个人而言,我会结合usort() 使用自定义(匿名)函数。

编辑:重新 - 您的评论。希望这将使您走上正确的道路。此功能对同时具有 EN 或均不具有 EN 的元素给予同等优先级,或者当只有一个具有 EN 时调整优先级。

usort($array,function ($a, $b) {
    $ac = strpos($a['countries'],'EN');
    $bc = strpos($b['countries'],'EN');
    if (($ac !== false && $bc !== false) || ($ac == false && $bc == false)) {
        return 0;
    }
    elseif ($ac !== false) {
        return 1;
    }
    else {
        return -1;
    }
});

另一方面,如果两者都有 EN,则此函数给予相同的优先级,如果一个有 EN,则优先级更高,如果两者都没有 EN,则进行文本比较。

usort($array,function ($a, $b) {
    $ac = strpos($a['countries'],'EN');
    $bc = strpos($b['countries'],'EN');
    if ($ac !== false && $bc !== false)) {
        return 0;
    }
    elseif ($ac !== false) {
        return 1;
    }
    elseif ($bc !== false) {
        return -1;
    }
    else {
        if ($a['countries'] == $b['countries']) {
            return 0;
        }
        elseif($a['countries'] > $b['countries']) {
            return 1;
        }
        else {
            return -1;
        }
    }
});

再次,希望这将为您提供足够的方向,让您自己前进。如果您有任何问题,请随时发布更多 cmets,我会尽力提供帮助。 如果您要将多个属性与重量进行比较,请注意:尝试一个时髦的开关块,例如

$ac = array_flip(explode(',',$a['countries']));
$bc = array_flip(explode(',',$b['countries']));
switch (true) {
    case array_key_exists('EN',$ac) && !array_key_exists('EN',$bc):
        return 1;
    case array_key_exists('DE',$ac) && !array_key_exists('EN',$bc) && !array_key_exists('EN',$bc):
        return 1;
    // and so on
}

更多编辑!

其实我在复杂排序的问题上考虑的比较多,我想出了下面的解决方案,供大家参考。它将允许您根据出现在国家/地区索引中的关键字定义数字排名。这是代码,包括一个例子:

示例数组

$array = array(
    array(
        'countries' => 'EN,DE,SP',
    ),
    array(
        'countries' => 'EN,CH,SP',
    ),
    array(
        'countries' => 'DE,SP,CH',
    ),
    array(
        'countries' => 'DE,SV,SP',
    ),
    array(
        'countries' => 'EN,SP,FR',
    ),
    array(
        'countries' => 'DE,FR,CH',
    ),
    array(
        'countries' => 'CH,EN,SP',
    ),

);

排序例程

$rankings = array(
    'EN' => 10,
    'SP' => 8,
    'FR' => 7,
    'DE' => 5,
    'CH' => 3,
    'SV' => 1,
);
usort($array, function (&$a, &$b) use ($rankings) {
    if (isset($a['_score'])) {
        $aScore = $a['_score'];
    }
    else {
        $aScore = 0;
        $aCountries = explode(',',$a['countries']);
        foreach ($aCountries as $country) {
            if (isset($rankings[$country])) {
                $aScore += $rankings[$country];
            }
        }
        $a['_score'] = $aScore;
    }

    if (isset($b['_score'])) {
        $bScore = $b['_score'];
    }
    else {
        $bScore = 0;
        $bCountries = explode(',',$b['countries']);
        foreach ($bCountries as $country) {
            if (isset($rankings[$country])) {
                $bScore += $rankings[$country];
            }
        }
        $b['_score'] = $bScore;
    }
    if ($aScore == $bScore) {
        return 0;
    }
    elseif ($aScore > $bScore) {
        return -1;
    }
    else {
        return 1;
    }
});

注意:此代码会将排名最高的整体排序到数组的顶部。如果你想要反向行为,改变这个:

    elseif ($aScore > $bScore) {

    elseif ($aScore < $bScore) {

请注意,大于号已更改为小于号。进行此更改将导致排名最低的条目被排序到数组的顶部。希望这对您有所帮助!

请注意!

此代码将对您的数组进行小幅更改,因为它将 _score 元素添加到每个数组。希望这不是问题,因为通过存储这个值,我确实能够将速度提高一倍以上(在我的基准测试中,0.00038-.00041 下降到 0.00016-.00018)。如果没有,则删除检索缓存值的if 块,让else 块的内容每次都执行,当然存储分数值的部分除外。

顺便说一下,这是数组排序后的var_export() 转储:

array (
  0 => array (
    'countries' => 'EN,SP,FR',
    '_score' => 25,
  ),
  1 => array (
    'countries' => 'EN,DE,SP',
    '_score' => 23,
  ),
  2 => array (
    'countries' => 'EN,CH,SP',
    '_score' => 21,
  ),
  3 => array (
    'countries' => 'CH,EN,SP',
    '_score' => 21,
  ),
  4 => array (
    'countries' => 'DE,SP,CH',
    '_score' => 16,
  ),
  5 => array (
    'countries' => 'DE,FR,CH',
    '_score' => 15,
  ),
  6 => array (
    'countries' => 'DE,SV,SP',
    '_score' => 14,
  ),
)

享受吧!

【讨论】:

    【解决方案3】:

    终于在 PHP.net 找到了这个奇妙的功能:

            function array_msort($array, $cols)
            {
                $colarr = array();
                foreach ($cols as $col => $order) {
                    $colarr[$col] = array();
                    foreach ($array as $k => $row) { $colarr[$col]['_'.$k] = strtolower($row[$col]); }
                }
                $eval = 'array_multisort(';
                foreach ($cols as $col => $order) {
                    $eval .= '$colarr[\''.$col.'\'],'.$order.',';
                }
                $eval = substr($eval,0,-1).');';
                eval($eval);
                $ret = array();
                foreach ($colarr as $col => $arr) {
                    foreach ($arr as $k => $v) {
                        $k = substr($k,1);
                        if (!isset($ret[$k])) $ret[$k] = $array[$k];
                        $ret[$k][$col] = $array[$k][$col];
                    }
                }
                return $ret;
    
            }
    

    这是每个国家/地区的样子: $array['countries'] = in_array($needle, $haystack); }

    $array = $array = array_msort($array, array('countries'=>SORT_DESC, 'id'=>SORT_ASC));
    

    感谢大家的帮助!

    【讨论】:

    • 哎呀...eval()?完全没有必要。
    【解决方案4】:

    我目前正在创建一个由 mysql 查询中的值组成的排序方法。

    真相:
    使用 MySQL 以外的任何工具对结果集进行排序会降低效率(使用 php,usort()array_multisort() 调用将更加复杂且难以维护),因此不合适。

    SQL: (Demo)

    ORDER BY IF(LOCATE('EN', countries), 0, 1), id;
    

    这会优先考虑包含ENcountries 列值,然后按id ASC 排序。


    对于没有处理 sql 结果集或由于某种原因无法操作查询的任何人,我支持usort()。 PHP7 提供了一个漂亮的新运算符,它执行比较并返回三个值之一(-1、0、1)。这个操作员被亲切地称为“飞船操作员”,看起来像这样&lt;=&gt;

    PHP: (Demo)

    $test = [
        ['id' => 1, 'countries' => 'EN,CH,SP'],
        ['id' => 2, 'countries' => 'GE,SP,SV'],
        ['id' => 3, 'countries' => 'PR,SP,IT'],
        ['id' => 4, 'countries' => 'EN'],
        ['id' => 5, 'countries' => 'SP,EN'],
        ['id' => 6, 'countries' => 'SV,SP,EN'],
        ['id' => 7, 'countries' => 'GE,SP'],
        ['id' => 8, 'countries' => 'FR'],
        ['id' => 9, 'countries' => 'RU,EN'],
        ['id' => 10, 'countries' => 'EN,SP,IT'],
        ['id' => 11, 'countries' => 'SP,GR'],
        ['id' => 12, 'countries' => 'GR,EN']
    ];
    
    usort($test, function($a, $b) {
        return [strpos($a['countries'], 'EN') === false, $a['id']]
               <=>
               [strpos($b['countries'], 'EN') === false, $b['id']];
    });
    
    var_export($test);
    

    PHP8 替代方案:

    return [str_contains($b['countries'], 'EN'), $a['id']]
           <=>
           [str_contains($a['countries'], 'EN'), $b['id']];
    

    return str_contains($b['countries'], 'EN') <=> [str_contains($a['countries'], 'EN')
           ?: $a['id']] <=> $b['id']];
    

    输出:

    array (
      0 => 
      array (
        'id' => 1,
        'countries' => 'EN,CH,SP',
      ),
      1 => 
      array (
        'id' => 4,
        'countries' => 'EN',
      ),
      2 => 
      array (
        'id' => 5,
        'countries' => 'SP,EN',
      ),
      3 => 
      array (
        'id' => 6,
        'countries' => 'SV,SP,EN',
      ),
      4 => 
      array (
        'id' => 9,
        'countries' => 'RU,EN',
      ),
      5 => 
      array (
        'id' => 10,
        'countries' => 'EN,SP,IT',
      ),
      6 => 
      array (
        'id' => 12,
        'countries' => 'GR,EN',
      ),
      7 => 
      array (
        'id' => 2,
        'countries' => 'GE,SP,SV',
      ),
      8 => 
      array (
        'id' => 3,
        'countries' => 'PR,SP,IT',
      ),
      9 => 
      array (
        'id' => 7,
        'countries' => 'GE,SP',
      ),
      10 => 
      array (
        'id' => 8,
        'countries' => 'FR',
      ),
      11 => 
      array (
        'id' => 11,
        'countries' => 'SP,GR',
      ),
    )
    

    从左到右计算 spaceship 运算符两侧的数组元素(左侧 [0] 与右侧 [0],如果两者之间存在“平局”,则移动到 [1] 值对[0] 个值)。

    如果=== false 向后看,让我解释一下...

    如果在国家字符串中找到EN,则条件将评估为false。在比较 truefalse 时,请记住 true 等于 1 和 false 等于 0。我们想要 ASC 排序,因此我们希望将错误结果放在真实结果之前,因此包含 EN 需要返回false。希望这能理清逻辑。

    【讨论】:

      猜你喜欢
      • 2023-03-25
      • 2016-11-07
      • 2021-05-03
      • 2022-01-04
      • 2020-09-03
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      相关资源
      最近更新 更多