匹配并替换字符串中的表情符号 - 最有效的方法是什么?

3

维基百科 定义了许多可能使用的表情符号。我想将这个列表与字符串中的单词匹配。我现在有如下代码:

$string = "Lorem ipsum :-) dolor :-| samet";
$emoticons = array(
  '[HAPPY]' => array(' :-) ', ' :) ', ' :o) '), //etc...
  '[SAD]'   => array(' :-( ', ' :( ', ' :-| ')
);
foreach ($emoticons as $emotion => $icons) {
  $string = str_replace($icons, " $emotion ", $string);
}
echo $string;

输出:

Lorem ipsum [HAPPY] dolor [SAD] samet

原则上这是可行的。然而,我有两个问题:

  1. 如您所见,我在数组中每个表情符号周围都加了空格,例如' :-) '而不是':-)'。我认为这使得数组不太易读。有没有一种方法可以存储没有空格的表情符号,但仍然可以匹配带有空格的$string?(并且与现在的代码一样高效?)

  2. 或者也许有一种方法可以将表情符号放入一个变量中,并在空格上分裂以检查$string?类似于

    $emoticons = array( '[HAPPY]' => ">:] :-) :) :o) :] :3 :c) :> =] 8) =) :} :^)", '[SAD]' => ":'-( :'( :'-) :')" //等等...

  3. str_replace是最有效的方法吗?

我问这个问题是因为我需要检查数百万个字符串,所以我正在寻找最有效的方法来节省处理时间 :)


1
你难道不应该处理转换成ASCII的表情符号吗?现在网站上超过80%都是Unicode编码了。Unicode中有一个完整的区块专门用于这些内容:Emoticons。但有些表情符号可能出现在其他地方。 - tchrist
@Li-aungYip 哈哈,这个不错!不,我的意思是像 U+1F609 眨眼的表情 和 U+263A 微笑的表情 ☺ 这样的码点。它们中的大多数都在 Emotions 块中(就像上面两个中的第一个),只有很少数在传统的 BMP 中。 - tchrist
@tchrist 不,我只关心在http://en.wikipedia.org/wiki/List_of_emoticons上定义的西方表情符号,但还是谢谢您的建议 :) - Pr0no
不幸的是,Ubuntu默认字体尚未包含该块中的所有代码点,因此我可以得到U+263A,但U+1F609是一个方框。我想知道Win7上的支持情况如何?;) - Li-aung Yip
这里的Win7没有眨眼表情符号。问题在于没有合适的备用字体。 - Kawa
5个回答

5

使用Perl第三方模块Regexp::Assemble(从CPAN获取)的想法如下。例如,给定以下程序:

#!/usr/bin/env perl
use utf8;
use strict;
use warnings;

use Regexp::Assemble;

my %faces = (
    HAPPY => [qw¡ :-) :) :o) :-} ;-} :-> ;-} ¡],
    SAD   => [qw¡ :-( :( :-| ;-) ;-( ;-< |-{ ¡],
);

for my $name (sort keys %faces) {
    my $ra = Regexp::Assemble->new();
    for my $face (@{ $faces{$name} }) {
        $ra->add(quotemeta($face));
    }
    printf "%-12s => %s\n", "[$name]", $ra->re;
}

它将输出这个:
[HAPPY]      => (?-xism:(?::(?:-(?:[)>]|\})|o?\))|;-\}))
[SAD]        => (?-xism:(?::(?:-(?:\||\()|\()|;-[()<]|\|-\{))

这里有一些你可能不需要的额外内容,所以这些可以简化为:

[HAPPY]      => (?:-(?:[)>]|\})|o?\))|;-\}
[SAD]        => (?:-(?:\||\()|\()|;-[()<]|\|-\{

或者更少。你可以将其构建到你的Perl程序中以修剪额外的部分。然后,你可以直接将右侧放入你的preg_replace中。

我使用use utf8的原因是为了使用¡作为我的qw//分隔符,因为我不想在其中逃避问题。

如果整个程序都是用Perl编写的话,你就不需要这样做,因为现代版本的Perl已经自动为你处理了。但学会使用该模块仍然很有用,因为你可以生成用于其他语言的模式。


@Li-aungYip 这只是冰山一角,你要记住和谁在说话,你知道的。 - tchrist
哦,糟了...(至少你不是《精通正则表达式》的作者。现在我必须在SO上注意一下是否有“jfriedl”...) - Li-aung Yip
@Li-aungYip 要真正掌握正则表达式,你需要了解现代模式匹配技术,而Jeffrey的MRE尚未涵盖这些内容。请参考此答案,了解我所说的内容类型:命名组(比Python中更灵活的组),递归模式和语法模式。 - tchrist

3
这似乎是正则表达式的一个好应用,正则表达式是一种模糊文本匹配和替换工具。 str_replace 是一种精确的文本搜索和替换工具;正则表达式可以让您搜索整个类别的“看起来像这样”的文本,其中 this 是根据接受哪些字符,有多少个字符,以什么顺序等定义的。
如果您使用正则表达式,则可以:
  1. \ s 通配符将匹配空格,因此您可以匹配 \ s $ emotion \ s

    (还要考虑表情符号出现在字符串末尾的情况 - 即那很有趣lol :) - 您不能总是假设表情符号周围有空格。您可以编写处理此问题的正则表达式。)

  2. 您可以编写一个正则表达式来匹配列表中的任何表情符号。您可以使用交替符号 | 来执行此操作,您可以将其视为 OR 符号。语法为(a | b | c)以匹配模式 a b c

    例如,(:\)|: - \)|:o \)将匹配任何一个 :),: - ),:o)。请注意,我必须转义,因为它们在正则表达式内具有特殊含义(括号用作分组运算符。)

  3. 过早优化是万恶之源。

    首先尝试最明显的事情。如果不起作用,您可以稍后进行优化(在分析代码以确保这确实会给您带来有形的性能收益之后。)

