【问题标题】:What's a good recipe for overriding hashcode in Dart?在 Dart 中覆盖哈希码的好方法是什么?
【发布时间】:2014-01-01 21:03:18
【问题描述】:

我发现自己想为一个对象覆盖哈希码和 ==,我想知道是否有最佳实践来实现依赖于多个属性的哈希码,而且似乎有一些特定于 Dart 的注意事项.

最简单的答案是对所有属性的哈希值进行异或运算,这可能还不错。 Dart Up and Running 中还有一个示例https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html

  // Override hashCode using strategy from Effective Java, Chapter 11.
 int get hashCode {
   int result = 17;
   result = 37 * result + firstName.hashCode;
   result = 37 * result + lastName.hashCode;
   return result;
 }

但这似乎需要截断整数语义,并且在 Dart 中溢出 JS 整数范围似乎不利于散列。

我们也可以这样做,并在每次操作后截断为 32 位。

对于我的应用程序,集合的预期大小非常小,几乎任何事情都可以,但我很惊讶没有看到针对一般情况的标准配方。有没有人有这方面的经验或经验丰富?

【问题讨论】:

  • 我说“食谱”几乎是-1,但是由于您的问题质量很好,所以我没有-1。当然,您应该在标题中使用的术语是算法,甚至是“方法”(例如,一种好的方法或一种好的算法),而不是“一个好的食谱”。如果您认为“算法”一词听起来像是在炫耀,我可以理解。你可以说“方式”,但使用“食谱”这个词是蹩脚的
  • 也许可以从另一种语言中寻找具有任意精度整数的示例。我相信 Smalltalk 有这些。
  • Dart 的整数很有趣,因为它们在 VM 上是任意的,但如果你编译成 JavaScript,你就会得到 Javascript 的限制。所以你想要截断,有趣的问题是在哪里以及如何做到最好。大概是移位/乘法、异或和截断的某种组合。这也是 Smalltalk 倾向于做的事情。魔鬼在细节中。

标签: dart hashcode


【解决方案1】:

从 2.14 版开始,Dart 语言增加了对 Object.hash() 以及 Object.hashAll()Object.hashAllUnordered() 的支持

hash()文档:

为多个对象创建组合哈希码。

例子:

class SomeObject {
  final Object a, b, c;
  SomeObject(this.a, this.b, this.c);

  bool operator==(Object other) =>
      other is SomeObject && a == other.a && b == other.b && c == other.c;

  int get hashCode => Object.hash(a, b, c); // <----- here
}

文档中关于实施的说明:

此函数生成的哈希值不能保证在同一程序的不同运行期间或在同一程序的不同隔离区中运行的代码之间保持稳定。不同平台或平台库的不同版本之间使用的确切算法可能有所不同,并且可能取决于每次程序执行时更改的值。

