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

时间:2017-12-10 04:56:56

标签: overloading hashset perl6

我的问题与user defined function in set operations有关,但我认为我可以切入问题的核心:

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

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

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

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

可能的API用法(受this启发的链式哈希逻辑,最初来自Boost)将是:

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

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

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

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

1 个答案:

答案 0 :(得分:1)

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

对于诸如Str和Int的值类型,您可以具有多个实例,它们的作用就好像它们是完全相同的值。因此4240 + 2的行为就好像它们是完全相同的实例,即使它们不是相同的。

这可行:

my %h{Any}; # defaults to Str keys

%h{'abc'} = 42;

my ($a,$b,$c) = 'a', 'b', 'c';

say %h{"$a $b $c"}; # 42

%h{42} = 'The answer';

say %h{"42"}; # (Any)
say %h{42}; # The answer

实际上并没有一种工具可以使几个不同的值假装为仅用于哈希的相同值。

'abc' === 'cba'; # False

'abc'.WHICH eq 'cba'.WHICH; # basically how the above works

我认为您要的是一项不应添加的功能。

有一种WHICH方法,该方法只应使整个语言中的两个值相同。

say 42.WHICH.perl;       # ValueObjAt.new("Int|42")
say (40 + 2).WHICH.perl; # ValueObjAt.new("Int|42")
42 === (40 + 2);         # True

say Hash.new.WHICH.perl; # ObjAt.new("Hash|94014087733456")
say Hash.new.WHICH.perl; # ObjAt.new("Hash|94014087735232")

请注意,对于它们不匹配的Hash.new,因为它们是随时间变化的不同实例。

例如,这是一件好事。假设您有两名名为“鲍勃”的员工。

my $a = Employee.new( name => 'Bob' );
my $b = Employee.new( name => 'Bob' );

my %salary{Employee};

%salary{$a} = 1200; # arbitrary number for an example
%salary{$b} = 2000;

请注意,通过改写WHICH方法,您可能最终会意外地给Bob $a加薪。

基本上,最好不要弄混.WHICH,除非您确切地知道自己在做什么,并且有充分的理由。


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

而是创建一个新的关联类,该类以您想要的方式工作。

role Custom-Str-Hasher {
  method hashed ( --> Str:D ){…}
}

class Custom-SetHash is SetHash {
  multi method AT-KEY ( Custom-Str-Hasher:D $key ) is rw {
    self.AT-KEY( $key.hashed() ); # call base class's method
  }
}


class Foo does Custom-Str-Hasher {
  has Str:D $.Str is required;

  # required by the Custom-Str-Hasher role
  method hashed ( --> Str:D ){
    $!Str.comb(/\w/).unique.sort.join;
    # 'b cb a' → 'abc' and 'aaababcccba' → 'abc'
  }
}

my $a = Foo.new(:Str('b cb a'));
my $b = Foo.new(:Str('aaababcccba'));

my %h is Custom-SetHash; # use a different class than the default

%h{$a} = True;
say %h{$b}; # True;

put $a; # b cb a
put $b; # aaababcccba

请注意,以上只是一个简单的示例,对于实际示例,我需要更改很多内容。例如,由于我实现%h{'abc'}方法的原因,True也将返回AT-KEY。它还缺少像ASSIGN-KEYDELETE-KEY这样的方法。