【问题标题】:Limit substitutions to first n instances per line in Perl将替换物限制在Perl中每行的第一个实例
【发布时间】:2016-02-13 01:03:25
【问题描述】:

我的情况与 Stack Overflow 帖子类似Substituting array elements from one tab delimited file with hash values from another file using Perl。我正在尝试用特定列中的各自值替换与哈希键匹配的字符串。

    Given the @array:

    a b abbd
    cc d abcd
    gg hh cdag

and the %hash:

$VAR1 = {
    'a'  => 'GAT_1',
    'b'  => 'GAT_2',
    'cc' => 'GAT_3',
    'd'  => 'GAT_4',
    'gg' => 'GAT_5',
    'hh' => 'GAT_6',
};

我已经尝试过这段代码,但它不起作用。如何限制仅替换匹配键的前两个实例(列)? (也就是保持第三列不变?)

foreach $line (@array) {
    my @cols = split (/\s+/, $line);
    $cols[0] = $hash{cols[0]};
    $cols[1] = $hash{cols[1]};
    push @newarray, $line;
}


Expected output:
GAT_1 GAT_2 abbd
GAT_3 GAT_4 abcd
GAT_5 GAT_6 cdag

【问题讨论】:

    标签: regex perl hash replace


    【解决方案1】:

    只需在替换后加入列:

    my $line_after_lookup;
    foreach $line (@array) {
        my @cols = split (/\s+/, $line);
    
        if (defined($$VAR1{$cols[0]})) { $cols[0] = $$VAR1{$cols[0]}; }    
        if (defined($$VAR1{$cols[1]})) { $cols[1] = $$VAR1{$cols[1]}; } 
    
        #
        # When using a hash instead of a hash reference, replace the previous 2 statements with the following 2 lines:
        # if (defined($hash{$cols[0]})) { $cols[0] = $hash{$cols[0]}; }
        # if (defined($hash{$cols[1]})) { $cols[1] = $hash{$cols[1]}; }
        #
    
        $line_after_lookup = join ( ' ', @cols );
        push @newarray, $line_after_lookup ;
    }
    

    【讨论】:

    • 谢谢,我已经尝试过了,但是当我打印输出时,我得到的是引用而不是哈希值。关于如何解决这个问题的任何想法?
    • 总是use strict;use warnings;
    • @stevieb 当然。答案只包含一个代码摘录,您提到的语句将放在包或脚本的开头。
    • @cmvdi01 抱歉,我使用了错误的变量名。代码已更正。
    • 您真的是指“{cols[0]}”和“{cols[1]}”吗? (不是“{$cols[0]}”和“{$cols[1]}”?)
    【解决方案2】:

    这就是诀窍:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use Data::Dumper; 
    
    my %substitute = (
        'a'  => 'GAT_1',
        'b'  => 'GAT_2',
        'cc' => 'GAT_3',
        'd'  => 'GAT_4',
        'gg' => 'GAT_5',
        'hh' => 'GAT_6',
    );
    
    my @newarray;
    
    while (<DATA>) {
        my @fields = split;
        $fields[0] = $substitute{ $fields[0] };
        $fields[1] = $substitute{ $fields[1] };
        push ( @newarray, join( " ", @fields ));
    }
    
    print Dumper \@newarray;
    
    __DATA__
        a b abbd
        cc d abcd
        gg hh cdag
    

    打印:

    $VAR1 = [
              'GAT_1 GAT_2 abbd',
              'GAT_3 GAT_4 abcd',
              'GAT_5 GAT_6 cdag'
            ];
    

    你的不工作,因为你正在更改 @fields 而不是 $line 的内容。然而,另一个可能的问题是您的拆分 - split /\s+/ 以不同的方式处理前导空格(您得到一个空字段)。

    【讨论】:

    • 当我直接从文件中读取时,您的代码工作正常,但它不适用于存储我的数据的数组 (@array)。用`foreach $line(@array)'替换while (&lt;IN&gt;)的任何替代方法?谢谢。
    • 是的,您应该能够做到这一点。我想确保我的示例是可运行的。请注意,如果没有要查找的哈希元素,则上述内容会中断。
    • 请记住,默认情况下 split 隐含地作用于 $_ 。所以你可以做foreach (@lines) { my @fields = split ;...
    【解决方案3】:

    如何限制仅替换匹配键的前两个实例(列)? (即保持第三列不变?)

    一种方法是使用一个正则表达式,它只选择这两列并执行适当的替换。

    插图(已编辑):

    #!/usr/bin/perl
    
    my @array = (
        "a b abbd",
        "cc d abcd",
        "gg hh cdag",
        "ii jj kmln"  # to show what happens when no mapping exists 
    );
    
    my %hash = (
    'a' => 'GAT_1',
    'b' => 'GAT_2',
    'cc' => 'GAT_3',
    'd' => 'GAT_4',
    'gg' => 'GAT_5',
    'hh' => 'GAT_6',
    );
    
    sub replace { $hash{$_[0]} || $_[0]; } # original string if no mapped value
    
    sub convert { replace($_[0]) . $_[1] . replace($_[2]); }
    
    my @newarray = map
    {
        my $line = $_;
        $line =~ s/(\w+)(\W+)(\w+)/convert($1, $2, $3)/e;
        $line;
    } @array;
    
    print "$_\n" for @newarray;
    

    需要的替换发生在这里:

        $line =~ s/(\w+)(\W+)(\w+)/convert($1, $2, $3)/e;
    

    s/// 表达式上的 e 修饰符导致第二部分中的替换表达式被计算,因此调用 sub convert()。 convert() 的参数来自第一部分正则表达式中的捕获:(\w+) 捕获第一个单词字符序列,(\W+) 捕获空格或标点符号等非单词字符,@987654327 @ 再次捕获第二个单词字符序列。其余不相关的行根本不需要解析,保持原样。

    您可能还需要考虑如果映射值不可用会发生什么。在这种情况下,上面的代码会保留原始字符串。

    【讨论】:

    • 子程序 replace() 和 convert() 应该很容易理解。关键操作是 s///e 替换表达式。 e 修饰符导致 s/// 的第二部分被评估(因此 convert() 被调用)。 3 个参数来自第一部分:\b 用于单词边界,(\w+) 用于捕获单词字符,(\W+) 用于捕获非单词字符,即空格、标点符号等,然后(\w+) 再次捕获第二个单词字符序列。无需拆分或解析整行。
    • @arayq2:最好将解释编辑到答案中。
    猜你喜欢
    • 2021-07-24
    • 2018-09-13
    • 1970-01-01
    • 2016-07-22
    • 2013-12-22
    • 2017-12-01
    • 2012-04-06
    • 2012-04-25
    • 1970-01-01
    相关资源
    最近更新 更多