簡體   English   中英

Perl列出哈希中所有具有相同值的鍵

[英]Perl list all keys in hash with identical values

如果我有一個以冒號分隔的文件名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

您的示例無法按您希望的方式工作,因為-n選項在單行程序周圍放置了while循環,因此將為文件中的每個記錄創建並保存聲明的哈希。 您可以通過不聲明哈希來解決該問題,並使其成為一個持久包變量,該變量將保留存儲在其中的所有值。

然后,您可以編寫push @{ $hash{$F[2]} }, $F[0]但請注意,它應該是$F[0]等,而不是@F[0] ,並且我已經使用push to為每個第3列值創建一個第1列值的列表,而不僅僅是將每個第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
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";

數組用於保存值列表,因此請使用數組。

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塊在退出時執行。 在其中,我們遍歷哈希鍵。 如果當前哈希元素的值是一個只有一個元素的數組,則將其跳過。 否則,我們將打印鍵,然后打印由hash元素引用的數組的內容。

這是另一種方式:

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

我們讀取每一行並使用第三列作為鍵,第一列作為匹配鍵的值列表來創建數組的哈希。 END塊中,我們使用grep和過濾器鍵(其數組計數大於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. 保留所有值。

最后一個是最有趣的,因為它涉及到對數組的引用作為哈希值:

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很清楚。 第二個@{[...]}是一個巧妙的小把戲。

你嘗試了什么?

如果將哈希reverse為值=>鍵對的列表,然后對列表使用List :: Util的pairs() ,則可以將哈希值轉換為值=>鍵arrayrefs的哈希。 ( foo => [ 'bar', 'baz' ] )grep {@{$hash{$_}} > 1} keys %hash ,並打印結果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM