【问题标题】:Comparing between 2 processed keys in 2 hashes比较 2 个哈希中的 2 个已处理键
【发布时间】:2012-03-13 10:18:52
【问题描述】:

我想读入带有“!”等符号的文件和“^”,并想在我将它们与另一行的其他字符串进行比较之前删除它们。如果删除符号后两个字符串相同,我想将它们存储在另一个名为“common”的哈希中。 例如... 文件A:

hello!world
help?!3233
oh no^!!
yes!

文件B:

hello
help?
oh no
yes

在这种情况下,FileA 和 FileB 应该是相同的,因为我正在比较字符直到“!”所在的位置或出现“^”。 我使用以下代码读取文件:

open FILEA, "< script/".$fileA or die;
my %read_file;
while (my $line=<FILEA>) {
   (my $word1,my $word2) = split /\n/, $line;
   $word1 =~ s/(!.+)|(!.*)|(\^.+)|(\^.*)//;#to remove ! and ^
   $read_file{$word1} = $word1;
}
close(FILEA);

我打印出散列中的键,它显示了正确的结果(即,它将 FileA 转换为“你好,帮助?哦,不,是的)。但是,当我使用以下方法比较 FileA 和 FileB 时代码,总是失败。

while(($key,$value)=each(%config))
{
    $num=keys(%base_config);
    $num--;#to get the correct index
    while($num>=0)
    {
        $common{$value}=$value if exists $read_file{$key};#stored the correct matches in %common
        $num--;
    }
}

我尝试使用以下示例测试我的替换和比较 2 个字符串,它有效。我不知道为什么它不能从文件中读取字符串到哈希中。

use strict;
use warnings;

my $str="hello^vsd";
my $test="hello";
$str =~ s/(!.+)|(!.*)|(\^.+)|(\^.*)//;
my %hash=();
$hash{$str}=();
foreach my $key(keys %hash)
{
    print "$key\n";
}
print "yay\n" if exists $hash{$test};
print "boo\n" unless exists $hash{$test};

两个文件的文本行数可以不同,搜索时文本行的顺序不必相同。 IE。 "oh no" 可以出现在 "hello" 之前。

【问题讨论】:

  • 你能假设文件 A 中的每一行只应与文件 B 中的相应行进行比较吗?并且文件 A 和文件 B 的行数相等?
  • 没有。 FileA 和 FileB 可以有不同的行数,并且行的顺序不必相同。

标签: string perl hash


【解决方案1】:

您可以使用正则表达式字符类 s/[?^]//g 删除 ^ 和 ?,注意 ^ 必须是组中的最后一个,或者您需要转义它。 (可能会更安全,以防您稍后添加其他字符,这样它们就不会被否定)。

我处理所有文件,使用哈希计算单词存在哪个文件。

