【问题标题】:Perl - parse file text into hashPerl - 将文件文本解析为哈希
【发布时间】:2013-03-06 18:32:42
【问题描述】:

我想解析文件文本,然后将其放入哈希中。我的文件如下所示:

key1 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val
key2 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val
key3 val
key4 val,val
key5 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val,val,val,val,val,val,val,val,val,val,val,val

我的键在空格之前,我的值是空格之后和每个逗号之前的元素列表。我有一些没有键的行,因为值在几行上继续。

所以我想要一个这样的哈希(我最熟悉 Python):

hash={'key1':[val,val,...],'key2':[val,val,...]} 

我的代码: `

my %hashNames;
open INFILE, "./file.txt" or die $!;
my @temp = ();

while (my $line = <INFILE>)
{

    my @names = split /[\t,]/, $line;
    my $ID = $names[0];
    if ( $line =~ /\t/ )
    {

        my @temp=();
        for (my $i = 1; $i < @names; $i +=1)
        {
            push (@temp, $names[$i]);
        }

    }
    else
    {   

        for (my $i = 0; $i < @names; $i +=1)
        {
            push (@temp, $names[$i]);
        }       
    }
}`

【问题讨论】:

  • 向我们展示您的尝试
  • 如果你知道如何在 Python 中做到这一点,请告诉我们。
  • 我认为我必须阅读每一行,如果有空格,我必须创建一个新键并推送不同的值(在列表中),如果没有空格,我必须将值添加到上一个键。

标签: perl parsing hash


【解决方案1】:

您的问题是换行符不再分隔您的记录。所以处理它的一种方法是禁用无效的默认输入记录分隔符$/ 并模拟一个有效的:

use strict;
use warnings;
use Data::Dumper;

my %hash;
my $file;
{
    local $/;         # disable input record separator
    $file = <DATA>;   # entire file here now!
}

for my $line (split /^(?=\S+ )/m, $file) {  # records begin this way now
    $line =~ s/\n//g;                       # remove newlines
    my ($key, $val) = split ' ', $line, 2;  # divide into two fields
    $hash{$key} = [ split /,/, $val ];      # store the data
}

print Dumper \%hash;

__DATA__
key1 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val
key2 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val
key3 val
key4 val,val
key5 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
val,val,val,val,val,val,val,val,val,val,val,val,val,val,val

说明:

  • 使用/m 修饰符分割/^(?=\S+ )/m 意味着^ 现在将匹配字符串中的换行符,这将模拟输入记录分隔符。
  • 通过将 LIMIT 2 添加到 split 来将字符串拆分为两个字段
  • 我们使用匿名数组[ ... ] 直接拆分为哈希,其中包含拆分语句。