【讨论】:

    【解决方案2】:

    为了最小化依赖关系,如果你已经依赖 Flutter,但不依赖于 quiver 之类的东西,dart:ui 库包含用于创建和组合哈希值的实用程序、hashValueshashList。如果组合列表值,必须小心确保相等运算符和哈希码匹配行为。如果哈希码计算它的哈希值很深,则使用深度相等,否则,使用浅相等。

    class Example {
        final String value1;
        final Object value2;
        final List<Object> deep;
        final List<Object> shallow;
    
        Example({this.value1, this.value2, this.deep, this.shallow});
    
        @override
        operator ==(o) =>
            o is Example &&
            o.value1 == value1 &&
            o.value2 == value2 &&
            listEquals(o.deep, deep) &&
            o.shallow == shallow;
    
        @override
        int get hashCode => hashValues(value1, value2, hashList(deep), shallow);
    }
    

    Flutter API docs for hashValues

    Flutter API docs for hashList

    【讨论】:

    • 太棒了,最小化依赖总是符合我的兴趣的!
    • 真的是你的回答救了我,非常感谢
    • 是否保证hashValues 将为两个不同的参数返回不同的哈希?还是将这种风险降到最低是一件好事?
    • 不能保证hashValues 会为两个不同的参数返回不同的has,没有哈希函数可以保证,因为键空间通常小于对象的值空间。但据我了解,该函数旨在最大限度地减少碰撞的机会,同时最大限度地降低计算复杂度。
    【解决方案3】:

    quiver package 提供了帮助函数hash2hash3 等,它们简化了实现hashCode 的任务,并确保它在 Dart VM 下正常工作编译成 JavaScript 时。

    import 'package:quiver/core.dart';
    
    class Person {
      String name;
      int age;
    
      Person(this.name, this.age);
    
      bool operator ==(o) => o is Person && name == o.name && age == o.age;
      int get hashCode => hash2(name.hashCode, age.hashCode);
    }
    

    另请参阅this post 以获得稍长的讨论。

    【讨论】:

    • 谢谢。正是我想要的。
    • 记得添加 quiver 作为依赖项。在您的 pubspec.yaml 中:quiver: '&gt;=0.18.0&lt;0.19.0'.
    • hash2(name, age) 不够吗?
    • hash2()添加import 'package:quiver/core.dart';
    【解决方案4】:

    equatable 包可以提供帮助

    import 'package:equatable/equatable.dart';
    
    class Person extends Equatable {
      final String name;
      final int age;
    
      Person(this.name, this.age);
    
      @override
      List<Object> get props => [name, age];
    }
    

    现在 Person 将使用来自 Equatable 的 ==hashCode,这将采用您提供的 props 列表

    【讨论】:

    • 不幸的是,当您使用提供者 StateManagement 时,这不适用于具有 ChangeNotifier 扩展的模型
    • @NiklasLehnfeld 如果你需要扩展另一个类,你可以使用 mixin。例如。 class MyModel extends ChangeNotifier with EquatableMixin
    • 这仅适用于不可变对象,因此所有成员变量都必须是最终的。如果您的类对象随时间变化,则不是那么有用。
    • @NiklasLehnfeld 你不应该从 ChangeNotifier 扩展模型。你做错了什么。模型应该是不可变的。如果您想要响应式字段,请考虑 MobX 或 Get。
    【解决方案5】:

    我推荐“equatable”插件

    https://pub.dev/packages/equatable

    例子:

    原始模式:

    class Person {
      final String name;
    
      const Person(this.name);
    
      @override
      bool operator ==(Object other) =>
        identical(this, other) ||
        other is Person &&
        runtimeType == other.runtimeType &&
        name == other.name;
    
      @override
      int get hashCode => name.hashCode;
    }
    

    与等价:

    import 'package:equatable/equatable.dart';
    
    class Person extends Equatable {
      final String name;
    
      Person(this.name);
    
      @override
      List<Object> get props => [name];
    }
    

    【讨论】:

      【解决方案6】:

      由于 Dart 与 Java 非常相似,您肯定可以在适用于 Dart 的 Java 的 hashCodes 上找到很好的参考。

      我在谷歌上搜索了一下Wikipedia page on Java's Object.hashCode()。有一个简单对象的哈希码的非常基本的示例。一种流行的方法是执行与素数(不同的)相乘,并为对象的每个属性添加一些值。

      This question f.e.解释了为什么选择数字 31 用于 String.hashCode() 方法的乘法。

      使用 Google 可以轻松找到更详细的哈希码实现示例。

      【讨论】:

      • 我不知道 Dart 和 Java 这么相似。特别是它对整数类型的处理不同,Java 的通用哈希如果应用于大量事物,会溢出 Javascript 整数范围,这很糟糕。
      • 好吧,Java 整数也会溢出。您可能希望使用 dart:typed_data 包中的 int32 或 int64 以使您的哈希码保持一致。
      • Java 整数因环绕而溢出。 Javascript 整数(以及编译为 Javascript 时的 Dart 整数)通过转换为双精度值而溢出,这对于散列来说更糟糕。使用 int32 数据类型会有所帮助,但 dart:typed_data 没有单一的 int 类型,它只有集合。
      猜你喜欢
      • 1970-01-01
      • 2015-06-14
      • 2014-02-17
      • 1970-01-01
      • 1970-01-01
      • 2013-08-02
      • 1970-01-01
      • 1970-01-01
      • 2018-09-28
      相关资源
      最近更新 更多