如果要学习正则表达式,请尝试 TextWrangler手册的第8章。这是一份非常易于理解的正则表达式用法和语法介绍。
注意:我的建议与编程语言无关。我的PHP-fu比我的Python-fu弱得多,因此我无法提供示例代码。 :(

2

我建议先尝试最简单的实现,使用str_replace和包含空格的数组。如果性能不佳,则尝试每种情绪使用一个正则表达式。这样可以大大压缩内容:

$emoticons = array(
  '[HAPPY]' => ' [:=]-?[\)\]] ', 
  '[SAD]'   => ' [:=]-?[\(\[\|] '
);

如果性能仍然不可接受,可以使用更高级的方法,比如后缀树(参见:http://en.wikipedia.org/wiki/Suffix_tree),它允许您仅扫描一次字符串以查找所有表情符号。这个概念很简单,你有一个树,根是一个空格(因为你想在表情符号前匹配一个空格),第一个子节点是 ':' 和 '=',然后 ':' 的子节点是 ']', ')', '-' 等等。您只需要用循环逐个字符扫描字符串。当遇到空格时,您就会进入树的下一层,然后查看下一个字符是否是该层的一个节点(':'或'='),如果是,则继续向下一层移动,以此类推。如果在任何时候当前字符不是当前层的一个节点,则返回根。

一个后缀树/有限状态机将是一个非常优雅的解决方案。赞。 (但在这种情况下,它不是前缀树吗?;)) - Li-aung Yip
计算机科学自从“动态规划”(实际上并不是一种编程类型)以来,就一直给事物起着令人费解的名称。 - Li-aung Yip
你想使用Perl [Regexp :: Assemble](http://search.cpan.org/perldoc?Regexp::Assemble)模块对一组模式运行分析,以创建前缀/后缀树表示为正则表达式。然后,您可以将优化后的结果正则表达式插入任何编程语言中。这对于那些不够聪明以像Perl一样使用TRIE表示的语言特别有用。 - tchrist
@tchrist:将其简化为正则表达式能让你区分出你要匹配的哪种表情符号吗?也就是说,你是否仍然可以使用一个preg_replace():)替换为[HAPPY],将:(替换为[SAD] - Li-aung Yip
在产生最小可能的有限状态机方面可以证明是最优的吗? - Li-aung Yip
显示剩余2条评论

2

简介注释:请一次只提一个问题,这样你会得到更好的答案。此外,如果您不向我们展示迄今为止所做的指标,就无法获得良好的性能建议。

从我能看到的代码中,您进行了两次字符串处理,可以节省,将替换放入特定的空格中。您可以先展开它:

$emoticons = array(
  ' [HAPPY] ' => array(' :-) ', ' :) ', ' :o) '), //etc...
  ' [SAD] '   => array(' :-( ', ' :( ', ' :-| ')
);

foreach ($emoticons as $replace => $search)
{
  $string = str_replace($search, $replace, $string);
}

每次调用它时,这将为您节省一些微秒的时间,这将提高性能,但您可能不会注意到。这就是为什么您应该使用C编写并编译它的原因。

更接近C的方法是使用一次编译的正则表达式,然后重复使用它,另一个答案已经建议过了。这里的好处是,如果您多次运行相同的表达式,并且可以预先生成正则表达式,那么您可以在PHP中以最快的方式完成此操作,因此您可以将其存储在更容易编辑的格式中。然后,您可以缓存正则表达式,以防您需要调整性能。

1. 正如您所看到的,我正在数组中每个表情符号周围放置空格,例如' :-) '而不是':-)'。我认为这使得数组不太可读。是否有一种方法可以存储没有空格的表情符号,但仍然针对$ string与它们周围的空格匹配?(并且与代码现在一样有效?)

是的,这是可能的,但不是更有效地处理配置数据为替换数据。不知道你真正谈论的效率类型,但我假设是后者,所以答案是,可能,但不适合您非常特殊的用例。通常,我更喜欢更容易编辑的东西,因此您更有效地处理它而不是关心处理速度,因为通过将处理分布在多台计算机上,可以相当好地缩短处理速度。

2. 或者也许有一种方法可以将表情符号放入一个变量中,并在空格上分裂以检查$ string?像这样

$emoticons = array('[HAPPY]' => ">:] :-) :) :o) :] :3 :c) :> =] 8) =) :} :^)", '[SAD]' => ":'-( :'( :'-) :')" //etc...

当然,这是可能的,但您会遇到与1中相同的问题。

3. 使用str_replace是最有效的方法吗?

好吧,现在使用您提供的代码,这是您询问的唯一方法。由于没有您告诉我们的替代方法,因此至少对您来说它是有效的,这是目前为止完成此操作的最有效方法。所以现在,是的。


你肯定不会期望将编译好的C代码嵌入到PHP应用程序中吧?虽然这是可行的,但对于初学者或任何想保持理智的人来说都不是一个好主意。 - Li-aung Yip
实际上,PHP是C编译函数的接口。由于OP要求性能,我认为这个建议并不遥远。然而,我并没有建议将C烘培到PHP应用程序中,而是建议他如果性能至关重要,则应完全使用C进行操作。但这只是答案中的一个非常小的点,如果OP想留在PHP中(正如你也做了),我在这里概述了使用正则表达式的替代方案。 - hakre
我的编程经验现在只限于一些php - 我不会考虑为此目的编写编译代码。假设我想在php的脚本环境中优化性能 :) 但还是谢谢你的建议! - Pr0no
如果您在同一脚本执行中多次使用相同的正则表达式模式,我猜preg_replace在您的情况下会是最快的。但是您需要进行度量以了解确切情况。 - hakre

2
如果您想替换表情符号的$string是由您网站的访问者提供的(我指的是用户输入,例如评论或其他内容),则不应该依赖于表情符号前后是否有空格。此外,至少有几个表情符号非常相似但不同,例如 :-) 和 :-))。因此,我认为如果您将表情符号数组定义为以下格式,您将获得更好的结果:
$emoticons = array(
    ':-)' => '[HAPPY]',
    ':)' => '[HAPPY]',
    ':o)' => '[HAPPY]',
    ':-(' => '[SAD]',
    ':(' => '[SAD]',
    ...
)

当您填写所有的查找/替换定义时,应该按一定方式重新排列此数组,以确保不会将 :-)) 替换为 :-)。我相信,如果您按长度对数组值进行排序就足够了。这是在您使用 str_replace() 的情况下。而如果您使用 strtr(),则会自动按长度排序!
如果您担心性能问题,可以查看 strtr vs str_replace,但我建议进行自己的测试(根据您的 $string 长度和查找/替换定义,结果可能有所不同)。
最简单的方法是,如果您的“查找定义”不包含尾随空格:
$string = strtr( $string, $emoticons );
$emoticons = str_replace( '][', '', trim( join( array_unique( $emoticons ) ), '[]' ) );
$string = preg_replace( '/\s*\[(' . join( '|', $emoticons ) . ')\]\s*/', '[$1]', $string ); // striping white spaces around word-styled emoticons

我以前从未遇到过 :-)) 这个符号。它是什么意思? - Li-aung Yip
原来非常开心的人会使用这个。在提供的维基百科列表中发现,似乎@Reveller将其作为参考。 我之前不知道这个表情符号,可能是因为即使是我最强烈的情感也只用“:)”来表达。 - Boris Belenski

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