在Perl中将整个文件读入哈希表

4
我在Perl中读取文件到哈希表时遇到了一些问题。
Chr1_supercontig_000000000  1   500
    PILOT21_588_1_3_14602_59349_1
Chr1_supercontig_000000001  5   100
    PILOT21_588_1_21_7318_90709_1
    PILOT21_588_1_43_18803_144592_1
    PILOT21_588_1_67_13829_193943_1
    PILOT21_588_1_42_19678_132419_1
    PILOT21_588_1_67_4757_125247_1
...

我有一个文件,我的期望输出是一个哈希表,其中“Chr1”行作为键,“PILOT”行作为值。

Chr1_supercontig_000000000 => PILOT21_588_1_3_14602_59349_1
Chr1_supercontig_000000001 => PILOT21_588_1_21_7318_90709_1, PILOT21_588_1_43_18803_144592_1,...

据我所知,只有通过引用才能将多个值分配给一个键,这是正确的吗?
在这一点上我卡住了,需要帮助。
6个回答

6
你是正确的,哈希值需要引用指向包含PILOT行的数组。
以下是一种实现方式:
my %hash;
open FILE, "filename.txt" or die $!;
my $key;
while (my $line = <FILE>) {
     chomp($line);
     if ($line !~ /^\s/) {
        ($key) = $line =~ /^\S+/g;
        $hash{$key} = [];
     } else {
        $line =~ s/^\s+//;
        push @{ $hash{$key} }, $line;
     }
 }
 close FILE;

1
你可能想要在第一个\s处截断$key,以获取仅为'Chr1_supercontig_000000000'而不是'Chr1_supercontig_000000000 1 500' - mu is too short
谢谢您的快速回复。我还没有尝试过,但现在会立即尝试!您在if语句中使用的正则表达式是在查找不以空格开头的行吗? - Philipp
没错,Philipp。还请注意我已经更新了代码,请使用你现在看到的代码。 - alexk
1
如果 ($line =~ /^(\S+)/) { $key = $1; ... } 可以为你节省一行代码。而且你不需要预定义 $hash{$key} - TLP

5

您可以逐行读取文件并跟踪当前哈希键:

open my $fh, '<', 'file' or die $!;

my (%hash, $current_key);

while (<$fh>) {
    chomp;        
    $current_key = $1, next if /^(\S+)/;
    s/^\s+//; # remove leading space
    push @{ $hash{$current_key} }, $_;
}

1
谢谢,我也尝试过了,运行得很好!清晰明了 =) - Philipp
1
push @{$hash{$current_key}}, split这样写既可以避免使用 chomp,也可以避免使用s/\s+// - TLP

2
如何看待以下内容:
#!/usr/bin/perl 
use strict;
use warnings;
use Data::Dump qw(dump);

my %hash;
my $key;
while(<DATA>) {
    chomp;
    if (/^(Chr1_supercontig_\d+)/) {
        $key = $1;
        $hash{$key} = ();
    } else {
        push @{$hash{$key}}, $_;
    }
}
dump%hash;

__DATA__
Chr1_supercontig_000000000  1   500
    PILOT21_588_1_3_14602_59349_1
Chr1_supercontig_000000001  5   100
    PILOT21_588_1_21_7318_90709_1
    PILOT21_588_1_43_18803_144592_1
    PILOT21_588_1_67_13829_193943_1
    PILOT21_588_1_42_19678_132419_1
    PILOT21_588_1_67_4757_125247_1

输出:

(
  "Chr1_supercontig_000000001",
  [
    "    PILOT21_588_1_21_7318_90709_1",
    "    PILOT21_588_1_43_18803_144592_1",
    "    PILOT21_588_1_67_13829_193943_1",
    "    PILOT21_588_1_42_19678_132419_1",
    "    PILOT21_588_1_67_4757_125247_1",
  ],
  "Chr1_supercontig_000000000",
  ["    PILOT21_588_1_3_14602_59349_1"],
)

2
许多好的答案已经有了,所以我会添加一些不依赖于正则表达式的内容,而是基于关键行包含三个空格/制表符分隔条目,而值只有一个的事实。它将自动删除前导空格和换行符,因此相当方便。
use strict;
use warnings;

my %hash;
my $key;

while (<DATA>) {
    my @row = split;
    if (@row > 1) {
        $key = shift @row;
    } else {
        push @{$hash{$key}}, shift @row;
    }
}

use Data::Dumper;
print Dumper \%hash;

__DATA__
Chr1_supercontig_000000000  1   500
    PILOT21_588_1_3_14602_59349_1
Chr1_supercontig_000000001  5   100
    PILOT21_588_1_21_7318_90709_1
    PILOT21_588_1_43_18803_144592_1
    PILOT21_588_1_67_13829_193943_1
    PILOT21_588_1_42_19678_132419_1
    PILOT21_588_1_67_4757_125247_1

1
这是另一个相当简短、清晰的版本:

while (<>) {
   if(/^Chr\S+/) {
      $c=$&;
   } else {
      /\S+/;
      push @{ $p{$c} }, $&;
   }
}

并且打印结果:

foreach my $pc ( sort keys %p ) {
   print "$pc => ".join(", ", @{$p{$pc}})."\n";
}

这是一个更短的打印结果(但我认为第一个更易读):
map { print "$_ => ".join(", ", @{$p{$_}})."\n" } sort keys %p;

命令行中的一行代码:

perl <1 -e 'while(<>){ if(/^Chr\S+/){ $c=$&; }else{ /\S+/; push(@{$p{$c}},$&);} } map { print "$_ => ".join(", ", @{$p{$_}})."\n" } sort keys %p;'

0

试试这个,

#!/usr/bin/perl 
use strict;
use warnings;
use Data::Dumper;

my ( $fh,$cur );
my $hash = ();
open $fh,'<' , 'file' or die "Can not open file\n";

while (<$fh> ) {
    chomp;
    if ( /^(Chr.+? ).+/ ) {
        $cur = $1;
        $hash->{$cur} = '';
    }
    else {
        $hash->{$cur} = $hash->{$cur} .$_ . ',';
    }
}

打印 Dumper $hash;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接