PHP performant搜索给定用户名的文本

时间:2015-01-30 12:31:59

标签: php regex performance search

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

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

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

  1. 浏览用户名列表(可能是> 1.000或更多名称)并搜索所有@Username而不使用正则表达式,只需搜索字符串。我会说这会效率低得多。
  2. 如果用户名内有空格或准点符号,请检查用JavaScript编写用户名,然后用引号括起来。喜欢@“用户名”。不喜欢这个想法,对用户来说看起来很脏。
  3. 不要从一个角色开始,但可能是4.如果不匹配,请返回。与排序算法相同的原理。分而治之。可能很难实施,也许什么都不会导致。
  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);
    }
    

    你会走哪条路?问题是文本会经常显示并且每次都会解析它会花费很多时间,但我不想大量修改用户输入的文本。

    我无法找出最好的方法,甚至为什么我的功能如此耗时。

    示例文本为:

      

    好的,@姓名姓氏,我提到你了!   听@ [TEAM]约翰,你是团队成员。   @Test是一个普通的名字,但@Thât♥也应该被跟踪。   看看@Wolfs花园!我只是指狼。

    该文本中的用户名将是

    • 名字姓氏
    • [TEAM] John
    • 测试
    • 该♥

    所以,是的,我知道名字可能会以何处结束。唯一的问题是新行。

1 个答案:

答案 0 :(得分:2)

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

的一部分

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

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

$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]);
}
  

阵   (       [0] =>名字姓氏       1 => [团队]约翰       2 =>测试       3 =>那♥       4 =>沃尔夫斯   )

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 FAQTest at eval.in链接即将过期