Perl列出具有相同值的散列中的所有键

时间:2014-08-05 15:05:13

标签: perl hash comparison command-line-interface

如果我有冒号分隔的文件名FILE,我会这样做:

cat FILE|perl -F: -lane 'my %hash = (); $hash{@F[0]} = @F[2]'

将第一个和第三个令牌分配为键=>哈希的值对..

1)这是一种将键值对分配给哈希的合理方法吗?

2)现在找到具有共享值的所有密钥并列出它们的最简单方法是什么?

假设FILE看起来像:

 Mike:34:Apple:Male
 Don:23:Corn:Male
 Jared:12:Apple:Male
 Beth:56:Maize:Female
 Sam:34:Apple:Male
 David:34:Apple:Male

所需输出:Keys with value "Apple": Mike,Jared,David,Sam

6 个答案:

答案 0 :(得分:3)

您的示例将无法正常工作,因为-n选项会在您的单行程序周围放置while循环,因此您声明的哈希值会为文件中的每个记录创建并停用。你可以通过不声明哈希来解决这个问题,因此使它成为一个持久的包变量,它将保留存储在其中的所有值。

然后您可以撰写push @{ $hash{$F[2]} }, $F[0],但请注意它应该是$F[0]等而不是@F[0],并且我已使用 push 创建列表第1列的每列第3列值而不是仅列出一对一值的列表,每列第1列值与第3列值相关。

为了澄清,您的方法会生成一个类似于此的哈希值,必须搜索该哈希值才能生成所需的显示。

(
  Beth  => "Maize",
  David => "Apple",
  Don   => "Corn",
  Jared => "Apple",
  Mike  => "Apple",
  Sam   => "Apple",
)

虽然我创造了这个,你可以看到它几乎已经是你想要的形式。

(
  Apple => ["Mike", "Jared", "Sam", "David"],
  Corn  => ["Don"],
  Maize => ["Beth"],
)

但是我认为这个问题有点太大了,无法通过单行Perl程序解决。下面的解决方案将输入文件的路径作为命令行参数,如此

> perl prog.pl colons.csv

但如果没有指定文件,它将默认为myfile.csv

use strict;
use warnings;

our @ARGV = 'myfile.csv' unless @ARGV;

my %data;
while (<>) {
  my @fields = split /:/;
  push @{ $data{$fields[2]} }, $fields[0];
}

while (my ($k, $v) = each %data) {
  next unless @$v > 1;
  printf qq{Keys with value "%s": %s\n}, $k, join ', ', @$v;
}

<强>输出

Keys with value "Apple": Mike, Jared, Sam, David

答案 1 :(得分:1)

use strict;
use warnings;

open my $in, '<', 'in.txt';
my %data;
while(<$in>){
    chomp;
    my @split = split/:/;
    $data{$split[0]} = $split[2];
}

my $query = 'Apple';

print "Keys with value $query = ";
foreach my $name (keys %data){
    print "$name " if $data{$name} eq $query;
}
print "\n";

答案 2 :(得分:1)

数组用于保存值列表,因此请使用数组。

perl -F: -lane'
   push @{ $h{$F[2]} }, $F[0];
   END {
      for my $fruit (keys %h) {
         next if @{ $h{$fruit} } < 2;
         print "$fruit: ", join(",", @{ $h{$fruit} });
      }
   }
' FILE

退出时执行END块。在其中,我们迭代哈希的键。如果当前哈希元素的值是只包含一个元素的数组,则跳过它。否则,我们打印密钥,然后打印哈希元素引用的数组的内容。

答案 3 :(得分:1)

这是另一种方式:

