【问题标题】:Efficient way of printing hash of hash of arrays in Perl在 Perl 中打印数组哈希的有效方法
【发布时间】:2014-11-04 06:05:21
【问题描述】:

我正在编写一个脚本,该脚本涉及打印数组散列的散列内容。

ex(伪代码):

my %hash = ();
$hash{key1}{key2} = ['value1', 'value2', 'value3', . . .];

$hash{key1}{key2} = @array_of_values;

基本上,我希望能够对任意数量的键组合执行此操作,并且能够遍历所有可能的键/值对(或者更正确地表述为键、键/数组对,因为每个值实际上都是一个值数组,每个数组有 2 个与之关联的键)并以以下格式打印输出:

"key1, key2, value1, value2, value3, ...\n"

例如:

#!/usr/bin/perl

use strict;
use warnings;

# initialize hash
my %hash = ();
my $string1 = "string1";
my $string2 = "string2";

# push strings onto arrays stored in hash
push @{$hash{a}{b}}, $string1;
push @{$hash{a}{b}}, $string2;
push @{$hash{c}{d}}, $string2;
push @{$hash{c}{d}}, $string1;

# print elements of hash
# (want to do this as a loop for all possible key/value pairs)
local $, = ',';
print "a, b, ";
print @{$hash{a}{b}};
print "\n";
print "c, d, ";
print @{$hash{c}{d}};
print "\n";

system ('pause');

这段代码的输出如下所示:

a, b, string1,string2
c, d, string2,string1
Press any key to continue . . .

我正在考虑使用 each 运算符,但它似乎只适用于一维散列。 (each只返回一个键值对,涉及2个键时不能正常工作)

如何简化此代码以循环遍历哈希并打印所需的输出,而不管我的哈希有多大?

【问题讨论】:

  • 也许应该补充一点,如果您不知道,有些模块会为您打印数据结构,例如Data::Dumper(核心模块)或Data::Dump。例如。 use Data::Dumper; print Dumper \%hash;

标签: perl data-structures hash


【解决方案1】:

我一直在考虑并想出了一种可能的解决方案。如果有人有更优雅的解决方案,我很乐意看到它。谢谢!

这是我想出的解决方案:

#!/usr/bin/perl

use strict;
use warnings;

# initialize hash
my %hash = ();
my $string1 = "string1";
my $string2 = "string2";

# push strings onto arrays stored in hash
push @{$hash{a}{b}}, $string1;
push @{$hash{a}{b}}, $string2;
push @{$hash{c}{d}}, $string2;
push @{$hash{c}{d}}, $string1;

# prints hash of hash of arrays in a loop :)
my @keys1 = keys %hash;
for my $key1 (@keys1) {
    my @keys2 = keys $hash{$key1};
    for my $key2 (@keys2) {
        local $" = ', ';
        print "$key1, $key2, @{$hash{$key1}{$key2}}";
        print "\n";
    }
}

system ('pause');

虽然此解决方案不关心键的顺序,但它会以随机顺序打印。

输出:

c, d, string2, string1
a, b, string1, string2
Press any key to continue . . .

【讨论】:

  • 如果您只使用一次密钥,则无需将它们存储在数组中,即您可以简单地做for my $key1 (keys %hash) { ... }
  • 你只能在哈希上调用keys,所以你的内部循环实际上应该是for my $key2 ( keys %{ $hash{$key1} } ) { ... }
  • @ThisSuitIsBlackNot 记录在perldoc -f keys:Starting with Perl 5.14, keys can take a scalar EXPR, which must contain a reference to an unblessed hash or array. The argument will be dereferenced automatically. This aspect of keys is considered highly experimental. The exact behaviour may change in a future version of Perl.
  • 因此,换句话说,您可以使用哈希引用,但最好不要。
  • @TLP 谢谢。之前有人提到过,我完全忘记了。
【解决方案2】:

即使对于多级散列,使用each 也能正常工作,您只需要确保参数是散列即可。这是一个如何做到这一点的例子。我还展示了如何初始化你的哈希值。

use strict;
use warnings;
use v5.14;

my $string1 = "string1";
my $string2 = "string2";
my %hash = (
    a => { 
        b => [ $string1, $string2 ],
    },
    c => {
        d => [ $string2, $string1 ],
    }
);

for my $key (keys %hash) {
    while (my ($k, $v) = each %{ $hash{$key} }) {
        say join ", ", $key, $k, @$v;
    }
}

输出:

c, d, string2, string1
a, b, string1, string2

请注意使用@$v 来访问最内层数组的简单性,而不是使用有些繁琐的替代@{ $hash{$key}{$k} }

【讨论】:

  • +1 @$v 很简洁 - 但如果一个人是 PBP 的追随者 @{ $hash{$key}{$k} } 会更清楚 ;-) 我想知道后缀取消引用将如何发挥作用在语法使用和实践中。
【解决方案3】:

要打印数组哈希的哈希值,您可以使用foreachfor my 遍历数据结构:

# keys of the outer hash
foreach my $oh (keys %hash) {
    # keys of the inner hash
    foreach my $ih (keys %{$hash{$oh}}) {
        # $hash{$oh}{$ih} is the array, so it can be printed thus:
        print join(", ", $oh, $ih, @{$hash{$oh}{$ih}}) . "\n";

        # or you can iterate through the items like this:
        # foreach my $arr (@{$hash{$oh}{$ih}})
        # {   doSomethingTo($arr); }

    }
}

对于所有制图迷,这里有一个map 版本:

map { my $oh = $_; 
    map { say join( ", ", $oh, $_, @{$hash{$oh}{$_}} )  } keys %{$hash{$_}}
} keys %hash;

【讨论】:

    【解决方案4】:

    这是最简洁的递归子例程,因为它只会递归几次,所以不是一种浪费的解决方案。

    use strict;
    use warnings;
    
    my %hash;
    my ($string1, $string2) = qw/ string1 string2 /;
    
    push @{$hash{a}{b}}, $string1;
    push @{$hash{a}{b}}, $string2;
    push @{$hash{c}{d}}, $string2;
    push @{$hash{c}{d}}, $string1;
    
    print_data(\%hash);
    
    sub print_data {
        my ($ref, $prefix) = (@_, '');
    
       if (ref $ref eq 'ARRAY') {
          print $prefix, join(', ', @$ref), "\n";
       }
       else {
          print_data($ref->{$_}, "$prefix$_, ") for sort keys %$ref;
       }
    }
    

    输出

    a, b, string1, string2
    c, d, string2, string1
    

    【讨论】:

      【解决方案5】:

      听起来你需要一个嵌套循环......可能有一种方法可以使用map 来做到这一点,这种方法更难记住,可读性也更差;-)(当然只对我自己说 - 我期待看到@ 987654326@解决方案出现在这里!):

      #!perl -l
      my %hash = ();
      $hash{key1}{key2} = ['value1', 'value2', 'value3',];
      
      for my $outer (keys %hash) { 
          for my $inner (keys $hash{$outer}) { 
             print join(', ', $outer, $inner, @{ $hash{$outer}{$inner} } )
          } 
      }
      

      输出:

      key1, key2, value1, value2, value3
      

      如果事情真的很复杂,还有Data::PrinterData::SeekData::Dive 和其他几个便利模块。有些需要使用哈希引用,但这可能会使更简单的解决方案复杂化,或者更容易解决更大、更困难的问题......

      干杯,

      编辑:将“嵌套地图”移动到单独的答案中。

      【讨论】:

      • 我同意,我也很想在这里看到map 解决方案。 :) 在发布的解决方案中,这似乎是迄今为止最简单、最直接的解决方案。
      • @tjwrona1992 ...我在map“回答”中编辑了我的回复...如果您认为合适,请惩罚我! :-)
      • 不幸的是,您的代码实际上并没有打印出 OP 要求的答案... :\ 我在答案中添加了 map 版本(尽管我确实喜欢匿名子在你的!)。
      • @tjwrona1992 也许map 方法会受益于5.20 风格postfix derferencing ;-)
      • @G.Cito 他或她的申请。 ;)
      【解决方案6】:

      您还没有说明为什么要打印 HoHoA 的内容(数组散列的散列)。如果出于调试目的,我会使用 Data::Dumper(核心)或 Data::Dump(在 CPAN 上)。

      use Data::Dumper qw(Dumper);
      print Dumper \%hash;
      

      use Data::Dump qw(pp);
      pp \%hash;
      

      假设您希望根据样本格式化输出是有原因的(并且它始终只是一个 HoHoA)我会使用嵌套循环:

      while (my ($ok, $ov) = each %hash) {
          while (my ($ik, $iv) = each %$ov) {
              say join ',', @$iv;
          }
      }
      

      我不建议使用map。它最适合用于列表转换而不是流控制,并且使用嵌套的map 块跟踪外部和内部键很尴尬,两者都使用$_ 处理不同的事情。不过,因为您和 G. Cito 表示有兴趣看到一个,所以这里是:

      say foreach map {
          my $o = $_;
          map {
              my $i = $_;
              join ',', $o, $i, @{$hash{$o}{$i}}
          } keys %{$hash{$o}};
      } keys %hash;
      

      【讨论】:

      • nice :-) 我刚刚做了一张双地图(见上文)......正如我所提到的(对我来说)似乎不太可读并且效率不高,但 errm 似乎很酷呵呵 ;-) 干杯。
      • foreach 不是超出要求,因为您有 map 在那里做基本相同的工作吗?
      • @Michael Carman HoHoA 正在镜像我正在使用的数据库的结构,并且数组存储值字符串以在运行脚本结束时导入数据库。 +1 使用each 的好解决方案虽然:)
      【解决方案7】:

      我要了它:-( ...“嵌套地图”(??)。

      #!perl -l
      my %hash = ();
      $hash{key1}{key2} = ['value1', 'value2', 'value3',];
      map{ &{ sub{
         map{ print for @{$hash{$_[0]}{$_}}} keys $hash{$_} }}($_)} keys %hash;
      

      输出:

      value1
      value2
      value3
      

      注意:我认为从技术上讲(在幕后)它仍然是一个循环并且没有效率。似乎如果您“嵌套”对map 的调用并用匿名sub{& sub { .. 位)包装内部map{},那么您可以通过@_ 引用外部map 主题值( ie$_[0] 中,因为它们进入内部map ...但前提是它们在列表上下文中($_) ... errm (?!?) 好的,我只是抓住这里。

      无论如何,它可以工作,但看起来比它应该的更混乱 - 可能是因为它是一个“anti-pattern”。一个好处是它使“嵌套的for 循环”看起来不那么凌乱并且更易于阅读。

      【讨论】:

      • 很好也很混乱。就像我喜欢的那样:)
      • :) 我一定遗漏了一些东西,但在 perl6 中它似乎有效:my %h = key1 => key2 => <value1 value2 value3> ; "{%h.kv} ".say;.
      • 哇,这是一些奇怪的 Perl 代码! “.”是干什么用的?我从未见过%h.kv 或后面有.say 的引用。
      • perl6 ... 非常不同 :-)
      猜你喜欢
      • 2012-07-22
      • 1970-01-01
      • 2020-10-16
      • 2013-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-21
      • 2015-01-09
      相关资源
      最近更新 更多