添加哈希键

时间:2013-08-01 13:14:28

标签: perl

我使用从0开始的递增数字键将数据添加到哈希。键/值很好。当我添加第二个时,第一个键/值对指向第二个。之后的每次添加都会替换第二个键的值,然后再指向它。 Dumper输出将是这样的。

$VAR1 = { '0' => { ... } }; 

添加第一个键/值后。在添加第二个之后我得到了

$VAR1= { '1' => { ... }, '0' => $VAR1->{'1} }; 

添加第三个键/值后,它看起来像这样。

$VAR1 = { '1' => { ... }, '0' => $VAR1->{'1'}, '2' => $VAR1->{'1'} };  

我的问题是为什么这样做?我希望每个键/值都显示在哈希中。当我遍历哈希时,我得到每个键/值的相同数据。如何摆脱第二个添加键的引用指针?

4 个答案:

答案 0 :(得分:4)

您正在将每个元素的值设置为对同一哈希的引用。 Data :: Dumper只是反映了这一点。

如果您使用Data :: Dumper作为序列化工具(哎呀!),那么您应该将$Data::Dumper::Purity设置为1以获得eval可以处理的内容。

use Data::Dumper qw( Dumper );

my %h2 = (a=>5,b=>6,c=>7);

my %h;
$h{0} = \%h2;
$h{1} = \%h2;
$h{2} = \%h2;

print("$h{0}{c} $h{2}{c}\n");
$h{0}{c} = 9;
print("$h{0}{c} $h{2}{c}\n");

{
   local $Data::Dumper::Purity = 1;
   print(Dumper(\%h));
}

输出:

7 7
9 9
$VAR1 = {
          '0' => {
                   'c' => 9,
                   'a' => 5,
                   'b' => 6
                 },
          '1' => {},
          '2' => {}
        };
$VAR1->{'0'} = $VAR1->{'1'};
$VAR1->{'2'} = $VAR1->{'1'};

另一方面,如果您不想将商店引用用于不同的哈希值,则可以使用

# Shallow copies
$h{0} = { %h2 };  # { ... }   means   do { my %anon = ( ... ); \%anon }
$h{1} = { %h2 };
$h{2} = { %h2 };

# Deep copies
use Storable qw( dclone );
$h{0} = dclone(\%h2);
$h{1} = dclone(\%h2);
$h{2} = dclone(\%h2);

输出:

7 7
9 7
$VAR1 = {
          '0' => {
                   'a' => 5,
                   'b' => 6,
                   'c' => 9
                 },
          '1' => {
                   'a' => 5,
                   'b' => 6,
                   'c' => 7
                 },
          '2' => {
                   'a' => 5,
                   'b' => 6,
                   'c' => 7
                 }
        };

答案 1 :(得分:2)

您尚未发布用于构建哈希的实际代码,但我认为它看起来像这样:

foreach my $i (1 .. 3) {
    %hash2 = (number => $i, foo => "bar", baz => "whatever");
    $hash1{$i} = \%hash2;
}

(实际上,我猜你在实际的代码中,你可能正在从while (<>)循环中的文件中读取数据,并根据它将值分配给%hash2,但是{ {1}}循环将用于演示目的。)

如果您运行上面的代码并使用Data :: Dumper转储生成的foreach,您将获得输出:

%hash1

为什么会这样?好吧,这是因为$VAR1 = { '1' => { 'baz' => 'whatever', 'number' => 3, 'foo' => 'bar' }, '3' => $VAR1->{'1'}, '2' => $VAR1->{'1'} }; 中的值都是指向相同哈希的引用,即%hash1。在循环中为%hash2分配新值时,这些值将覆盖%hash2中的旧值,但它仍将是相同的散列值。 Data :: Dumper只是强调了这一事实。

那么,你怎么解决它?嗯,有(至少)两种方式。一种方法是将%hash2替换为\%hash2,将%hash2替换为{ %hash2 },将%hash2的内容替换为新的匿名hash并返回对它的引用:

foreach my $i (1 .. 3) {
    %hash2 = (number => $i, foo => "bar", baz => "whatever");
    $hash1{$i} = { %hash2 };
}

另一种(IMO最好的)方法是使用%hash2在循环中将my声明为(词法范围的)局部变量:

foreach my $i (1 .. 3) {
    my %hash2 = (number => $i, foo => "bar", baz => "whatever");
    $hash1{$i} = \%hash2;
}

这样,循环的每次迭代都会创建一个 new ,不同的散列名为%hash2,而在之前的迭代中创建的散列将继续存在(因为它们是从%hash1)独立。

顺便说一下,如果您遵循标准的Perl最佳实践,首先就不会遇到这个问题,具体而言:

  • 始终use strict;(和use warnings;)。这会强迫您使用%hash2声明my(虽然它不会强迫您在循环中

    )。

  • 始终在尽可能小的范围内声明局部变量。在这种情况下,由于%hash2仅在循环中使用,因此您应该在循环中声明它,如上所述。

遵循这些最佳实践,上面的示例代码如下所示:

use strict;
use warnings;
use Data::Dumper qw(Dumper);

my %hash1;
foreach my $i (1 .. 3) {
    my %hash2 = (number => $i, foo => "bar", baz => "whatever");
    $hash1{$i} = \%hash2;
}

print Dumper(\%hash1);

正如预期的那样,将打印出来:

$VAR1 = {
          '1' => {
                   'baz' => 'whatever',
                   'number' => 1,
                   'foo' => 'bar'
                 },
          '3' => {
                   'baz' => 'whatever',
                   'number' => 3,
                   'foo' => 'bar'
                 },
          '2' => {
                   'baz' => 'whatever',
                   'number' => 2,
                   'foo' => 'bar'
                 }
        };

答案 2 :(得分:0)

当您不发布Data :: Dumper的代码或实际结果时,很难看出问题是什么。

关于Data :: Dumper,您应该知道一件事:当您转储数组或(特别是)散列时,您应该转储对它的引用。否则,Data :: Dumper会将其视为一系列变量。另请注意,哈希 保留在您创建它们的顺序中。我在下面附上一个例子。确保您的问题与令人困惑的Data :: Dumper输出无关。

另一个问题:如果您通过顺序键键入哈希值,那么使用数组会更好吗?

如果可以,请编辑您的问题以发布您的代码和实际结果。

use strict;
use warnings;
use autodie;
use feature qw(say);
use Data::Dumper;

my @array = qw(one two three four five);

my %hash = (one => 1, two => 2, three => 3, four => 4);

say "Dumped Array: " . Dumper @array;
say "Dumped Hash: " . Dumper %hash;
say "Dumped Array Reference: " . Dumper \@array;
say "Dumped Hash Reference: " . Dumper \%hash;

输出:

Dumped Array: $VAR1 = 'one';
$VAR2 = 'two';
$VAR3 = 'three';
$VAR4 = 'four';
$VAR5 = 'five';

Dumped Hash: $VAR1 = 'three';
$VAR2 = 3;
$VAR3 = 'one';
$VAR4 = 1;
$VAR5 = 'two';
$VAR6 = 2;
$VAR7 = 'four';
$VAR8 = 4;

Dumped Array Reference: $VAR1 = [
          'one',
          'two',
          'three',
          'four',
          'five'
        ];

Dumped Hash Reference: $VAR1 = {
          'three' => 3,
          'one' => 1,
          'two' => 2,
          'four' => 4
        };

答案 3 :(得分:0)

这样做的原因是你给同一个哈希提供了相同的引用 大概是在一个循环结构中。

这是一个有这种行为的简单程序。

use strict;
use warnings;
# always use the above two lines until you
# understand completely why they are recommended

use Data::Printer;

my %hash;
my %inner; # <-- wrong place to put it

for my $index (0..5){
  $inner{int rand} = $index; # <- doesn't matter

  $hash{$index} = \%inner;
}

p %hash;

要修复它,只需确保每次循环都要创建一个新的哈希引用。

use strict;
use warnings;
use Data::Printer;

my %hash;

for my $index (0..5){
  my %inner; # <-- place the declaration here instead

  $inner{int rand} = $index; # <- doesn't matter

  $hash{$index} = \%inner;
}

p %hash;

如果您只是为索引使用数字,并且它们从0开始单调增加,那么我建议使用数组。
数组会更快,内存效率更高。

use strict;
use warnings;
use Data::Printer;

my @array; # <--

for my $index (0..5){
  my %inner;
  $inner{int rand} = $index;

  $array[$index] = \%inner; # <--
}

p @array;