如何在 Perl 中正确替换哈希数组中的值?

Vis*_*333 2 arrays perl hash

如下所示,我有一个 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";
}
Run Code Online (Sandbox Code Playgroud)

这里@row_buff和@vars是两个哈希数组。它们预先填充了所有使用的键的值。

哈希值被推入数组,如下所示:

推@row_buff,\%散列;

问题:假设第一个 foreach 打印中的打印语句如下:

string_a
string_b
string_c
string_d
Run Code Online (Sandbox Code Playgroud)

然后第二个 foreach 循环中的打印语句如下所示:

string_d
string_d
string_d
string_d
Run Code Online (Sandbox Code Playgroud)

这就是让我困惑的地方。两个打印语句应该以完全相同的方式打印,对吗?但是第二个打印语句打印的值只是重复方式的最后一个值。有人可以指出我这里可能出了什么问题吗?任何提示都将不胜感激。这是我第一次提出问题,如果我遗漏了什么,请原谅我。

更新

我可以添加一些信息,对此大家表示歉意。在 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";
}
Run Code Online (Sandbox Code Playgroud)

$itercnt 是一个整数。我用它多次复制@row_buff。

zdi*_*dim 5

这显然与在数组上存储引用而不是独立数据有关。由于没有给出详细信息,因此尚不清楚这是如何发生的,但以下讨论应该会有所帮助。

\n

考虑这两个基本例子。

\n

首先,在数组上放置一个散列(引用),每次首先更改一个值

\n
use warnings;\nuse strict;\nuse feature \'say\';\nuse Data::Dump qw(dd);\n# use Storable qw(dclone);\n\nmy %h = ( a => 1, b => 2 );\n\nmy @ary_w_refs;\n\nfor my $i (1..3) {\n    $h{a} = $i; \n    push @ary_w_refs, \\%h;           # almost certainly WRONG\n\n    # push @ary_w_refs, { %h };      # *copy* data\n    # push @ary_w_refs, dclone \\%h;  # may be necessary, or just safer\n}\n\ndd $_ for @ary_w_refs;\n
Run Code Online (Sandbox Code Playgroud)\n

我使用Data::Dump来显示复杂的数据结构,因为它的简单性和默认的紧凑输出。还有其他模块用于此目的,Data::Dumper位于核心(已安装)。

\n

以上打印

\n
\n{ a => 3, b => 2 }\n{ a => 3, b => 2 }\n{ a => 3, b => 2 }\n
\n

看看我们每次在哈希中更改的 key 值如何a,因此应该为每个数组元素设置不同的值(1, 2, 3) - 最终是相同的,并且等于我们最后分配的值?(问题中似乎就是这种情况。)

\n

这是因为我们为每个元素分配了对哈希的引用%h,因此即使每次循环时我们首先更改该键的哈希值,最终它只是每个元素处的引用相同的哈希值。\xe2\x88\x97

\n

因此,当循环后查询数组时,我们只能获取哈希中的内容(关键是a它是最后分配的数字,3)。该数组没有自己的数据,只有一个指向哈希数据的指针。\xe2\x80\xa0(因此散列的数据也可以通过写入数组来更改,如下面的示例所示。)

\n

大多数时候,我们想要一个单独的、独立的副本。解决方案?复制数据。

\n

天真地,而不是

\n
push @ary_w_refs, \\%h;\n
Run Code Online (Sandbox Code Playgroud)\n

我们可以做的

\n
push @ary_w_refs, { %h };\n
Run Code Online (Sandbox Code Playgroud)\n

{}是一个匿名哈希的构造函数,\xe2\x80\xa1因此%h内部被复制。那么实际数据进入数组后一切都很好吗?在这种情况下,是的,其中哈希值是纯字符串/数字。

\n

但是当哈希值本身就是引用时怎么办?然后这些引用被复制,并且@ary_w_refs再次没有自己的数据!我们会遇到完全相同的问题。(尝试上面的哈希值为( a => [1..10] )

\n

如果我们有一个复杂的数据结构,携带值的引用,我们需要一个深拷贝。一个好的方法是使用库,Storable及其dclone非常好

\n
use Storable qw(dclone);\n...\n\n    push @ary_w_refs, dclone \\%h;\n
Run Code Online (Sandbox Code Playgroud)\n

现在数组元素有自己的数据,与 无关(但在复制时等于)%h

\n

对于简单的散列/数组来说,这也是一件好事,可以避免将来的更改,即散列发生更改,但我们忘记了它被复制的位置(或者散列及其副本不存在)甚至彼此都不了解)。

\n

另一个例子。让我们用 hashref 填充一个数组,然后将其复制到另一个数组

