【问题标题】:Skipping particular positions in a string using substitution operator in perl在 perl 中使用替换运算符跳过字符串中的特定位置
【发布时间】:2012-08-11 11:56:39
【问题描述】:

昨天,我陷入了 perl 脚本。让我简化一下,假设有一个字符串(比如 ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD),首先我必须在“E”出现的每个位置断开它,其次,在用户想要的位置专门断开它。但是,条件是,程序不应该在 E 后面跟着 P 的位置进行切割。例如,这个序列中有 6 个 E,所以应该得到 7 个片段,但是由于 2 个 E 后面跟着 P,所以只能得到 5 个片段输出中的片段。

我需要关于第二种情况的帮助。假设用户不想在序列中 E 的第 5 位和第 10 位剪切这个序列,那么对应的脚本应该是什么让程序只跳过这两个位置?我的第一种情况的脚本是:

my $otext = 'ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD';

$otext=~ s/([E])/$1=/g; #Main cut rule.

$otext=~ s/=P/P/g;

@output = split( /\=/, $otext);

print "@output";

请帮忙!

【问题讨论】:

  • 您应该使用代码降价来使代码更具可读性,类似于我编辑的方式 - 您可以单击文本编辑器窗口旁边的问号以获取标记帮助。

标签: perl skip string-substitution


【解决方案1】:

要在“E”上拆分,除了后面跟着“P”的地方,你应该使用否定的前瞻断言。

来自perldoc perlre“Look-Around Assertions”部分:

  • (?!模式)
    一个零宽度的负前瞻断言。
    例如,/foo(?!bar)/ 匹配任何出现的“foo”,但后面没有“bar”。
my $otext = 'ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD'; 
#                E    E    EP    E    EP    E
my @output=split(/E(?!P)/, $otext); 
use Data::Dumper; print Data::Dumper->Dump([\@output]);"

$VAR1 = [
          'ABCD',
          'ABCD',
          'ABCDEPABCD',
          'ABCDEPABCD',
          'ABCD'
        ];

现在,为了不在出现 #2 和 #4 的情况下进行切割,您可以做 2 件事:

  1. 编造一个非常奇特的正则表达式,它在给定的出现时自动无法匹配。为了完整起见,我将把它留给其他人尝试回答。

  2. 只需将正确的片段拼接在一起即可。

    我太脑残了,无法想出一个好的惯用方法,但简单而肮脏的方法是:

      my %no_cuts = map { ($_=>1) } (2,4); # Do not cut in positions 2,4
      my @output_final;
      for(my $i=0; $i < @output; $i++) {
          if ($no_cuts{$i}) {
              $output_final[-1] .= $output[$i];
          } else {
              push @output_final, $output[$i];
          } 
      }
      print Data::Dumper->Dump([\@output_final];
    
      $VAR1 = [
                'ABCD',
                'ABCDABCDEPABCD',
                'ABCDEPABCDABCD'
              ];
    

    或者,更简单:

      my %no_cuts = map { ($_=>1) } (2,4); # Do not cut in positions 2,4
      for(my $i=0; $i < @output; $i++) {
          $output[$i-1] .= $output[$i]; 
          $output[$i]=undef; # Make the slot empty
      }
      my @output_final = grep {$_} @output; # Skip empty slots
      print Data::Dumper->Dump([\@output_final];
    
      $VAR1 = [
                'ABCD',
                'ABCDABCDEPABCD',
                'ABCDEPABCDABCD'
              ];
    

【讨论】:

    【解决方案2】:

    这是一个利用两个事实的肮脏把戏:

    • 普通文本字符串从不包含空字节(如果你不知道空字节是什么,作为程序员你应该:http://en.wikipedia.org/wiki/Null_character,和nb。它与数字0或字符0不是一回事)。
    • perl 字符串可以包含空字节,如果你把它们放在那里,但要小心,因为这可能会搞砸一些 perl 内部函数。

    “小心”只是需要注意的一点。无论如何,这个想法是在你不想中断的地方替换一个空字节:

    my $s = "ABCDEABCDEABCDEPABCDEABCDEPABCDEABCD";
    
    my @nobreak = (4,9);
    
    foreach (@nobreak) {
        substr($s, $_, 1) = "\0";
    }
    

    "\0" 是一个转义序列,表示一个空字节,例如 "\t" 是一个制表符。再说一遍:它不是字符 0。我使用了 4 和 9,因为在这些位置有 E。如果你现在打印字符串,它看起来像:

    ABCDABCDABCDEPABCDEABCDEPABCDEABCD
    

    因为空字节不显示,但它们在那里,我们稍后将它们换回。先拆分:

    my @a = split(/E(?!P)/, $s);
    

    然后将零字节交换回来:

    $_ =~ s/\0/E/g foreach (@a);
    

    如果你现在打印@a,你会得到:

    ABCDEABCDEABCDEPABCD
    ABCDEPABCD
    ABCD
    

    这正是您想要的。请注意, split 删除了分隔符(在本例中为 E);如果您打算保留那些,您可以在之后再次将它们重新添加。如果分隔符来自更动态的正则表达式,则稍微复杂一些,请参见此处:

    http://perlmeme.org/howtos/perlfunc/split_function.html

    “示例 9. 保留分隔符”

    如果@nobreak 的位置有可能不是 E,那么您还必须在交换它们时跟踪它们,以确保再次替换为正确的字符。

    【讨论】:

    • 我认为这不是 OP 想要的。您避免在 characters # 4 和 9 上进行拆分; OP 想避免在“E”#4 和 9 的位置上分裂
    • 好吧,在第一段中,他/她说“在用户想要的地方特别打破它”但在第二段中“假设用户不想在第 5 和E在序列中的第10位"。这将完成后者:如果您想在 E 上拆分,除非指定的地方,用不是 E 的东西替换指定的地方,然后在拆分后再次替换回来。我假设用户不会指示不要在某处休息,除非那里实际上可能有休息,并且休息是在 E 上,所以这符合这些标准,但我会添加一个注释......
    • @DVK:我试过你的脚本,非常有用。但是我希望用户输入程序跳过拆分的那些位置,其次我希望在实际发生拆分的输出片段末尾添加“E”。因此,输出应如下所示: $VAR1 = [ 'ABCDE', 'ABCDABCDEPABCDE', 'ABCDEPABCDABCDE' ];
    • @prashant - 您可以在for 循环的末尾简单地添加“E” - 不是很整洁但很容易工作。 “我希望用户输入程序跳过拆分的那些位置” - 不确定您的意思?我的程序已经这样做了。
    • @DVK:我在 for 循环中添加了“E”,现在可以正常工作了。但是,我希望用户通过命令行输入这些位置(例如位置:1,2),程序必须跳过。
    猜你喜欢
    • 2017-09-17
    • 1970-01-01
    • 2013-05-12
    • 2017-04-10
    • 2015-10-18
    • 2021-07-04
    • 2015-05-05
    • 2021-09-17
    • 2019-05-08
    相关资源
    最近更新 更多