如何在Perl 6中创建哈希数组?

3
如何将已经推入数组的哈希(hash)与“源”哈希分离?这里涉及到IT技术。
my %country;
my Hash @array;

%country{ 'country' } = 'France';
@array.push(%country);
%country{ 'country' } = 'Germany';
@array.push(%country);

.say for @array;

输出结果如下:
{country => Germany}
{country => Germany}

当然这不是我想要的。

3个回答

3
几乎所有编程语言都会遇到这个问题。你正在将相同的哈希值多次推入数组中。当你更改哈希值时,你同时更改了数组内部的两个引用。
如果你将不同的哈希值推入数组中,你就会看到期望的结果:
my %a = ( country => 'China' );
my %b = ( country => 'USA' );
my Hash @array;
@array.push(%a);
@array.push(%b);
say @array.perl;

您甚至可以在将其推入数组时复制哈希,而不是声明两个哈希。这也将解决此问题:

my %country;
my @array;
%country<country> = 'México';
@array.push(%country.list.hash);
%country<country> = 'Canada';
@array.push(%country.list.hash);
say @array.perl;

顺便提一下,复制哈希表的方法有很多种。关键是获取键/值对,然后将其转换回哈希表。使用哪个哈希构造函数以及哪种展平方法由您自己决定。(.kv, .list, .pairs, .flat都是Hash方法,以一种或另一种方式按顺序获取元素。Håkon所展示的方法更加隐式,仅通过语法获取元素,然后创建另一个哈希表。)


1
@EugeneBarsky 大多数单一值是不可变的(Int/Str/DateTime),因此当您更改它时,实际上是将一个新值放入而不是修改现有值。 - Brad Gilbert
1
@EugeneBarsky 是的,解决方案是不要共享您不希望代码的其他部分修改的对象。不修改另一部分代码“拥有”的对象也是良好的编程实践。此外,当函数从类返回数据时,可以返回一个不可变版本的数据。例如,不要返回数组,而是返回该数组的迭代器。(这样避免了额外的复制,但仍然返回数据,这不允许调用者进行任何意外的更改。) - piojo
1
@EugeneBarsky 问题在于你没有意识到你在不同的方式下进行了更改。my $a = 1; ++$a; # ($a = $a + 1)my $h = Hash.new('a' => 'b'); $h{'a'} = 'c'; # ( $h.STORE_AT_KEY('a','c') )是不同的。 - Brad Gilbert
1
@EugeneBarsky 我不会期望在Perl 6文档中找到这个(虽然也许有人在某个地方提到过)。这更像是CS 101的陷阱。好吧,可能不是第一门CS课程,因为它涉及数据结构内的可变数据结构,但这是一个通用的编程陷阱,超出了Perl 6教程或参考范围。 - piojo
1
@EugeneBarsky 我能想到的基本准则是:一个函数不应该修改它的参数,除非API明确指出这就是发生的事情(比如通过参数命名为“foo_out”或特定于语言的关键字)。如果某个逻辑通过调用getter获取类实例的成员,则在修改该对象时应格外小心,以防它仍在使用(永久数据而非临时数据)。总的来说,我建议考虑对象的共享所有权。每个数据片段应该在逻辑上有一个“所有者”,其他人不应该修改它。 - piojo
显示剩余5条评论

3

当你将哈希%country推入数组中时,你将推送对%country的引用。这样,每个数组元素都将引用相同的原始哈希%country。当您更改哈希值时,所有数组元素都将反映此更改(因为它们都引用相同的哈希)。如果您想每次推送时创建一个新的哈希,请尝试推送匿名哈希。例如:

%country{ 'country' } = 'France';
@array.push({%country});
%country{ 'country' } = 'Germany';
@array.push({%country});

通过这种方式,每次推送的是对 %country 的副本的引用(而不是对 %country 的引用)。

输出结果:

{country => France}
{country => Germany}

1
@EugeneBarsky 我认为标量是按值推送的,但哈希是按引用推送的。这就是区别所在 :) - Håkon Hægland
1
根据routine push的文档所述:“push不会尝试展平其参数列表。如果您将数组或列表作为要推送的内容传递,它将成为一个附加元素。” - Håkon Hægland
所以,如果我想确保将任何标量以上的内容推送到内部{}中,它会被展平吗? - Eugene Barsky
1
如果您有一个要推送的数组,那么您将使用匿名数组。由于方括号生成匿名数组,因此应该是@array.push( [@myarr] )。但请注意,由于您将@array声明为哈希数组,因此不允许将数组推送到此数组中。 - Håkon Hægland
1
是的,那是我现在能想到的 :) 但我同意这应该是统一的。目前不可能 .clone 哈希和数组。你可以在它们上调用 .clone,但据我所见,它实际上并不会克隆它们。也许你可以尝试在数组和哈希上打补丁 .clone,然后 .clone 可以作为通用方法使用? - Håkon Hægland
显示剩余5条评论

1
如果您只需要一个简单的键值对(而不是多部分哈希表),可以考虑使用Pairs?
my Pair @array;
@array.push( (:country<Germany>) );
@array.push( (country => "France") );
say @array;
say .kv for @array

谢谢你的建议!通常情况下,我必须使用一个带有许多键值对的哈希表。 - Eugene Barsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接