我如何比较 dart 记录与深度平等?

cwtwac6a  于 2023-07-31  发布在  其他
关注(0)|答案(2)|浏览(91)

Dart 3引入了一种新的类型RecordRecord s通过每个字段与==运算符进行比较,这意味着如果Record具有ListMap作为其字段,则比较将是false,即使List s或Map s在结构上相同。

final record1 = ([1, 2],);
final record2 = ([1, 2],);
print(record1 == record2);
// Prints false

字符串
我如何将它们与深度平等进行比较?

5hcedyr0

5hcedyr01#

所谓“深度相等”,你的意思是传递遍历集合,这些集合在Dart中不是自然相等的,然后使用其他一些“ListEquality”函数来完成这些集合的相等。
没有简单的办法。原因是您不能抽象超过记录类型。如果你想在记录上定义一个相等,它在具有相同形状的记录的字段上使用==以外的东西,你需要为每个记录形状定义一个 *。
我可以给予你一对

import "package:collection/equality.dart";
class PairEquality<A, B> implements Equality<(A, B)> {
  final Equality<A> _first;
  final Equality<B> _second;
  const PairEquality({
    Equality<A> first = const DefaultEquality(), 
    Equality<B> second = const DefaultEquality()})
      : _first = first,
        _second = second;
  
  bool equals((A, B) pair1, (A, B) pair2) =>
    _first.equals(pair1.$1, pair2.$1) &&
    _second.equals(pair1.$2, pair2.$2);

  int hash((A, B) pair) => 
     Object.hash(_first.hash(pair.$1), _second.hash(pair.$2));

  bool isValidKey(Object? pair) => pair is (A, B);
}

字符串
即使这样也对您的示例没有帮助,在您有单例记录的情况下,您还需要一个类来处理它。
我假设你的配对不仅在顶部,因为这样你就可以这样做:

var eq = const DeepCollectionEquality();
var record1 = ...;
var record2 = ...;
var equals = eq.equals(record1.$1, record2.$1);


因此,您可能需要 * 重写 * DeepCollectionEquality来识别您知道如何比较的记录类型(它不是为了注入更多可识别的类型而编写的)。

@override
  bool equals(Object? e1, Object? e2) {
    if (e1 is Set) {
      return e2 is Set && SetEquality(this).equals(e1, e2);
    }
    if (e1 is Map) {
      return e2 is Map && MapEquality(keys: this, values: this).equals(e1, e2);
    }
    if (!_unordered) {
      if (e1 is List) {
        return e2 is List && ListEquality(this).equals(e1, e2);
      }
      if (e1 is Iterable) {
        return e2 is Iterable && IterableEquality(this).equals(e1, e2);
      }
    } else if (e1 is Iterable) {
      if (e1 is List != e2 is List) return false;
      return e2 is Iterable && UnorderedIterableEquality(this).equals(e1, e2);
    } else if (e1 is (Object?, Object?)) {        // new
      return e2 is (Object?, Object?) &&          // new
         PairEquality(this, this).equals(e1, e2); // new
    }  
    return _base.equals(e1, e2);
  }

  @override
  int hash(Object? o) {
    if (o is Set) return SetEquality(this).hash(o);
    if (o is Map) return MapEquality(keys: this, values: this).hash(o);
    if (!_unordered) {
      if (o is List) return ListEquality(this).hash(o);
      if (o is Iterable) return IterableEquality(this).hash(o);
    } else if (o is Iterable) {
      return UnorderedIterableEquality(this).hash(o);
    } else if (o is (Object?, Object?) {        // new
      return PairEquality(this, this).hash(o);  // new
    }
    return _base.hash(o);
  }


你需要为你需要识别的每一个记录形状添加新的案例。
(That建议也许DeepCollectionEquality应该用一些“识别类型并为其提供相等性”对象进行参数化。但现在,它不是)。
这种可配置的深度相等的可能实现可以是如下内容:

import "package:collection/collection.dart";

/// Builds a recursive equality.
///
/// Allows for easily creating equalities for structures, 
/// like collections or similar, that refer back to the deep equality
/// for element equality.
class DeepEquality implements Equality<Object?> {
  List<Equality<Object?>> _equalities = const [];
  /// Creates recursive equality.
  ///
  /// Each function in [factories] is called with this deep equality
  /// as argument, and should return an equality for a specific type.
  ///
  /// When comparing or hashing, each of these equalities are tried,
  /// in original iteration order, until one's [Equality.isValidKey] 
  /// returns true, then that equality is used on the objects.
  ///
  /// For [equals], both arguments must be valid.
  ///
  /// If no factory-created equality matches, the [base] equality
  /// is used instead. It *must* accept all remaining values.
  DeepEquality(Iterable<Equality<Object?> Function(Equality<Object?>)> factories,
      [Equality<Object?> base = const DefaultEquality()]) {
    _equalities = [for (var factory in factories) factory(this), base];
  }

  @override
  bool equals(Object? a, Object? b) {
    if (identical(a, b)) return true;
    for (var equality in _equalities) {
      if (equality.isValidKey(a) && equality.isValidKey(b)) {
        return equality.equals(a, b);
      }
    }
    return false;
  }

  @override
  int hash(Object? a) {
    for (var equality in _equalities) {
      if (equality.isValidKey(a)) {
        return equality.hash(a);
      }
    }
    return a.hashCode;
  }

  @override
  bool isValidKey(Object? o) => true;
}

/// Creates an [Equality] from equals and hash code functions.
class PredicateEquality<A> implements Equality<A> {
  final bool Function(A, A) _equals;
  final int Function(A) _hash;
  final bool Function(Object?)? _isValid;
  PredicateEquality({
    required bool Function(A, A) equals,
    required int Function(A) hash,
    bool Function(Object?)? isValidKey,
  })  : _equals = equals,
        _hash = hash,
        _isValid = isValidKey;

  @override
  bool equals(A a, A b) => _equals(a, b);
  @override
  int hash(A a) => _hash(a);
  @override
  bool isValidKey(Object? o) => _isValid?.call(o) ?? o is A;
}

// Usage:
void main() {
  var deepListEq = DeepEquality([
    ListEquality.new,
  ], const DefaultEquality());
  print(deepListEq.equals([1, [2, 3]], [1, [2, 3]]));

  var deepPairListEq = DeepEquality([
    ListEquality<Object?>.new,
    (eq) => PredicateEquality<(Object?, Object?)>(
          equals: ((Object?, Object?) pair1, (Object?, Object?) pair2) =>
              eq.equals(pair1.$1, pair2.$1) && eq.equals(pair1.$2, pair2.$2),
          hash: ((Object?, Object?) pair) =>
              Object.hash(eq.hash(pair.$1), eq.hash(pair.$2)),
        )
  ]);

  var v1 = ([1, (1, 2)], [(1, [2])]);
  var v2 = ([1, (1, 2)], [(1, [2])]);
  print(deepPairListEq.equals(v1, v2));
}

3mpgtkmj

3mpgtkmj2#

The spec说:
记录具有值相等性,这意味着如果两个记录具有相同的形状并且对应的字段相等,则它们相等。
关于记录是否具有原始相等性,甚至同一性,还有很多内容,但您必须阅读规范。我不打算在这里剪切和粘贴它。
我的解释是,简单地使用==就可以满足几乎所有需要“深度”相等的情况。

相关问题