perl -F: -lane'
    push @{ $h{$F[2]} }, $F[0];
}{
    print "$_: ", join(",", @{ $h{$_} }) for grep { @{$h{$_}} > 1 } keys %h;
' file

我们读取每一行并使用第三列作为键创建数组的散列,并使用第一列作为匹配键的值列表。在END块中,我们使用grep迭代我们的哈希,并过滤数组计数大于1的键,然后打印键,后跟数组元素。

答案 4 :(得分:1)

  

它不一定是单行,

好。它不会......

  

这是一种将键值对分配给哈希的合理方法吗?

您只需将键值对分配为:

$hash{"key"} = "value";

这很简单。可能有一种方法可以通过map来实现。但是,我看到的主要问题是如果您有重复的密钥会发生什么。

假设你的文件是这样的:

Mike:34:Apple:Male
Don:23:Corn:Male
Jared:12:Apple:Male
Beth:56:Maize:Female
Sam:34:Apple:Male
David:34:Apple:Male   # Note this entry is here twice!
David:35:Wheat:Male   # Note this entry is here twice!

让我们做一个简单的赋值循环:

my %hash;
while my $line ( <$fh> ) {
    chomp $line;
    my ($name, $age, $category, $sex) = split /:/, $line;
    $hash{$name} = $category;
}

当您到达$hash{David}时,它将首先设置为Apple,然后您将值更改为Wheat。有四种方法可以解决这个问题:

  1. 使用最后一个值。循环没有变化。
  2. 使用第一个值并忽略后续值。这很简单。
  3. 如果发生这种情况,那就是错误。中止程序并报告错误。
  4. 保留所有值。
  5. 最后一个是最有趣的,因为它涉及对数组的引用作为哈希的值:

    my %hash;
    while my $line ( <$fh> ) {
        chomp $line;
        my ($name, $age, $category, $sex) = split /:/, $line;
        $hash{$name} = [] if not exists $hash{$name};   # I'm making this an array reference
        push @{ $hash{$name} }, $category;
    }
    

    现在,我的哈希值中的每个值都是对数组的引用:

    my @values = @{ $hash{David} );   # The values of David...
    print "David is in categories " . join ( ", ", @values ) . "\n";
    

    这将打印出David is in categories Wheat, Apple

      

    现在找到包含共享值的所有密钥并列出它们的最简单方法是什么?

    最简单的方法是创建一个由您的值键入的第二个哈希。在此哈希中,您将需要使用数组引用。我们假设现在没有重复的名称:

    my %hash;
    my %indexed_hash;
    while my $line ( <$fh> ) {
        chomp $line;
        my ($name, $age, $category, $sex) = split /:/, $line;
        $hash{$name} = $category;
    
        my $indexed_hash{$category} = [] if not exist $indexed_hash{$category};
        push @{ $indexed_hash{$category} }, $name;
    }
    

    现在,如果我想查找Apple的所有副本:

    my @names = @{ $indexed_hash{Apple} };
    print "The following are in 'Apple': " . join ( ", " @names ) . "\n";
    

    由于我们正在进行引用,因此我们可以更进一步,并将您文件的所有值存储在哈希中。同样,为简单起见,我假设每个名称只有一个条目:

    my %hash;
    while my $line ( <$fh> ) {
        chomp $line;
        my ($name, $age, $category, $sex) = split /:/, $line;
        $hash{$name}->{AGE}      = $age;
        $hash{$name}->{CATEGORY} = $category;
        $hash{$name}->{SEX}      = $sex;
    }
    
    for my $name ( sort keys %hash ) {
        print "$name Information:\n";
        print "    Age: " . $hash{$name}->{AGE} . "\n";
        printf "Category: %s\n",  $hash{$name}->{CATEGORY};
        print "    Sex: @{[$hash{$name}->{SEX}]}\n\n";
    }
    

    最后两个语句是将复杂数据结构插入字符串的更简单方法。 printf非常明确。第二个@{[...]}是一个巧妙的小技巧。

答案 5 :(得分:0)

你有什么尝试?

如果您将reverse哈希值列入值列表=&gt;密钥对然后对列表使用List :: Util的pairs(),您可以将哈希值转换为值的哈希值=&gt; key arrayrefs。即( foo => [ 'bar', 'baz' ] )grep {@{$hash{$_}} > 1} keys %hash,并打印结果。