自定义排序字符串 - 如果以特定字符串开头,则按字母顺序排序

7

我希望能在PHP中对字符串进行排序,首先应该根据子字符串的第一个字母进行匹配,然后再根据整个字符串的每个字母进行匹配。

例如,如果有人搜索“do”,并且列表包含:

Adolf
Doe
Done

结果应该是:

Doe
Done
Adolf

使用常规的sort($array, SORT_STRING)或类似方法无法解决此问题,Adolf会排在其他人之前。

有没有什么好的办法可以解决这个问题呢?


1
你不能用简单的搜索完成它。我建议你创建多个列表,针对你要查找的每个位置,然后对这些子列表进行排序。 - Tchoupi
@user1603166,你的问题有点含糊不清。从@Roman的例子中可以看出,如果列表还包括“Odometer”和“Abdomen”,应该如何排序? - Matthew
4个回答

3

usort(数组, 回调函数) 可以通过回调函数来进行排序。

示例(类似于以下内容,未尝试过)

usort($list, function($a, $b) {
   $posa = strpos(tolower($a), 'do');
   $posb = strpos(tolower($b), 'do');
   if($posa != 0 && $posb != 0)return strcmp($a, $b);
   if($posa == 0 && $posb == 0)return strcmp($a, $b);
   if($posa == 0 && $posb != 0)return -1;
   if($posa != 0 && $posb == 0)return 1;
});

我不理解你的回答。好的,让我自己用一个函数来排序,但问题仍然是在这种情况下排序函数会把Adolf排在Doe之前。 - user1603166
根据在usort()内进行的比较次数,这可能会变得相当繁重 :) - Ja͢ck
@user Matthews的回答更加详细。我的只是一个例子,作为一个起点。 - Roman
@Jack,你说得很有道理,但是你的答案可能有类似数量的比较。然而,你正在缓存stripos()的输出,因此你在节省CPU时间的同时增加了内存消耗。对于大型列表,肯定值得对这两种风格进行基准测试。对于小型列表,差异将是无关紧要的。 - Matthew

3
我会使用自定义排序:
<?php
$list = ['Adolf', 'Doe', 'Done'];

function searchFunc($needle)
{
  return function ($a, $b) use ($needle)
  { 
    $a_pos = stripos($a, $needle);
    $b_pos = stripos($b, $needle);

    # if needle is found in only one of the two strings, sort by that one
    if ($a_pos === false && $b_pos !== false) return 1;
    if ($a_pos !== false && $b_pos === false) return -1;

    # if the positions differ, sort by the first one
    $diff = $a_pos - $b_pos;
    # alternatively: $diff = ($b_pos === 0) - ($a_pos === 0) 
    if ($diff) return $diff;

    # else sort by natural case
    return strcasecmp($a, $b);

  };
}

usort($list, searchFunc('do'));

var_dump($list);

输出:

array(3) {
  [0] =>
  string(3) "Doe"
  [1] =>
  string(4) "Done"
  [2] =>
  string(5) "Adolf"
}

1
+1。尽管 OP 应该意识到,在这里 Odometer 将会在 Abdomen 之前列出,这可能是可取的,也可能不是。 - Roman
@Roman,我认为这就是搜索的重点。但如果不是,删除$diff检查和return将消除该行为。 - Matthew
不知道,我猜它是被某种“自动完成”功能使用的,在这种情况下,我更喜欢将所有结果按字母顺序排序,而不是以$needle开头的结果。 - Roman
@Roman,他的例子有歧义。虽然我同意你的偏好。如果他期望您描述的行为,则我认为只需将代码更改为 $diff = ($b_pos === 0) - ($a_pos === 0); 就足够了。 - Matthew

0
在现代PHP中,编写两个规则可以比过去更简洁地完成。
按顺序排列:
  1. do 开头 -- 假的评估在真的评估之前
  2. 通过不区分大小写地比较整个字符串来打破第一个规则的平局
代码:(演示
$array = [
    'Adolf',
    'Doe',
    'adept',
    'Done',
    'dear',
    'adopt',
    'Deer'
];

$startsWith = 'do';

usort(
    $array,
    fn($a, $b) =>
        (stripos($a, $startsWith) !== 0) <=> (stripos($b, $startsWith) !== 0)
        ?: strcasecmp($a, $b)
);
var_export($array);

比之前的方法更高效(因为函数调用次数更少),我推荐使用array_multisort()。最终参数($array)是最终被该函数修改的变量。
代码:(演示
array_multisort(
    array_map(fn($v) => stripos($v, $startsWith) !== 0, $array),
    $array,
    SORT_STRING | SORT_FLAG_CASE,
    $array
);

0

您可以根据 stripos($str, $search) 对字符串进行排序,这样前面的字符串(stripos() == 0)将首先出现。

以下代码将搜索字符串的子字符串位置推入单独的数组中,然后使用 array_multisort() 对匹配项应用正确的排序;以这种方式而不是使用 usort() 可避免多次调用 stripos()

$k = array_map(function($v) use ($search) {
    return stripos($v, $search);
}, $matches);

// $k contains all the substring positions of the search string for all matches

array_multisort($k, SORT_NUMERIC, $matches, SORT_STRING);

// $matches is now sorted against the position

1
这是一个聪明的解决方案,但如果列表中有不包含“$search”的字符串,则会失败。stripos()将返回false,它被等同于0。(如果数组映射返回一个巨大的数字而不是false,很容易纠正。) - Matthew
@Matthew 我假设匹配已经使用grep或其他方式完成了 :) - Ja͢ck
当然,理想情况下应该在确定位置的同时完成;-)让我考虑一下。 - Ja͢ck
1
дҪ еҸҜиғҪжҳҜеҜ№зҡ„пјҢжҗңзҙўжҳҜдёҖдёӘ SELECT ... WHERE name LIKE '%$string%'пјҢеҰӮжһңжҳҜиҝҷж ·пјҢдҪ зҡ„зӯ”жЎҲе°ұи¶іеӨҹдәҶгҖӮжҲ‘еҸӘжҳҜдёәдәҶе®Ңж•ҙжҖ§иҖҢжҢҮеҮәе®ғгҖӮ - Matthew

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