【问题标题】:How can I do a text search that ignores control characters?如何进行忽略控制字符的文本搜索?
【发布时间】:2013-03-08 21:55:08
【问题描述】:

我有一个源字符串,其中可能包含任何字符,包括空格、回车和换行符(控制字符)。控制字符可以出现在任何地方,包括单词的中间。

我有一个搜索字符串,它可能具有与源字符串相同的字符选择,但通常是源的子字符串。此搜索字符串中控制字符的顺序和数量可能与来源不同。

当搜索字符串中的非控制字符匹配时,我需要从源字符串中删除该字符串,包括字符串中的任何控制字符。不应删除源字符串中其他地方的控制字符。

我的计划是在搜索字符串中的每个字符后添加\s*。这很好,但是我需要转义搜索字符串中的任何正则表达式特殊字符,否则它们将被视为正则表达式命令,而不是它们真正的纯文本。

我可以在每个字符 ('mytext.scan(/./).join("\\s*")') 之后添加\s*,但是如何转义特殊字符而不是我插入的正则表达式代码?如果我反过来做,那么我可以转义正则表达式特殊字符,但我不能简单地在每个字符后添加\s*;我需要避免转义字符。

为了清楚起见 控制字符 = 空格或 \t 或 \r 或 \n 或 \f

编辑:修改了第 3 段以提高我的要求的清晰度

【问题讨论】:

  • 复制源字符串和搜索字符串。消除两个副本中的所有控制字符。在源字符串的副本中使用搜索字符串的副本进行搜索。如果需要(或重音删除,或...),您也可以进行大小写转换。使用大量 \s* 可能会大大减慢您的正则表达式。
  • @Jonathan Leffler 但是你如何重做原始字符串的替换?
  • 搜索字符串只需要复制和预处理一次。每次都需要复制和预处理源字符串。如果最坏的情况出现在最坏的情况下,当您知道有匹配项时,您可以返回原始源字符串并制作搜索字符串的新副本,以便在每个常规字符之间确实有类似 \s* 的内容,并且将搜索字符串的第二个(残缺的)副本中的正则表达式应用于原始源字符串。因为你知道有一个匹配,所以性能应该是合理的,即使失败匹配模式太慢了。
  • @Patashu 是的,这是解决方案呈现方式的问题,我需要在删除搜索文本后维护原始字符串
  • @jonathan 正则表达式速度不是问题,尽管我感谢您的观察。带着你的两个 cmets,我仍然在努力寻找在找到匹配项后如何得到包含控制字符的原始字符串。不过,我非常感谢您的帮助。

标签: ruby regex search replace special-characters


【解决方案1】:

或多或少在 cmets 中讨论过:

复制源字符串和搜索字符串。消除两个副本中的所有控制字符。在源字符串的副本中使用搜索字符串的副本进行搜索。如果需要(或重音删除,或...),您也可以进行大小写转换。大量使用\s* 可能会大大减慢您的正则表达式。

搜索字符串只需要复制和预处理一次。每个源字符串也需要复制和预处理一次。如果最坏的情况发生在最坏的情况下,当您知道有匹配项时,您可以返回原始源字符串并制作搜索字符串的新副本,以便在每个常规字符之间确实有类似 \s* 的内容,并且将搜索字符串的第二个(残缺的)副本中的正则表达式应用于原始源字符串。因为你知道有一个匹配,所以性能应该是合理的,即使失败匹配模式太慢了。

这是所讨论想法的 Perl 实现。

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

$Data::Dumper::Useqq = 1;

my $source = "'Twas (Tweedle-Dee's)\fBirthday\n\n\f\f\nand\ta\tl\tl\this friends were happy\n";
my $search = "(\fTwee\ndle\t-\tDee\r'\rs)\nBi\frth\fday";

print Data::Dumper->Dump([$source], [qw($source)]);
print Data::Dumper->Dump([$search], [qw($search)]);

my $c_source = $source;
my $c_search = $search;

$c_source =~ s/ |[[:cntrl:]]//g;    # Or s/\s//g;
$c_search =~ s/ |[[:cntrl:]]//g;    # Or s/\s//g;

print Data::Dumper->Dump([$c_source], [qw($c_source)]);
print Data::Dumper->Dump([$c_search], [qw($c_search)]);

