【问题标题】:replace the 'value' of the 'key' when 'key' is found in file在文件中找到“key”时替换“key”的“value”
【发布时间】:2012-08-30 04:14:17
【问题描述】:

我在将一个文件中的某些字符串的值替换为另一个文件中的相同字符串时遇到问题。

test1.txt

porsche  430+turbo blue
ferrari  520+supercharged red
buggati  1001+supersport black
fiat     turbo white

这个 test1.txt 中的一些字符串有它们自己的值,如 test2.txt 中给出的那样

test2.txt
turbo=30
supercharged=50 
supersport=100

每当我找到相应的字符串时,我想在 test1.txt 中替换 test2.txt 中的值。

output.txt
    porsche  460 blue
    ferrari  570 red
    buggati  1101 black
    fiat     30 white

在test1.txt中搜索test2.txt中的turbo,然后将值替换并求和。

我做了很多谷歌搜索,但我没有找到继续进行此操作的方法。 谁能帮我解决这个问题 提前致谢

【问题讨论】:

  • 这并不是一件小事……您不仅需要解析器,还需要小型语言解释器。
  • 这实际上是微不足道的。你认为你应该如何进行?你为什么要写这个程序?是作业吗?
  • 我正在考虑将 test2.txt 存储到带有键和值的散列中。然后在 test1.txt 中搜索键并替换 test1.txt 中的值。这是我工作的一小部分,我不知道如何解决这个问题
  • 听起来完全正确;你在哪一部分有问题?

标签: regex perl replace


【解决方案1】:

amon 的解决方案既漂亮又优雅。 +1(我还没有足够的声望点来对他的回答发表评论)

如果您发现不需要操作员调度表的可扩展性,这里有一个相当简单的替代方案:

(2012/08/29 编辑:合并了 amon 的调度表,用于处理 +、-、*、/、^ 运算符)

use strict;
use warnings;
use English qw( -no_match_vars );

setup_for_testing( );

open my $source_file, '<',  'text1.txt' || die "Couldn't open source file: $OS_ERROR";
open my $key_file,    '<',  'text2.txt' || die "Couldn't open key file: $OS_ERROR";

# Clear the output file
open my $output_file, '>',  'output.txt' || die "Couldn't open output file: $OS_ERROR";

# Now open output file for append
open $output_file,    '>>', 'output.txt' || die "Couldn't open output file: $OS_ERROR";

# Create translation map from text2.txt
my %translation_map = translation_map( $key_file );

# Process text1.txt and print to output.txt
while ( my $source_line = <$source_file> ) {
    my $transformed_line = transform( $source_line, \%translation_map );
    print $output_file $transformed_line ||
        die "Couldn't print to output file: $OS_ERROR";;
}

# Tidy up
close $source_file || die "Couldn't close source file: $OS_ERROR";
close $key_file    || die "Couldn't close key file: $OS_ERROR";
close $output_file || die "Couldn't close output file: $OS_ERROR";

###################
sub setup_for_testing {
   open my $textfile1, '>',  'text1.txt' || die "Couldn't open source file: $OS_ERROR";
   open my $textfile2, '>',  'text2.txt' || die "Couldn't open key file: $OS_ERROR";

   my $source_text =<<'END_TEXT';
porsche  430-turbo blue
ferrari  520*supercharged red
buggati  1001+supersport black
fiat     turbo white
END_TEXT

   my $key_file_text =<<'END_TEXT';
turbo=30
supercharged=50
supersport=100
END_TEXT

   print $textfile1 $source_text   || die "Couldn't print to text1.txt: $OS_ERROR";
   print $textfile2 $key_file_text || die "Couldn't print to text2.txt: $OS_ERROR";

   close $textfile1 || die "Couldn't close source file: $OS_ERROR";
   close $textfile2 || die "Couldn't close key file: $OS_ERROR";

   return; # intentional void return
}

sub translation_map {
    my $key_file = shift;

    my %translation_map;
    while ( my $key_mapping = <$key_file> ) {
        chomp $key_mapping;

        # The regex /x option allows whitespace in the regular expression for readability
        my ( $key, $value ) = split / \s* = \s* /x, $key_mapping;
        $translation_map{ $key } = $value;
    }

    return %translation_map;
}

