在Perl 6中使用自定义散列函数设置/ hash

pio*_*ojo 5 overloading hashset perl6

我的问题与集合操作中的用户定义函数有关,但我认为我可以切入问题的核心:

如何选择特定的散列函数?例如,如果我想进行基于值的匹配而不是引用匹配,并且我想查看某个元组是否存在(或者只是删除它):

my %data := SetHash.new: (1, 2), (3, 4);
%data{$(1, 2)}:delete; # False
Run Code Online (Sandbox Code Playgroud)

在C++或C#中,我可以为构造函数提供自定义哈希/比较函数.在C#中,如果我的数据类型是a struct(值类型而不是引用类型),则会自动按值进行散列.Perl 6在某种程度上进行了值类型哈希Pair(如果Pair不包含任何容器),但我不知道如何使它适用于任何其他复杂类型.

一方面,我明白为什么这不是最安全的操作 - 很容易定义哈希代码插入后可以更改的对象.但这并没有阻止.NET和C++ STL允许自定义散列.

一种可能的API使用(与由激发了链式散列逻辑,最初来自升压)将是:

class MyHasher does Hasher of Array[Int] {
  method get-hash-value(Int @array) {
    reduce
      -> $a, $b {$a +^ ($b + 0x9e3779b97f4a7c16 + ($a +< 6) + ($a +> 2))},
      0,
      |@array;
  }
  method equals(Int @a, Int @b) { @a eqv @b; }
}

my %data := SetHash.new(
  my Int @=[1, 2], my Int @=[3, 4],
  :hasher(MyHasher.new)
);
say %data{$[1, 2]}; # should be True
Run Code Online (Sandbox Code Playgroud)

这将是另一个角色,它应该由Perl 6的核心库提供,如果它还不存在的话:

role Hasher[::T=Any] { method equals(T $a, T $b --> Bool) { ... }; method get-hash-value(T $obj) { ... } }
Run Code Online (Sandbox Code Playgroud)

解决方案:目前,最合理的解决方案是覆盖类的.WHICH方法,该方法用作哈希值并用于相等性测试.我举了一个哈希键类的例子,它在这里模拟了一个值类型.它几乎与每个哈希对象的自定义哈希函数一样通用,因为在创建哈希时可以声明键类型.(Set由于Set未参数化,因此无法完成此操作.)

Bra*_*ert 1

\n\n

哈希的工作方式是使用键来存储值,并使用完全相同的键来检索该值。

\n\n

对于 Str 和 Int 等值类型,您可以拥有多个实例,这些实例的作用就好像它们是完全相同的值一样。因此,即使它们不是同一实例,42也要40\xc2\xa0+\xc2\xa02表现得好像它们是完全相同的实例。

\n\n

所以这有效:

\n\n
my %h{Any}; # defaults to Str keys\n\n%h{\'abc\'} = 42;\n\nmy ($a,$b,$c) = \'a\', \'b\', \'c\';\n\nsay %h{"$a $b $c"}; # 42\n\n%h{42} = \'The answer\';\n\nsay %h{"42"}; # (Any)\nsay %h{42}; # The answer\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

确实没有一种工具可以使多个不同的值假装为哈希值的相同值。

\n\n
\'abc\' === \'cba\'; # False\n\n\'abc\'.WHICH eq \'cba\'.WHICH; # basically how the above works\n
Run Code Online (Sandbox Code Playgroud)\n\n

我认为你所要求的是一个不应该添加的功能。

\n\n

有一种WHICH方法,只能用于使整个语言中各处的两个值相同。

\n\n
say 42.WHICH.perl;       # ValueObjAt.new("Int|42")\nsay (40 + 2).WHICH.perl; # ValueObjAt.new("Int|42")\n42 === (40 + 2);         # True\n\nsay Hash.new.WHICH.perl; # ObjAt.new("Hash|94014087733456")\nsay Hash.new.WHICH.perl; # ObjAt.new("Hash|94014087735232")\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,Hash.new它们不匹配,因为它们是不同的实例,可能会随着时间的推移而改变。

\n\n

举个例子,这是一件好事。假设您有两名名为“Bob”的员工。

\n\n
my $a = Employee.new( name => \'Bob\' );\nmy $b = Employee.new( name => \'Bob\' );\n\nmy %salary{Employee};\n\n%salary{$a} = 1200; # arbitrary number for an example\n%salary{$b} = 2000;\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,通过覆盖该WHICH方法,您最终可能会意外地加薪Bob\xc2\xa0$a

\n\n

.WHICH基本上,除非你确切地知道自己在做什么,并且有充分的理由,否则乱搞可能不是一个好主意。

\n\n
\n\n

所以你不能/不应该这样做。至少不是您尝试的方式。

\n\n

相反,创建一个新的关联类,它按照您想要的方式工作。

\n\n
role Custom-Str-Hasher {\n  method hashed ( --> Str:D ){\xe2\x80\xa6}\n}\n\nclass Custom-SetHash is SetHash {\n  multi method AT-KEY ( Custom-Str-Hasher:D $key ) is rw {\n    self.AT-KEY( $key.hashed() ); # call base class\'s method\n  }\n}\n\n\nclass Foo does Custom-Str-Hasher {\n  has Str:D $.Str is required;\n\n  # required by the Custom-Str-Hasher role\n  method hashed ( --> Str:D ){\n    $!Str.comb(/\\w/).unique.sort.join;\n    # \'b cb a\' \xe2\x86\x92 \'abc\' and \'aaababcccba\' \xe2\x86\x92 \'abc\'\n  }\n}\n\nmy $a = Foo.new(:Str(\'b cb a\'));\nmy $b = Foo.new(:Str(\'aaababcccba\'));\n\nmy %h is Custom-SetHash; # use a different class than the default\n\n%h{$a} = True;\nsay %h{$b}; # True;\n\nput $a; # b cb a\nput $b; # aaababcccba\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,上面只是一个简单的示例,对于真实的示例,我会更改很多内容。其一,由于我实现该方法的方式,%h{\'abc\'}也会返回。它还缺少一些像和 之类的方法。TrueAT-KEYASSIGN-KEYDELETE-KEY

\n