\n
use warnings;\nuse strict;\nuse feature \'say\';    \nuse Data::Dump qw(dd pp);\n\nmy %h = ( a => 1, b => 2 );\n\nmy @ary_src = \\%h;\nsay "Source array: ", pp \\@ary_src;\n\nmy @ary_tgt = $ary_src[0];\nsay "Target array: ", pp \\@ary_tgt;\n\n$h{a} = 10;\nsay "Target array: ", pp(\\@ary_tgt), " (after hash change)";\n\n$ary_src[0]{b} = 20;\nsay "Target array: ", pp(\\@ary_tgt), " (after hash change)";\n\n$ary_tgt[0]{a} = 100;\ndd \\%h;\n
Run Code Online (Sandbox Code Playgroud)\n

(为简单起见,我使用只有一个元素的数组。)

\n

这打印

\n
\n源数组:[{ a => 1, b => 2 }]\n目标数组:[{ a => 1, b => 2 }]\n目标数组:[{ a => 10, b => 2 } ](哈希更改后)\n目标数组:[{ a => 10, b => 20 }](哈希更改后)\n{ a => 100, b => 20 }\n
\n

这个“目标”数组,据说只是从源数组中复制而来,当远程哈希发生变化时,它就会发生变化!当它的源数组发生变化时。同样,这是因为对哈希的引用被复制,首先复制到一个数组,然后复制到另一个数组。

\n

为了获得独立的数据副本,请再次复制数据,每次。我再次建议为了安全起见并使用Storable::dclone(或者当然是等效的库),即使使用简单的散列和数组也是如此。

\n

最后,请注意最后一个稍微险恶的情况——写入该数组会更改散列!这个(第二次复制的)数组可能远离哈希,在哈希甚至不知道的函数(在另一个模块中)中。这种错误可能是真正隐藏的错误的根源。

\n

现在,如果您澄清引用的复制位置,并更完整(简单)地描述您的问题,我们可以提供更具体的补救措施。

\n
\n

\xe2\x88\x97使用正确且经常使用的引用的一种重要方法是,每次都将引用的结构声明为词法变量

\n
for my $elem (@data) { \n    my %h = ...\n    ... \n    push @results, \\%h;  # all good\n}\n
Run Code Online (Sandbox Code Playgroud)\n

每次都会重新引入该词法%h,因此保留数组上其引用的数据,因为数组在循环之外持续存在,对于每个元素都是独立的。

\n

这样做的效率也更高,因为 in 中的数据%h不像 那样被复制{ %h },而只是“重新调整用途”,也就是说,从%h迭代结束时被破坏的词法到数组中的引用。

\n

如果要复制的结构自然位于循环之外,这当然可能并不总是合适。然后使用它的深层副本。

\n

同样的机制也适用于函数调用

\n
sub some_func {\n    ...\n    my %h = ...\n    ...\n    return \\%h;  # good\n}\n\nmy $hashref = some_func();\n
Run Code Online (Sandbox Code Playgroud)\n

同样,当函数返回时,词法%h超出范围,并且不再存在,但它携带的数据和对它的引用被保留,因为它被返回并分配,因此它的引用计数不为零。(至少返回给调用者,也就是说;它可能在子执行过程中被传递到其他地方,因此我们可能仍然会遇到多个使用相同引用的参与者的混乱。)所以有一个对数据的$hashref引用这是在子程序中创建的。

\n

回想一下,如果一个函数被传递了一个引用,当它被调用时或在其执行期间(通过调用其他返回引用的子函数),更改并返回它,那么我们再次在某些调用者中更改了数据,可能与这部分相去甚远的程序流程。

\n

当然,这种情况经常发生,因为数据池较大,而不能一直复制,但是需要小心并组织代码(例如,尽可能模块化),以最大程度地减少出现问题的可能性。错误。

\n

\xe2\x80\xa0这是对“指针”一词的宽松使用,用于表示引用的作用,但如果要引用 CI,会说它有点像“经过修饰的”C 指针

\n

\xe2\x80\xa1在不同的上下文中它可以是一个块

\n

  • 非常感谢您提供详细且内容丰富的答案。我尝试使用 `push \@ary_w_refs, \%h;`、`push @ary_w_refs, { %h };` 和 `push \@ary_w_refs, dclone \%h;` 就像您提到的那样,但它没有修复问题,然后我意识到我有这样一行: `\@row_buff = (\@row_buff) x $itercnt;` 在这一行中,数组中的引用被复制,所以我使用您的建议替换了这一行foreach 循环如下: (2认同)
  • `$size = $#row_buff; foreach (0 .. $itercnt -2){ foreach (0 .. $size){ 推@row_buff, dclone \%h; 现在,这里不是通过复制其中的哈希引用来复制数组,而是深度克隆数组的每个元素并将其推回以具有相同的复制效果,但不存在复制引用的问题。这有助于解决问题。 (2认同)