为了比较差异,我使用 2**(# of file),所以我得到的值是 2**0=1、2**1=2、2**2=4,依此类推。我用来显示字符串属于哪个文件。如果它们存在于所有文件中,它们将等于总文件数,因此在这种情况下为 2 - 3 (2+1) 表示它们在两个文件中,1 表示仅 FileA,2 表示 FileB。您可以通过按位 (&) 来检查这一点。

编辑:添加测试条件

<!-- language: perl -->

my @files = qw(FileA.txt FileB.txt);
my %words;
foreach my $i (0 .. $#files) {
    my $file = $files[$i];
    open(FILE,$file) or die "Error: missing file $file\n$!\n";
    while (<FILE>) {
        chomp;
        next if /^$/;
        my ($word) = split /[!\^]/;
        $word =~ s/[?\^]//g; # removes ^ and ?
        $words{$word} += 2**$i;
    }
    close(FILE);
}

my %common;
foreach my $key (sort keys %words) {
    my @found;
    foreach my $i (0 .. $#files) {
        if ( $words{$key} & 2**$i ) { push @found, $files[$i] }
    }
    if ( $words{$key} & 2**$#files ) { $common{$key}++ }
    printf "%10s %d: @found\n",$key,$words{$key};
}

my @tests = qw(hello^vsd chuck help? test marymary^);
print "\nTesting Words: @tests\n";
foreach (@tests) {
    my ($word) = split /[!\^]/;
    $word =~ s/[?\^]//g; # removes ^ and ?
    if ( exists $common{ $word } ) {
        print "Found: $word\n";
    }
    else {
        print "Cannot find: $word\n";
    }
}

输出:

    bahbah 2: FileB.txt
   chucker 1: FileA.txt
     hello 3: FileA.txt FileB.txt
      help 3: FileA.txt FileB.txt
  marymary 2: FileB.txt
     oh no 3: FileA.txt FileB.txt
      test 1: FileA.txt
       yes 3: FileA.txt FileB.txt

Testing Words: hello^vsd chuck help? test marymary^
Found: hello
Cannot find: chuck
Found: help
Cannot find: test
Found: marymary

【讨论】:

    【解决方案2】:

    这是同时读取两个文件的另一种解决方案(假设两个文件的行数相同):

    use strict;
    use warnings;
    
    our $INVALID = '!\^'; #regexp character class, must escape
    
    my $fileA = "file1.txt";
    my $fileB = "file2.txt";
    
    sub readl
    {
      my $fh = shift;
      my $ln = "";
    
      if ($fh and $ln = <$fh>)
      {
        chomp $ln;
        $ln =~ s/[$INVALID]+.*//g;
      }
    
      return $ln;
    }
    
    my ($fhA, $fhB);
    my ($wdA, $wdB);
    my %common = ();
    
    open $fhA, $fileA or die "$!\n";
    open $fhB, $fileB or die "$!\n";
    
    while ($wdA = readl($fhA) and $wdB = readl($fhB))
    {
      $common{$wdA} = undef if $wdA eq $wdB;
    }
    
    print "$_\n" foreach keys %common;
    

    输出

    andrew@gidget:comparefiles$ cat file1.txt 
    hello!world
    help?!3233
    oh no^!!
    yes!
    
    andrew@gidget:comparefiles$ cat file2.txt 
    hello
    help?
    oh no
    yes
    
    andrew@gidget:comparefiles$ perl comparefiles.pl 
    yes
    oh no
    hello
    help?
    

    【讨论】:

      【解决方案3】:

      首先将可重用的段打包成子程序:

      sub read_file {
          open my $fh, "<", $_[0] or die "read_file($_[0]) error: $!";
            # lexical handles auto-close when they fall out of scope
            # and detailed error messages are good
          my %file;
          while (my $line = <$fh>) {
              chomp $line;          # remove newline
              $line =~ s{[!^].*}{}; # remove everything starting from ! or ^
              $file{$line}++;
          }
          \%file
      }
      

      read_file 接受输入文件名并在任何 !^ 字符之前返回线段的哈希值。每条线段是一个key,value是它出现的次数。

      使用这个,下一步是找出文件之间匹配的行:

      my ($fileA, $fileB) = map {read_file $_} your_file_names_here();
      
      my %common;
      $$fileA{$_} and $common{$_}++ for keys %$fileB;
      
      print "common: $_\n" for keys %common;
      

      将打印的内容:

      常见:是 常见:哦不 常见:你好 常见:帮助?

      如果你想测试your_file_names_here,你可以定义如下:

      sub your_file_names_here {\(<<'/A', <<'/B')}
      hello!world
      help?!3233
      oh no^!!
      yes!
      /A
      hello
      help?
      oh no
      yes
      /B
      

      【讨论】:

      • 嗨。我刚开始学习 Perl,不太了解“my ($fileA, $fileB) = map {read_file $_} your_file_names_here();”你能进一步解释一下吗?
      • map 将转换应用于列表并返回转换后的列表。如果('filea.txt', 'fileb.txt')your_file_names_here 占位符返回,则与my $fileA = read_file('filea.txt'); my $fileB = read_file('fileb.txt'); 相同。
      【解决方案4】:

      首先,我们必须标准化您的输入。下面的代码为每条路径创建一个哈希。对于给定文件中的每一行,删除以第一个 !^ 字符开头的所有内容并记录其存在。

      sub read_inputs {
        my @result;
      
        foreach my $path (@_) {
          my $data = {};
      
          open my $fh, "<", $path or die "$0: open $path: $!";
          while (<$fh>) {
            chomp;
            s/[!^].*//;  # don't put the caret first without escaping!
            ++$data->{$_};
          }
      
          push @result, $data;
        }
      
        wantarray ? @result : \@result;
      }
      

      Computing the intersection of two arrays 包含在Perl FAQ listData Manipulation 部分中。根据您的情况调整该技术,我们想知道所有输入共有的行。

      sub common {
        my %matches;
        for (@_) {
          ++$matches{$_} for keys %$_;
        }
      
        my @result = grep $matches{$_} == @_, keys %matches;
        wantarray ? @result : \@result;
      }
      

      把它和

      结合起来
      my @input = read_inputs "FileA", "FileB";
      my @common = common @input;
      print "$_\n" for sort @common;
      

      输出

      你好
      帮助?
      不好了
      是的

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-28
        相关资源
        最近更新 更多