在Perl 6中,我能否使用数组作为哈希键?

10
在哈希文档中,对象键 部分似乎暗示着只要指定,可以使用任何类型作为哈希键,但当我试图使用数组作为键时遇到了麻烦:
> my %h{Array};
{}
> %h{[1,2]} = [3,4];
Type check failed in binding to parameter 'key'; expected Array but got Int (1)
in block <unit> at <unknown file> line 1

这个可以做到吗?


如果你将 %h{[1,2]} = [3,4]; 改为 %h{item [1,2]} = [3,4];,它会防止数组被扩展成切片,并将其保留为数组,以便可以将其用作键。 - callyalater
@callyalater:没错,但这并没有什么用 :-(。请看我的回答。 - Elizabeth Mattijsen
@raiph 对于我的用例,我可以保证输入将是整数,因此使用Set([1,2])~[1,2]都完全符合我的需求。.perl看起来很有趣,但我不确定在正常开发中实际上会经常使用它。 - Hunter McMillen
谢谢。我删除了早先的评论和答案,建议使用~,但是你可能知道,我将我的答案从~前缀演变为.perl后缀。这是因为,除了简洁性外,它至少与其他两种hack一样好,在许多情况下甚至更好,即常规字符串化(~)或Liz建议的.Set.perl区分[1,2][2,1],而.Set则不区分,并且区分<<'a a' 'b'>><<'a' 'a b'>>,而常规字符串化(带有~)则不区分。总之,就像你说的那样,如果您的数组/列表元素都是整数,则~将很好地工作。 - raiph
@raiph,我也认为重要的是要告诉后来寻求答案的人,答案是否定的,由于Elizabeth的回答所解释的原因,你不能直接使用数组作为哈希键。 - Hunter McMillen
2个回答

8
%h{1 => 0, 2 => 0} = [3,4]

这里的[1,2]%h{[1,2]} = [3,4]中被解释为切片。 因此它试图分配%h{1}%h{2}。 由于键必须是一个Array,因此类型检查不好。 这就是错误消息告诉你的内容。

如果将数组列出来,那么它就“起作用”了:

%h{1 => 0, 2 => 0} = [3,4]
my %h{Array};
%h{ $[1,2] } = [3,4];
say %h.perl;  # (my Any %{Array} = ([1, 2]) => $[3, 4])

然而,那可能并不能满足您的需求,因为:

say %h{ $[1,2] };  # (Any)

这是因为对象哈希将.WHICH方法的返回值用作底层数组中的键。
say [1,2].WHICH; say [1,2].WHICH;
# Array|140324137953800
# Array|140324137962312

请注意,这些看似相同的数组的.WHICH值是不同的。那是因为Array是可变的。就像List一样,所以这并不起作用。
那么你想要实现什么?如果数组中的值的顺序不重要,你可能可以使用Set作为键:
say [1,2].Set.WHICH; say [1,2].Set.WHICH
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2

请注意这两个 .WHICH 是相同的。因此,您可以将其写成以下方式:
my %h{Set};
dd %h{ (1,2).Set } = (3,4); # $(3, 4)
dd %h; # (my Any %{Set} = ((2,1).Set) => $(3, 4))

希望这能澄清事情。更多信息请参见:https://docs.raku.org/routine/WHICH

2
很棒的答案,但有些人可能会误解你所写的内容,认为不能使用数组。但问题只是字面量--my %h{Array}; my @array = 5,6; %h{ $@array } = 'foo'; say %h{ $@array }; # foo可以正常工作,甚至可以修改数组,它仍然可以工作:@array = 7,8; @array.append: 9,42; say %h{ $@array }; # foo。另外:%h { $@array } = $@array理论上应该可以将数组保留为键,并将数组的值作为相关值(未经测试,我需要去睡觉)。 - raiph
1
@raiph,虽然这似乎可以在插入时工作,但您还需要传递一个数组来_retrieve_值,这使得使用起来非常麻烦。我会尝试使用Set解决方案并报告结果。 - Hunter McMillen
1
@raiph:正如Hunter McMillen发现的那样,即使您不使用文字数组,您也必须使用相同的数组才能获取值。我认为在现实生活中这种情况并不经常发生。因此,我建议使用Set,如果数组中的值的顺序无关紧要,则只有这才有意义。 - Elizabeth Mattijsen

3
如果您真的只对对象哈希的使用感兴趣,可以参考Liz在这里的回答,特别是对类似早期问题的回答和评论。 答案的最终重点是一种简单的方法,将类似于[1,'abc',[3/4,Mu,["more",5e6],9.9],"It's {<sunny rainy>.pick} today"]Array用作常规字符串哈希键。
基本原则是使用.perl来近似一个不可变的"值类型"数组,直到有一个具有更强大值类型.WHICH的规范不可变Positional类型。

使用数组作为哈希键的简单方法

my %hash;
%hash{ [1,2,3].perl } = 'foo';
say %hash{ [1,2,3].perl }; # displays 'foo'

.perl 将其参数转换为一个 Perl 6 代码字符串,该字符串是该参数的字面值版本。string表示链接到“string”页面,literal表示链接到“literal”页面。

say [1,2,3].perl;    # displays '[1, 2, 3]'

请注意空格已经添加,但这并不重要。
这不是一个完美的解决方案。如果在键访问之间改变数组,显然会得到错误的结果。不太明显的是,您将获得与.perl中任何限制或错误对应的破损结果。
say [my %foo{Array},42].perl; # displays '[(my Any %{Array}), 42]'

1 希望这是我对你问题的最终回答。请参见我之前的第10个(!!)版本答案,以讨论使用前缀来实现更有限但类似效果的替代方案和/或尝试理解我与Liz在下面评论中的交流。


我认为这里的问题实际上是:(1,2,3)目前不是值类型。我认为字面量列表应该是值类型。而($a,$b,$c)将解除变量,以创建值类型。然后正常的对象哈希将做正确的事情。 - Elizabeth Mattijsen
Joiner常量与IterationEnd一样具有相同的漏洞。它是可见的,因此可能会被滥用。我更多地考虑一种词法Mu.new类型的方法,在这种情况下,对象的地址是“秘密”。虽然不确定如何解决这个问题 :-( </handwave> - Elizabeth Mattijsen
谢谢。我喜欢构建时间深度(递归)去除列表文字的想法,确保文字列表始终是冻结/不可变的。也可以在List.new上作为副词(:ro?)。如果然后标记列表,则可以知道它是不可变的,这应该显着改善编写明确不变代码的简单性和一大类编译器优化。 - raiph

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