if ($c_source =~ m/\Q$c_search\E/)
{
    # Locating the search in the original source...hard work...
    my @a_search = split //, $c_search;
    printf "Lengths: c_search %d; a_search %d\n", length($c_search), scalar(@a_search);

    @a_search = map { s/[][\\.*?+(){}]/\\$&/g; $_ } @a_search;   # Escape regex metacharacters
    #print Data::Dumper->Dump([\@a_search], [qw(@a_search)]);
    my $r_search = join "\\s*", @a_search;
    print Data::Dumper->Dump([$r_search], [qw($r_search)]);

    my $t_source = $source;
    $t_source =~ s/$r_search//g;
    print Data::Dumper->Dump([$t_source], [qw($t_source)]);
}

好干净的象形文字乐趣——毫无疑问,像泥巴一样清晰。前三行检查没有任何愚蠢的错误。 Data::Dumper 模块明确打印数据;它在那里进行调试。 Useqq 变量可以明确地调整数据的打印方式。

变量$source$search 是源字符串和搜索字符串。有一个匹配,尽管它们每个都有所有控制字符。请注意,混合中有一些正则表达式元字符——括号是正则表达式元字符。这些字符串被转储以供参考。

接下来的两行复制了搜索字符串和源字符串。控制字符和空格被删除,使用基于 POSIX 的正则表达式类来指定所有控制字符。这些转换后的字符串被转储以供检查。

if 语句将转换后的源与转换后的搜索进行比较。 \Q...\E 部分抑制了正则表达式元字符之间的含义。如果匹配,则我们在大括号中输入代码块。

split 操作从转换后的搜索字符串中创建单个字符数组。 printf 检查理智。 map 操作将每个正则表达式元字符替换为反斜杠和元字符,而其他字符保持不变。 join 将数组@a_search 中的每个字符或字符对收集到一个字符串$r_search 中,\s* 分隔数组条目。

变量$t_source 是源的另一个副本。 $r_search 中的正则表达式应用于$t_search,并且任何匹配项都被替换为空。结果被转储。该脚本的输出是:

$source = "'Twas (Tweedle-Dee's)\fBirthday\n\n\f\f\nand\ta\tl\tl\this friends were happy\n";
$search = "(\fTwee\ndle\t-\tDee\r'\rs)\nBi\frth\fday";
$c_source = "'Twas(Tweedle-Dee's)Birthdayandallhisfriendswerehappy";
$c_search = "(Tweedle-Dee's)Birthday";
Lengths: c_search 23; a_search 23
$r_search = "\\(\\s*T\\s*w\\s*e\\s*e\\s*d\\s*l\\s*e\\s*-\\s*D\\s*e\\s*e\\s*'\\s*s\\s*\\)\\s*B\\s*i\\s*r\\s*t\\s*h\\s*d\\s*a\\s*y";
$t_source = "'Twas \n\n\f\f\nand\ta\tl\tl\this friends were happy\n";

字符串$t_source确实对应$source,去掉了'(Tweedle-Dee's) Birthday',这似乎符合要求。

将其转换为 Ruby 留给受虐狂^H^H^H^H^H^H^H^H^H^H^H 感兴趣的读者作为练习。

显然,您可以简单地创建并使用$r_search 字符串作为正则表达式,并将其直接应用于$source(的副本);它会起作用的。但我非常怀疑,如果将它应用于千字节长度的源字符串,代码运行速度会非常慢。不过,我还没有进行测量来证明这一点。

【讨论】:

  • 谢谢乔纳森,经过一些睡眠和您非常完整的回复,我现在明白您在原始答案中所说的内容。我将运行一些时间来查看 ruby​​ 对性能的影响并在此处发回。
【解决方案2】:

一种天真的方法是

1) 将搜索字符串拆分为单个字符的列表(每个字符都是一个字符串)

2) 清理每个单独的字符(仍然是字符串列表)

3) 通过\s**加入列表

*除了\s* 将不起作用,顺便说一下 - \s* 将匹配 0 个或多个空格,这与 0 个或多个控制字符不同。请参阅http://www.regular-expressions.info/posixbrackets.html#class,并使用符合您的正则表达式风格的“控制字符”形式:)

\W* 也可以工作,因为\W 是不在 a-zA-Z0-9_ 中的任何字符。但我从未测试过它是否匹配控制字符或仅可打印字符。

【讨论】:

  • 这是一个不错的解决方案,错过了简单的解决方案,呵呵!额外的处理不是一个大问题。我将阅读并验证用于匹配的正确正则表达式。感谢您的快速响应
  • \w 等价于[[:alnum:]_] 而不是[a-zA-Z0-9]
猜你喜欢
  • 2011-07-07
  • 1970-01-01
  • 1970-01-01
  • 2014-02-17
  • 2012-07-14
  • 1970-01-01
  • 1970-01-01
  • 2019-03-01
  • 1970-01-01
相关资源
最近更新 更多