如何在 Perl 中正确替换哈希数组中的值?
how to replace values in an array of hashes properly in Perl?
如下所示,我有一个 foreach 循环,其中一个哈希数组中的值被另一个哈希数组中的值替换。
第二个 foreach 循环只是打印并测试值是否被正确分配。
foreach my $row (0 .. $#row_buff) {
$row_buff[$row]{'offset'} = $vars[$row]{'expression'};
print $row_buff[$row]{'offset'},"\n";
}
foreach (0 .. $#row_buff) {
print $row_buff[$_]{'offset'},"\n";
}
这里@row_buff和@vars是两个哈希数组。它们预先填充了所有使用的键的值。
散列像这样被推入数组:
push @row_buff, \%hash;
问题:
假设第一个 foreach 打印中的打印语句是这样的:
string_a
string_b
string_c
string_d
那么第二个foreach循环打印的打印语句是这样的:
string_d
string_d
string_d
string_d
这就是让我困惑的地方。两个打印语句都应该以完全相同的方式打印,对吗?但是第二个 print 语句打印的值只是以重复方式单独打印的最后一个值。有人可以指出这里可能出了什么问题吗?非常感谢任何提示。这是我第一次提出问题,如有遗漏请见谅。
更新
我可以添加一些信息,对此大家感到抱歉。 foreach之前多了一行,是这样的:
@row_buff = (@row_buff) x $itercnt;
foreach my $row (0 .. $#row_buff) {
$row_buff[$row]{'offset'} = $vars[$row]{'expression'};
print $row_buff[$row]{'offset'},"\n";
}
foreach (0 .. $#row_buff) {
print $row_buff[$_]{'offset'},"\n";
}
$itercnt 是一个整数。我用它复制了很多次@row_buff。
这显然与在数组上存储引用有关,而不是独立数据。由于未提供详细信息,因此尚不清楚这是如何发生的,但以下讨论应该有所帮助。
考虑这两个基本示例。
首先,在一个数组上放置一个散列(引用),每次先改变一个值
use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);
# use Storable qw(dclone);
my %h = ( a => 1, b => 2 );
my @ary_w_refs;
for my $i (1..3) {
$h{a} = $i;
push @ary_w_refs, \%h; # almost certainly WRONG
# push @ary_w_refs, { %h }; # *copy* data
# push @ary_w_refs, dclone \%h; # may be necessary, or just safer
}
dd $_ for @ary_w_refs;
我用Data::Dump for displaying complex data structures, for its simplicity and default compact output. There are other modules for this purpose, Data::Dumper在内核中(已安装)
以上打印
{ a => 3, b => 2 }
{ a => 3, b => 2 }
{ a => 3, b => 2 }
看看键 a
的值,我们每次都在散列中更改,因此应该为每个数组元素设置一个 不同的 值( 1
, 2
, 3
) -- 最后是一样的,等于我们上次分配的那个? (这似乎是问题中的情况。)
这是因为我们为每个元素分配了一个reference到散列%h
,所以即使每次循环我们首先改变散列中的值对于最后那个键,它只是在每个元素处对同一个散列的引用。∗
所以当循环后查询数组时,我们只能得到散列中的内容(在键 a
处,它是最后分配的数字,3
)。该数组没有自己的数据,只有一个指向散列数据的指针。†(因此散列的数据也可以通过写入数组来更改,如下例所示。 )
大多数时候,我们想要一个单独的、独立的副本。解决方案?复制数据。
天真,而不是
push @ary_w_refs, \%h;
我们可以做到
push @ary_w_refs, { %h };
这里{}
是一个匿名散列的构造函数,‡所以%h
里面被复制了。所以实际数据进入数组并且一切都很好?在这种情况下,是的,哈希值是普通的 strings/numbers.
但是当散列值本身是引用时怎么办?然后这些引用被复制,并且 @ary_w_refs
再次没有自己的数据!我们会遇到完全相同的问题。 (尝试上面的散列为 ( a => [1..10] )
)
如果我们有一个复杂的数据结构,携带值的引用,我们需要一个深拷贝。做到这一点的一个好方法是使用库,Storable 及其 dclone
非常好
use Storable qw(dclone);
...
push @ary_w_refs, dclone \%h;
现在数组元素有自己的数据,与 %h
无关(但在复制时相等)。
这也是使用简单的 hash/array 做的一件好事,以防止将来发生更改,从而更改散列但我们忘记了它被复制的位置(或散列及其及其副本甚至不知道彼此)。
再举个例子。让我们用 hashref 填充一个数组,然后将它复制到另一个数组
use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd pp);
my %h = ( a => 1, b => 2 );
my @ary_src = \%h;
say "Source array: ", pp \@ary_src;
my @ary_tgt = $ary_src[0];
say "Target array: ", pp \@ary_tgt;
$h{a} = 10;
say "Target array: ", pp(\@ary_tgt), " (after hash change)";
$ary_src[0]{b} = 20;
say "Target array: ", pp(\@ary_tgt), " (after hash change)";
$ary_tgt[0]{a} = 100;
dd \%h;
(为简单起见,我使用只有一个元素的数组。)
这会打印
Source array: [{ a => 1, b => 2 }]
Target array: [{ a => 1, b => 2 }]
Target array: [{ a => 10, b => 2 }] (after hash change)
Target array: [{ a => 10, b => 20 }] (after hash change)
{ a => 100, b => 20 }
那个“目标”数组,据说只是从源数组中复制出来的,当远程哈希发生变化时会发生变化!当它的源数组发生变化时。同样,这是因为对散列的引用被复制,首先复制到一个数组,然后复制到另一个数组。
为了获得独立的数据副本,再次,每次都复制数据。我再次建议为安全起见并使用 Storable::dclone
(或者当然是等效的库),即使使用简单的哈希和数组也是如此。
最后,请注意最后一个有点险恶的情况——写入该数组会更改散列!这个(第二次复制的)数组可能远离散列,在散列甚至不知道的函数(在另一个模块中)中。这种错误可能是真正隐藏错误的来源。
现在,如果您澄清引用的复制位置,并更完整(简单)地说明您的问题,我们可以提供更具体的补救措施。
∗ 正确使用引用的一种重要方式,也是经常使用的方式,就是每次通过
for my $elem (@data) {
my %h = ...
...
push @results, \%h; # all good
}
词法 %h
每次都会重新引入,因此保留数组中引用的数据,因为数组在循环之外持续存在,每个元素都是独立的。
这样做也更有效,因为 %h
中的数据没有被复制,就像 { %h }
中的数据一样,而只是“重新调整用途”,可以这么说,从在迭代结束时被销毁的词法 %h
到数组中的引用。
如果要复制的结构自然存在于循环之外,这当然可能并不总是合适的。然后使用它的深拷贝。
同一种机制在函数调用中起作用
sub some_func {
...
my %h = ...
...
return \%h; # good
}
my $hashref = some_func();
同样,词法 %h
作为函数 returns 超出范围,它不再存在,但它携带的数据和对它的引用被保留,因为它是 returned 和分配的,所以它的引用计数是非零的。 (至少 returned 给调用者,也就是说;它可能在子程序执行期间被传递到其他地方,所以我们可能仍然会与多个参与者一起工作相同的引用。)因此 $hashref
具有对在子中创建的数据的引用。
回想一下,如果一个函数被传递了一个引用,当它被调用时或在执行期间(通过调用其他 return 引用的子程序),更改并 return 编辑它,然后再次我们在某些调用者中更改了数据,可能与这部分程序流程相去甚远。
这当然是经常做的,因为有更大的数据池,不能一直复制,但是需要小心组织代码(例如,尽可能模块化)所以以尽量减少出错的机会。
† 这是对“指针”一词的一种松散使用,用于表示引用的作用,但如果要引用 C,我会说它有点一个“打扮”的 C 指针
‡ 在不同的上下文中它可以是一个块
如下所示,我有一个 foreach 循环,其中一个哈希数组中的值被另一个哈希数组中的值替换。
第二个 foreach 循环只是打印并测试值是否被正确分配。
foreach my $row (0 .. $#row_buff) {
$row_buff[$row]{'offset'} = $vars[$row]{'expression'};
print $row_buff[$row]{'offset'},"\n";
}
foreach (0 .. $#row_buff) {
print $row_buff[$_]{'offset'},"\n";
}
这里@row_buff和@vars是两个哈希数组。它们预先填充了所有使用的键的值。
散列像这样被推入数组:
push @row_buff, \%hash;
问题: 假设第一个 foreach 打印中的打印语句是这样的:
string_a
string_b
string_c
string_d
那么第二个foreach循环打印的打印语句是这样的:
string_d
string_d
string_d
string_d
这就是让我困惑的地方。两个打印语句都应该以完全相同的方式打印,对吗?但是第二个 print 语句打印的值只是以重复方式单独打印的最后一个值。有人可以指出这里可能出了什么问题吗?非常感谢任何提示。这是我第一次提出问题,如有遗漏请见谅。
更新
我可以添加一些信息,对此大家感到抱歉。 foreach之前多了一行,是这样的:
@row_buff = (@row_buff) x $itercnt;
foreach my $row (0 .. $#row_buff) {
$row_buff[$row]{'offset'} = $vars[$row]{'expression'};
print $row_buff[$row]{'offset'},"\n";
}
foreach (0 .. $#row_buff) {
print $row_buff[$_]{'offset'},"\n";
}
$itercnt 是一个整数。我用它复制了很多次@row_buff。
这显然与在数组上存储引用有关,而不是独立数据。由于未提供详细信息,因此尚不清楚这是如何发生的,但以下讨论应该有所帮助。
考虑这两个基本示例。
首先,在一个数组上放置一个散列(引用),每次先改变一个值
use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);
# use Storable qw(dclone);
my %h = ( a => 1, b => 2 );
my @ary_w_refs;
for my $i (1..3) {
$h{a} = $i;
push @ary_w_refs, \%h; # almost certainly WRONG
# push @ary_w_refs, { %h }; # *copy* data
# push @ary_w_refs, dclone \%h; # may be necessary, or just safer
}
dd $_ for @ary_w_refs;
我用Data::Dump for displaying complex data structures, for its simplicity and default compact output. There are other modules for this purpose, Data::Dumper在内核中(已安装)
以上打印
{ a => 3, b => 2 } { a => 3, b => 2 } { a => 3, b => 2 }
看看键 a
的值,我们每次都在散列中更改,因此应该为每个数组元素设置一个 不同的 值( 1
, 2
, 3
) -- 最后是一样的,等于我们上次分配的那个? (这似乎是问题中的情况。)
这是因为我们为每个元素分配了一个reference到散列%h
,所以即使每次循环我们首先改变散列中的值对于最后那个键,它只是在每个元素处对同一个散列的引用。∗
所以当循环后查询数组时,我们只能得到散列中的内容(在键 a
处,它是最后分配的数字,3
)。该数组没有自己的数据,只有一个指向散列数据的指针。†(因此散列的数据也可以通过写入数组来更改,如下例所示。 )
大多数时候,我们想要一个单独的、独立的副本。解决方案?复制数据。
天真,而不是
push @ary_w_refs, \%h;
我们可以做到
push @ary_w_refs, { %h };
这里{}
是一个匿名散列的构造函数,‡所以%h
里面被复制了。所以实际数据进入数组并且一切都很好?在这种情况下,是的,哈希值是普通的 strings/numbers.
但是当散列值本身是引用时怎么办?然后这些引用被复制,并且 @ary_w_refs
再次没有自己的数据!我们会遇到完全相同的问题。 (尝试上面的散列为 ( a => [1..10] )
)
如果我们有一个复杂的数据结构,携带值的引用,我们需要一个深拷贝。做到这一点的一个好方法是使用库,Storable 及其 dclone
非常好
use Storable qw(dclone);
...
push @ary_w_refs, dclone \%h;
现在数组元素有自己的数据,与 %h
无关(但在复制时相等)。
这也是使用简单的 hash/array 做的一件好事,以防止将来发生更改,从而更改散列但我们忘记了它被复制的位置(或散列及其及其副本甚至不知道彼此)。
再举个例子。让我们用 hashref 填充一个数组,然后将它复制到另一个数组
use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd pp);
my %h = ( a => 1, b => 2 );
my @ary_src = \%h;
say "Source array: ", pp \@ary_src;
my @ary_tgt = $ary_src[0];
say "Target array: ", pp \@ary_tgt;
$h{a} = 10;
say "Target array: ", pp(\@ary_tgt), " (after hash change)";
$ary_src[0]{b} = 20;
say "Target array: ", pp(\@ary_tgt), " (after hash change)";
$ary_tgt[0]{a} = 100;
dd \%h;
(为简单起见,我使用只有一个元素的数组。)
这会打印
Source array: [{ a => 1, b => 2 }] Target array: [{ a => 1, b => 2 }] Target array: [{ a => 10, b => 2 }] (after hash change) Target array: [{ a => 10, b => 20 }] (after hash change) { a => 100, b => 20 }
那个“目标”数组,据说只是从源数组中复制出来的,当远程哈希发生变化时会发生变化!当它的源数组发生变化时。同样,这是因为对散列的引用被复制,首先复制到一个数组,然后复制到另一个数组。
为了获得独立的数据副本,再次,每次都复制数据。我再次建议为安全起见并使用 Storable::dclone
(或者当然是等效的库),即使使用简单的哈希和数组也是如此。
最后,请注意最后一个有点险恶的情况——写入该数组会更改散列!这个(第二次复制的)数组可能远离散列,在散列甚至不知道的函数(在另一个模块中)中。这种错误可能是真正隐藏错误的来源。
现在,如果您澄清引用的复制位置,并更完整(简单)地说明您的问题,我们可以提供更具体的补救措施。
∗ 正确使用引用的一种重要方式,也是经常使用的方式,就是每次通过
for my $elem (@data) {
my %h = ...
...
push @results, \%h; # all good
}
词法 %h
每次都会重新引入,因此保留数组中引用的数据,因为数组在循环之外持续存在,每个元素都是独立的。
这样做也更有效,因为 %h
中的数据没有被复制,就像 { %h }
中的数据一样,而只是“重新调整用途”,可以这么说,从在迭代结束时被销毁的词法 %h
到数组中的引用。
如果要复制的结构自然存在于循环之外,这当然可能并不总是合适的。然后使用它的深拷贝。
同一种机制在函数调用中起作用
sub some_func {
...
my %h = ...
...
return \%h; # good
}
my $hashref = some_func();
同样,词法 %h
作为函数 returns 超出范围,它不再存在,但它携带的数据和对它的引用被保留,因为它是 returned 和分配的,所以它的引用计数是非零的。 (至少 returned 给调用者,也就是说;它可能在子程序执行期间被传递到其他地方,所以我们可能仍然会与多个参与者一起工作相同的引用。)因此 $hashref
具有对在子中创建的数据的引用。
回想一下,如果一个函数被传递了一个引用,当它被调用时或在执行期间(通过调用其他 return 引用的子程序),更改并 return 编辑它,然后再次我们在某些调用者中更改了数据,可能与这部分程序流程相去甚远。
这当然是经常做的,因为有更大的数据池,不能一直复制,但是需要小心组织代码(例如,尽可能模块化)所以以尽量减少出错的机会。
† 这是对“指针”一词的一种松散使用,用于表示引用的作用,但如果要引用 C,我会说它有点一个“打扮”的 C 指针
‡ 在不同的上下文中它可以是一个块