sub transform {
    my $source_line = shift @_;
    my %value_for   = %{ shift @_ };

    my $transformed_line = $source_line;

    foreach my $key ( keys %value_for ) {
        # The regex /e option causes the rights side of a substitution to be evaluated as
        # a Perl expression.
        my $value = $value_for{ $key };
        my ( $before_expression, $lvalue, $operator, $rvalue_key, $after_expression ) =
            ( $transformed_line =~ m/ \A
                                      ( .*? )
                                      ( \d+ ) ([-+*\/^]?) ( $key )
                                      ( .* )
                                      \Z
                                    /x );

        if ( $operator  ) {
            my $rvalue = $value_for{ $rvalue_key };

            # Using the dispatch table from amon's answer
            my $value_of_expression = {
              '+' => sub {$_[0] +  $_[1]},
              '-' => sub {$_[0] -  $_[1]},
              '*' => sub {$_[0] *  $_[1]},
              '/' => sub {$_[0] /  $_[1]},
              '^' => sub {$_[0] ** $_[1]},
            }->{$operator}->($lvalue, $rvalue);

            $transformed_line =
                $before_expression . $value_of_expression . $after_expression . "\n";
        } else {
            $transformed_line =~ s/$key/$value/;
        }
    }

    return $transformed_line;
}

此脚本根据您的问题规范创建测试文件 text1.txt 和 text2.txt,然后进行转换并输出到 output.txt:

> ls
stackoverflow-12169648_replace_value_of_key.pl

> perl stackoverflow-12169648_replace_value_of_key.pl 

> ls
output.txt                  text1.txt
stackoverflow-12169648_replace_value_of_key.pl  text2.txt

> more text1.txt 
porsche  430+turbo blue
ferrari  520+supercharged red
buggati  1001+supersport black
fiat     turbo white

> more text2.txt 
turbo=30
supercharged=50
supersport=100

> more output.txt 
porsche  460 blue
ferrari  570 red
buggati  1101 black
fiat     30 white

希望这是有用的。

.大卫

【讨论】:

  • 为什么要在代码中给我的输入文件????我不能通过一些文件名或文件句柄访问它吗???
  • 很抱歉让您感到困惑。我并不是说您的最终解决方案应该在代码中硬编码这些示例文件。我只是把它放在那里,以便您更方便地验证脚本是否执行您需要它执行的操作。之后,不再需要 setup_for_testing() 函数。我做了很多测试驱动的开发,所以我想我已经习惯了编写测试。 :) 很高兴它对你有帮助。
  • 非常感谢大卫,这并不令人困惑,但我只是觉得没有必要。再次感谢你..
  • 大卫如果我用减法和乘法运算符(“-”或“*”)代替“+”怎么办?我如何修改此代码以适用于这些运营商中的任何一个
  • 这在很长一段时间内都是矫枉过正的。根本没有理由,例如,打开输出文件两次,并且您的大部分代码都在那里生成测试文件。大量的 cmets 和子程序只会隐藏程序的功能
【解决方案2】:

我将假设第一个文件(称为 A)具有由一个或多个空白字符分隔的三列。第二列可能包含一个算术表达式,其中包含由基本(中缀)运算符分隔的十进制数字和变量。变量值固定在另一个文件中,此后称为 B

准备变量很简单:

my %variables = map {chomp; split /=/, $_, 2} do {
  open my $file, "<", $filename_B or die;
  <$file>;
};

解析其他文件更加困难。假设它在文件句柄$fileA 中打开,我们循环遍历这些行并将每一行拆分为三个字段:

while (defined(my $line = <$fileA>)) {
   chomp $line;
   my ($model, $expression, $color) = split /\s+/, $line, 3;
   my $value = parseExpression($expression);
   print "\t$model $value $color\n"; # use printf to prettyprint if needed
}

然后我们将表达式值与其他数据一起打印出来,假设您要打印到 STDOUT。

我们的子 parseExpression 将在运算符处拆分表达式字符串。变量名将被替换。然后这些操作严格地右关联地执行。虽然这使得解析更容易,但这并不完全自然:3*4+1 的计算结果为15。我们使用递归是因为我更喜欢它而不是迭代来解决多个操作:

sub parseExpression {
  my ($string) = @_;
  my ($part, $operator, $rest) = ($string =~ /(\w+)([-+*\/^]?)(.*$)/g);
  if (not $operator) {
    # $part is the whole expression
    my $value = exists $variables{$part} ? $variables{$part} : $part;
    die if $value =~ /[a-z]/i; # The variable name was not substituted
    return $value;
  } else {
    my $rval = parseExpression($rest);
    my $lval = parseExpression($part); # you don't need this
                                       # if there are no variables on the left
    my $value = {
      '+' => sub {$_[0] +  $_[1]},
      '-' => sub {$_[0] -  $_[1]},
      '*' => sub {$_[0] *  $_[1]},
      '/' => sub {$_[0] /  $_[1]},
      '^' => sub {$_[0] ** $_[1]},
    }->{$operator}->($lval, $rval);
    return $value;
  }
}

我们使用一个可爱的小调度表来为每个操作员执行适当的计算。您始终可以增强运算符正则表达式和表格以支持其他运算符。

请注意,当前的实现允许数字作为变量名。不是您可能想要的,但它让生活更轻松。

可能会出现一些有趣的问题,即随机出现未定义的值,但这段代码应该可以为您指明正确的方向。 (如果第二列只允许一个操作,可以去掉递归)

【讨论】:

    【解决方案3】:

    只需分两步执行替换即可。首先找到从test2.txt 文件派生的%values 哈希中存在的所有关键字。然后寻找由算术运算符连接的多个十进制数并计算表达式以形成替换。

    用于查找哈希键的正则表达式是使用join 动态构建的,以使用正则表达式交替运算符| 连接。

    第二个正则表达式相当于

    expression ::= digits, operator, digits, { operator, digits }
    

    并允许术语之间有空格

    use strict;
    use warnings;
    
    my %values = do {
      open my $fh, '<', 'test2.txt' or die $!;
      local $/;
      <$fh> =~ /\w+/g;
    };
    
    my $regex = join '|', keys %values;
    
    open my $fh, '<', 'test1.txt' or die $!;
    
    while (<$fh>) {
      s/\b($regex)\b/$values{$1}/g;
      s|([0-9]+(\s*[-+*/]\s*[0-9]+)+)|$1|eeg;
      print;
    }
    

    输出

    porsche  460 blue
    ferrari  570 red
    buggati  1101 black
    fiat     30 white
    

    【讨论】:

    • 很明显,我们来自非常不同的编码风格哲学。 :) 比起简洁,我更倾向于可读性。不过,我真的很喜欢你如何解决它。它适用于每行多个表达式,而我的假设每行只有一个表达式。 +1
    • 我不是 perl 专家,但 David 的版本更容易理解。
    • @Borodin 当我有多个表达式时,这对我来说很好用,但我不知道为什么每行只有一个表达式时我会产生不良结果...感谢您提供如此紧凑的代码..: )
    【解决方案4】:

    我根本不会用 perl 编写代码,但我想我可能只是提供一些总体提示...

    首先,为了保持简单,您必须对有限的模式格式做出一定的承诺,否则正如评论中所建议的那样,这将成为语言解析器。所以你可能不得不说 test1.txt 中的一行可能包含也可能不包含[numbers][operator][characters] 模式。

    所以我可能建议将 test2.txt 解析为关联数组(哈希),这样你就有了类似的东西:

    {
        "turbo"        =>  30,
        "supercharged" =>  50,
        "supersport"   => 100,
    }
    

    然后对于 test1.txt 中的每一行,您可以对以下模式执行正则表达式匹配:
    \b((\d+)([+])(\w+))\b

    通过此模式,您可以检查 + 运算符和密钥。如果你得到它们,你可以在你的哈希中查找密钥,并对parsed number, operator, hash[key] =&gt; value执行一个评估

    如果您只想处理+,则只需将数字转换为int 并添加即可。否则,要支持多种运算符模式,您必须明确处理它们或安全地评估字符串。

    您可以在第一个主要捕获组上进行正则表达式替换

    【讨论】:

      猜你喜欢
      • 2017-05-07
      • 1970-01-01
      • 2017-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-22
      • 1970-01-01
      相关资源
      最近更新 更多