PHP 高性能搜索文本中的给定用户名

标签 php regex performance search

我目前正在处理性能问题,但找不到解决方法。我想在文本中搜索前面带有 @ 符号的用户名。用户名列表以 PHP 数组形式提供。

问题是用户名可能包含空格或其他特殊字符。对此没有任何限制。所以我找不到处理这个问题的正则表达式。 目前,我正在使用一个函数,该函数获取 @ 之后的整行,并逐个字符地检查哪些用户名可以与该提及相匹配,直到只剩下一个与该提及完全匹配的用户名。但对于包含 5 次提及的长文本,需要几秒钟(!!!)才能完成。超过 20 次提及后,脚本会无休止地运行。

我有一些想法,但不知道是否可行。

  1. 遍历用户名列表(可能>1.000个名称或更多)并搜索所有@Username,无需正则表达式,只需字符串搜索。我想说这效率会低得多。
  2. 检查使用 JavaScript 编写用户名时用户名内是否有空格或标点符号,然后用引号将其引起来。如@“用户名”。不喜欢这个想法,这对用户来说看起来很脏。
  3. 不要以 1 个字符开头,可以从 4 个字符开始。如果不匹配,则返回。与排序算法的原理相同。分而治之。可能很难实现,而且可能一事无成。

Facebook 或 Twitter 以及任何其他网站如何做到这一点?他们是否在直接在消息的存储文本中键入并保存提到的用户名时直接解析文本?

这是我当前的功能:

$regular_expression_match = '#(?:^|\\s)@(.+?)(?:\n|$)#';
$matches = false;
$offset = 0;

while (preg_match($regular_expression_match, $post_text, $matches, PREG_OFFSET_CAPTURE, $offset))
{
    $line = $matches[1][0];
    $search_string = substr($line, 0, 1);
    $filtered_usernames = array_keys($user_list);
    $matched_username = false;

    // Loop, make the search string one by one char longer and see if we have still usernames matching
    while (count($filtered_usernames) > 1)
    {
        $filtered_usernames = array_filter($filtered_usernames, function ($username_clean) use ($search_string, &$matched_username) {
            $search_string = utf8_clean_string($search_string);

            if (strlen($username_clean) == strlen($search_string))
            {
                if ($username_clean == $search_string)
                {
                    $matched_username = $username_clean;
                }
                return false;
            }

            return (substr($username_clean, 0, strlen($search_string)) == $search_string);
        });

        if ($search_string == $line)
        {
            // We have reached the end of the line, so stop
            break;
        }
        $search_string = substr($line, 0, strlen($search_string) + 1);
    }

    //  If there is still one in filter, we check if it is matching
    $first_username = reset($filtered_usernames);
    if (count($filtered_usernames) == 1 && utf8_clean_string(substr($line, 0, strlen($first_username))) == $first_username)
    {
        $matched_username = $first_username;
    }

    // We can assume that $matched_username is the longest matching username we have found due to iteration with growing search_string
    // So we use it now as the only match (Even if there are maybe shorter usernames matching too. But this is nothing we can solve here,
    // This needs to be handled by the user, honestly. There is a autocomplete popup which tells the other, longer fitting name if the user is still typing,
    // and if he continues to enter the full name, I think it is okay to choose the longer name as the chosen one.)
    if ($matched_username)
    {
        $startpos = $matches[1][1];

        // We need to get the endpos, cause the username is cleaned and the real string might be longer
        $full_username = substr($post_text, $startpos, strlen($matched_username));
        while (utf8_clean_string($full_username) != $matched_username)
        {
            $full_username = substr($post_text, $startpos, strlen($full_username) + 1);
        }

        $length = strlen($full_username);
        $user_data = $user_list[$matched_username];

        $mentioned[] = array_merge($user_data, array(
            'type'          => self::MENTION_AT,
            'start'         => $startpos,
            'length'        => $length,
        ));
    }

    $offset = $matches[0][1] + strlen($search_string);
}

你会走哪条路?问题是文本会经常显示,每次解析它会消耗大量时间,但我不想大量修改用户输入的文本内容。

我不知道什么是最好的方法,甚至不知道为什么我的函数如此耗时。

示例文本如下:

Okay, @Firstname Lastname, I mention you! Listen @[TEAM] John, you are a team member. @Test is a normal name, but @Thât♥ should be tracked too. And see @Wolfs garden! I just mean the Wolf.

该文本中的用户名是

  • 名字姓氏
  • [团队]约翰
  • 测试
  • 那个♥

所以,是的,我显然不知道名字的结尾。唯一的问题是换行符。

最佳答案

我认为主要问题是,你无法区分用户名和文本,在文本中查找可能有数千个用户名是一个坏主意,这也可能导致进一步的问题,John[TEAM] John‌JohnFoo...

的一部分

需要将用户名与其他文本分开。假设您使用的是 UTF-8,可以将用户名放入不可见的零 w 空间 \xE2\x80\x8B和非加入者\xE2\x80\x8C .

现在可以快速轻松地提取用户名,并且如果需要,仍然可以在数据库中进行验证。

$txt = "
Okay, @\xE2\x80\x8BFirstname Lastname\xE2\x80\x8C, I mention you!
Listen @\xE2\x80\x8B[TEAM] John\xE2\x80\x8C, you are a team member.
@\xE2\x80\x8BTest\xE2\x80\x8C is a normal name, but 
@\xE2\x80\x8BThât?\xE2\x80\x8C should be tracked too.
And see @\xE2\x80\x8BWolfs\xE2\x80\x8C garden! I just mean the Wolf.";

// extract usernames
if(preg_match_all('~@\xE2\x80\x8B\K.*?(?=\xE2\x80\x8C)~s', $txt, $out)){
  print_r($out[0]);
}

Array ( [0] => Firstname Lastname 1 => [TEAM] John 2 => Test 3 => Thât♥ 4 => Wolfs )

echo $txt;

Okay, @​Firstname Lastname, I mention you!
Listen @​[TEAM] John‌, you are a team member.
@​Test‌ is a normal name, but 
@​Thât♥‌ should be tracked too.
And see @​Wolfs‌ garden! I just mean the Wolf.

可以使用任何您喜欢且可能不会出现在其他地方的字符进行分隔。

Regex FAQ , Test at eval.in (链接即将过期)

关于PHP 高性能搜索文本中的给定用户名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28236015/

相关文章:

performance - 在 Java 11 中使用堆栈跟踪明显慢于 Java 8

algorithm - 递归算法中的基本情况和时间复杂度

c++ - 欧拉计划 3(性能)

php - 页面一直自动刷新

php - MySQL 多表与行

php - 引用:什么是变量范围,哪些变量可从何处访问,什么是“ undefined variable ”错误?

php - 使用单个输入将 2 个 csv 文件上传到 mysql 表中?这是可能的?

python - Python 中 numpy 字符串数组的功能屏蔽

php - 从php中的字符串替换br标签

javascript - 接受 CTRL + A 按键吗?