【讨论】:

    【解决方案2】:

    使用Parse::RecDescent 模块

    #! /usr/bin/env perl
    
    use strict;
    use warnings;
    
    use Parse::RecDescent;
    
    our %hash;
    my $p = Parse::RecDescent->new(q!
      hash: entry(s?)
      entry: key value(s /,/)  { $::hash{$item[1]} = [ @{ $item[2] } ] }
      key: /\S+/
      value: /([^,\n]|\\,])+/
    !);
    die "$0: failed to create parser" unless defined $p;
    
    my $text = do {{ local $/; <DATA> }};
    $p->hash($text) or die "$0: parse failed";
    
    for (sort keys %hash) {
      print "$_ => val x ", scalar @{ $hash{$_} }, "\n";
    }
    
    __DATA__
    key1 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
    val,val,val,val
    key2 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
    val,val,val,val
    key3 val
    key4 val,val
    key5 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
    val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
    val,val,val,val,val,val,val,val,val,val,val,val,val,val,val
    

    输出:

    key1 => 值 x 22
    key2 => 值 x 22
    key3 => 验证 x 1
    key4 => 验证 x 2
    key5 => val x 52

    【讨论】:

      【解决方案3】:

      这里的困难在于您的记录以“不带逗号的换行符”结尾。不幸的是,输入记录分隔符$/ 不能设置为正则表达式。这留下了三个舒适的解决方案:

      1. 将整个文件加载到内存中。这并不像听起来那么糟糕,因为我们稍后在哈希中拥有相同数量的信息。然后我们可以split /(?&lt;!,)\n/获取实际记录。

        my %hash = do {
          local $/; # set to undef, for slurp
          map {
            my ($key, $vals) = split /\s+/, $_, 2; # split on first whitespace, into two strings
            $key => [ split /\s*,\s*/, $vals ];    # return a list of a key and a value array
          } split /(?<!,)\n/, <FILE>;              # split the file into records
        };
        
      2. 我们可以编写一个 readline 替代品来缓冲输入并可以用正则表达式终止行。

      3. 我们可以把结尾的逗号看作是一个续行符。

        my %hash;
        while(<FILE>) {
          $_ .= <FILE> while /,\n\z/;
          my ($key, $value) = split /\s+/, $_, 2;
          push @{ $hash{$key} }, split /\s*,\s*/, $value; # allow multiple occurrences of one key, simply append values to list.
        }
        

      【讨论】:

      • 非常感谢您的回答。如何检索列表的键和值?
      • @Tof 我不太明白这个问题。对于给定的$key,您可以获得@vals,如@vals = @{ $hash{$key} }。您需要@{...},因为该哈希只能存储对数组的引用,而不是数组本身,因此我们必须将其解引用回数组。
      • 例如,我将检索特定键的数组,然后对其进行扫描。
      • @Tof 这是一个例子吗?有关基本 perl 结构的信息,请参阅 perldoc perldata
      【解决方案4】:

      来吧:

      my %results;
      my $key;
      while(my $line = <INFILE>) {
          chomp($line);
          my @items = split(/, */, $line);
          $key = shift @items;
          $results{$key} = \@items;
      }
      

      除了你的陈述之外,这适用于简单的情况:

      我有一些没有键的行,因为值在几行上继续。

      不过,要处理这个问题,您必须解释如何检测下一行是键还是值。如果你知道,那么你可以把它放在一个 if 语句中,并使用以前的键向哈希添加新值:

      my %results;
      my $key;
      while(my $line = <INFILE>) {
          chomp($line);
          my @items = split(/, */, $line);
          my $tmpkey = shift @items;
          if (is_real_key($tmpkey)) {
              $key = shift @items;
              $results{$key} = \@items;
          } else {
              push (@{$results{$key}}, $tmpkey, @items);
          }
      }
      

      【讨论】:

      • 事实上我的值在多行上连续,所以没有键。
      • 是的,我处理了。但是您没有说如何知道值何时停止并且新键何时开始。
      【解决方案5】:
      #!/usr/bin/perl
      
      use strict;
      use warnings;
      use feature 'say';
      
      use Data::Dumper;
      
      my $res_hash = {};
      my ($current_key, $values);
      my $push_again;
      while ( my $line = <DATA>) {
        chomp $line;
        push ( @{ $res_hash->{$current_key} }, split(/,/, $values) ) if ( $current_key and $values and ( index($line, ' ') > 0) );
        if ( index($line, ' ') > 0 ){
          $push_again = 0;
          ($current_key, $values) = split( /\s/, $line);    
        } else {
          $values .= $line;
          $push_again = 1;
        }
      
      };
      push ( @{ $res_hash->{$current_key} }, split(/,/, $values) ) if $push_again;
      
      say "result:".Dumper($res_hash);
      
      
      
      __DATA__
      key1 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
      val,val,val,val
      key2 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
      val,val,val,val
      key3 val
      key4 val,val
      key5 val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
      val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,val,
      val,val,val,val,val,val,val,val,val,val,val,val,val,val,val
      

      【讨论】:

      • 您缺少最后一条记录。您还必须在最后一次循环迭代之后推送缓冲区。
      • 你是对的,谢谢 - 但是,我更喜欢你的解决方案
      猜你喜欢
      • 2021-02-27
      • 1970-01-01
      • 2018-12-29
      • 2013-06-14
      • 2013-12-05
      • 2013-02-04
      • 2016-04-20
      • 2018-07-04
      • 2014-04-27
      相关资源
      最近更新 更多