【问题标题】:Set/hash with custom hashing function in Perl 6在 Perl 6 中使用自定义散列函数设置/散列
【发布时间】:2017-12-10 04:56:56
【问题描述】:

我的问题与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 进行值类型散列(如果 Pair 不包含任何容器),但我不知道如何使其适用于任何其他复杂类型。

一方面,我明白为什么这不是最安全的操作 - 很容易定义在插入后哈希码可以更改的对象。但这并没有阻止 .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 没有参数化。)

【问题讨论】:

  • 请注意,构造函数不能简单地重载,因为当前的设计是将所有参数都转换为集合元素,所以:hasher(hash-logic)会被吞入数据结构中。
  • FWIW,Set.new 只接受位置,目前忽略命名参数:dd Set.new(:foo) # Set.new()
  • 就我目前所见,允许自定义哈希函数将是一项相当多的工作,并且在一般情况下会对性能产生负面影响。此外,我不完全确定 .classify (docs.perl6.org/routine/classify) 是否具有上述映射器,这不是您真正想要的。可以吗?
  • 感谢@ElizabethMattijsen 的更正。我有时会忘记传递一对和命名参数 [这是一对] 之间的区别。
  • 请注意.classify 也有一个:into' 命名参数(显然似乎没有记录),它允许my %h = 97 =&gt; ["a"]; &lt;b c d&gt;.classify( *.ord, :into(%h) ); dd %h # Hash %h = {"100" =&gt; $["d"], "97" =&gt; $["a"], "98" =&gt; $["b"], "99" =&gt; $["c"]}。这应该使使用.classify 作为Set 的一种类型更容易。

标签: overloading hashset raku


【解决方案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,它们不匹配,因为它们是不同的实例,会随着时间而改变。

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

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

【讨论】:

  • 谢谢。但是,我敦促您重新考虑这很奇怪或有风险的想法。我承认 STL 不是最好的 API。但是 C# 确实是一门非常可爱的语言,它允许自定义散列。将对象视为值类型确实很有必要。在您使用姓名而不是员工的情况下,Name.new(name =&gt; 'Bob') 在语义上确实与Name.new(name =&gt; 'Bob') 相同,但如果其中一个值发生变化,它们可能会变得不同。也许这个概念与 Perl 6 的映射很差。我同意重新定义 WHICH 在大型程序中并不理想。
  • @piojo 在 C# 中可能没问题,但在 Perl 6 中它是有风险的。我是说这两个 Employee 对象具有相同的信息,但它们可能指的是两个不同的人。另外,如果您将其中一个 Bob 更改为 Robert,该怎么办?现在,如果您将 .WHICH 更改为基于名称,您将无法再使用他的对象获取正确的数据。更改 .WHICH 会对 Perl 6 的工作方式产生巨大影响,而 C# 中的特性影响很小。如果您要为员工创建一个.WHICH,我会根据员工识别号创建它,该识别号不应更改或发生数据冲突。
猜你喜欢
  • 2021-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-03
  • 2014-05-19
  • 1970-01-01
  • 2013-12-07
  • 2013-09-10
相关资源
最近更新 更多