Add `iterableEquals`, `listEquals`, etc. extension methods.

Fixes type issue with `UnorderedIterableEquality`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e99669f..9796f1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,8 +8,12 @@
 * Add a large number of extension methods on `Iterable` and `List` types,
   and on a few other types.
   These either provide easy access to the operations from `algorithms.dart`,
-  or provide convenience variants of existing `Iterable` and `List` methods
-  like `singleWhereOrNull` or `forEachIndexed`.
+  provide convenience variants of existing `Iterable` and `List` methods
+  like `singleWhereOrNull` or `forEachIndexed`, or add a way to compare
+  the elements of a collection for equality.
+
+* Add `NullableEquality` class for extending equality on a type to
+  the nullable variant of the type.
 
 ## 1.15.0-nullsafety.3
 
diff --git a/lib/src/equality.dart b/lib/src/equality.dart
index eba3841..b0e4c27 100644
--- a/lib/src/equality.dart
+++ b/lib/src/equality.dart
@@ -202,14 +202,14 @@
   bool isValidKey(Object? o) => o is List<E>;
 }
 
-abstract class _UnorderedEquality<E, T extends Iterable<E>?>
+abstract class _UnorderedEquality<E, T extends Iterable<E>>
     implements Equality<T> {
   final Equality<E> _elementEquality;
 
   const _UnorderedEquality(this._elementEquality);
 
   @override
-  bool equals(T elements1, T elements2) {
+  bool equals(T? elements1, T? elements2) {
     if (identical(elements1, elements2)) return true;
     if (elements1 == null || elements2 == null) return false;
     var counts = HashMap(
@@ -232,7 +232,7 @@
   }
 
   @override
-  int hash(T elements) {
+  int hash(T? elements) {
     if (elements == null) return null.hashCode;
     var hash = 0;
     for (E element in elements) {
@@ -251,7 +251,7 @@
 /// Two iterables are considered equal if they have the same number of elements,
 /// and the elements of one set can be paired with the elements
 /// of the other iterable, so that each pair are equal.
-class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>?> {
+class UnorderedIterableEquality<E> extends _UnorderedEquality<E, Iterable<E>> {
   const UnorderedIterableEquality(
       [Equality<E> elementEquality = const DefaultEquality<Never>()])
       : super(elementEquality);
@@ -272,7 +272,7 @@
 /// The [equals] and [hash] methods accepts `null` values,
 /// even if the [isValidKey] returns `false` for `null`.
 /// The [hash] of `null` is `null.hashCode`.
-class SetEquality<E> extends _UnorderedEquality<E, Set<E>?> {
+class SetEquality<E> extends _UnorderedEquality<E, Set<E>> {
   const SetEquality(
       [Equality<E> elementEquality = const DefaultEquality<Never>()])
       : super(elementEquality);
@@ -363,12 +363,13 @@
 
 /// Combines several equalities into a single equality.
 ///
-/// Tries each equality in order, using [Equality.isValidKey], and returns
-/// the result of the first equality that applies to the argument or arguments.
+/// Tries each equality in order, using [Equality.isValidKey],
+/// and returns the result of the first equality
+/// that applies to the argument or arguments.
 ///
 /// For `equals`, the first equality that matches the first argument is used,
 /// and if the second argument of `equals` is not valid for that equality,
-/// it returns false.
+/// this equality returns false.
 ///
 /// Because the equalities are tried in order, they should generally work on
 /// disjoint types. Otherwise the multi-equality may give inconsistent results
@@ -476,6 +477,50 @@
       o is Iterable || o is Map || _base.isValidKey(o);
 }
 
+/// Equality on a nullable type.
+///
+/// Considers `null` equal only to another `null`,
+/// and non-`null` values equal based on the value equality
+/// passed to the constructor.
+///
+/// The [hash] of `null` is `null.hashCode`.
+class NullableEquality<T> implements Equality<T?> {
+  final Equality<T>? _elementEquality;
+
+  /// Creates an equality on a nullable type.
+  ///
+  /// Non-`null` values are compared using [valueEquality],
+  /// and `null` values are only equal to `null`.
+  ///
+  /// If [valueEquality] is omitted, it defaults to
+  /// using `==` and `.hashCode` on the non-`null` values,
+  /// and `is T` for [isValidKey].
+  const NullableEquality([Equality<T>? valueEquality])
+      : _elementEquality = valueEquality;
+
+  @override
+  int hash(T? value) {
+    var equality = _elementEquality;
+    if (equality != null && value != null) {
+      return equality.hash(value);
+    }
+    return value.hashCode;
+  }
+
+  @override
+  bool equals(T? e1, T? e2) {
+    var equality = _elementEquality;
+    if (equality != null && e1 != null) {
+      return e2 != null && equality.equals(e1, e2);
+    }
+    return e1 == e2;
+  }
+
+  @override
+  bool isValidKey(Object? o) =>
+      o == null || (_elementEquality?.isValidKey(o) ?? (o is T));
+}
+
 /// String equality that's insensitive to differences in ASCII case.
 ///
 /// Non-ASCII characters are compared as-is, with no conversion.
diff --git a/lib/src/iterable_extensions.dart b/lib/src/iterable_extensions.dart
index 82217e9..d6cd8fe 100644
--- a/lib/src/iterable_extensions.dart
+++ b/lib/src/iterable_extensions.dart
@@ -7,6 +7,7 @@
 import 'package:collection/src/utils.dart';
 
 import 'algorithms.dart';
+import 'equality.dart';
 
 /// Extensions that apply to all iterables.
 ///
@@ -18,6 +19,38 @@
 /// iterables with specific element types include those of
 /// [IterableComparableExtension] and [IterableNullableExtension].
 extension IterableExtension<T> on Iterable<T> {
+  /// Whether this iterable has the same element order as [elements].
+  ///
+  /// Checks whether this iterable has the same elements as [elements],
+  /// in the same iteration order.
+  ///
+  /// Uses [equality] for determining if the values are the same.
+  /// If [equality] is omitted, the default `==` behavior is used.
+  bool iterableEquals(Iterable<T> elements, [Equality<T>? equality]) {
+    equality ??= const DefaultEquality();
+    var it1 = iterator;
+    var it2 = elements.iterator;
+    while (it1.moveNext()) {
+      if (!it2.moveNext() || !equality.equals(it1.current, it2.current)) {
+        return false;
+      }
+    }
+    return !it2.moveNext();
+  }
+
+  /// Whether this iterable has the same unordered elements as [elements].
+  ///
+  /// Checks whether this iterable has the same elements as [elements],
+  /// but not necessarily in the same iteration order.
+  ///
+  /// Uses [equality] for determining if the values are the same.
+  /// If [equality] is omitted, the default `==` behavior is used.
+  bool iterableUnorderedEquals(Iterable<T> elements, [Equality<T>? equality]) {
+    var equals = UnorderedIterableEquality<T>(
+        equality ?? const DefaultEquality<Never>());
+    return equals.equals(this, elements);
+  }
+
   /// Selects [count] elements at random from this iterable.
   ///
   /// The returned list contains [count] different elements of the iterable.
@@ -793,3 +826,66 @@
         return result;
       };
 }
+
+/// Extensions on sets.
+extension SetExtension<T> on Set<T> {
+  /// Whether this set has the same unordered elements as [elements].
+  ///
+  /// Checks whether this set has the same elements as [elements],
+  /// but not necessarily in the same iteration order.
+  ///
+  /// Uses [equality] for determining if the values are the same.
+  /// If [equality] is omitted, default to the equality used by
+  /// this set.
+  /// _(Notice: This differs from [SetEquality],
+  /// which is symmetric in the arguments)_.
+  bool setEquals(Set<T> elements, [Equality<T>? equality]) {
+    if (identical(this, elements)) return true;
+    if (length != elements.length) return false;
+    if (equality != null) {
+      return SetEquality<T>(equality).equals(this, elements);
+    }
+    var clone = toSet(); // Should have same equality as set itself.
+    for (var value in elements) {
+      if (!clone.remove(value)) return false;
+    }
+    return clone.isEmpty;
+  }
+}
+
+// Extensions on maps.
+extension MapExtension<K, V> on Map<K, V> {
+  /// Whether this map has the same unordered entries as [entries].
+  ///
+  /// Checks whether this set has the same entries as [entries],
+  /// but not necessarily in the same iteration order.
+  ///
+  /// Uses [keys] and [values] for determining whether keys and values
+  /// are the same between the maps.
+  /// If [keys] is omitted, key equality default to the key equality
+  /// used by this set.
+  /// _(Notice: This differs from [MapEquality],
+  /// which is symmetric in the arguments)_.
+  /// If [values] is omitted, value equality defaults to using `==`.
+  bool mapEquals(Map<K, V> entries, {Equality<K>? keys, Equality<V>? values}) {
+    if (identical(this, entries)) return true;
+    if (length != entries.length) return false;
+    if (keys != null) {
+      var equality = MapEquality<K, V>(
+          keys: keys, values: values ?? const DefaultEquality<Never>());
+      return equality.equals(this, entries);
+    }
+    // Use nullable equality because [] returns a nullable type.
+    var valueEquality = (values == null)
+        ? const DefaultEquality<Never>() as Equality<V?>
+        : NullableEquality<V>(values);
+    // Keys uses map's equality.
+    var keySet = this.keys.toSet(); // Uses same equality as the map's keys.
+    for (var otherKey in entries.keys) {
+      if (!keySet.remove(otherKey)) return false;
+      if (!valueEquality.equals(this[otherKey], entries[otherKey]))
+        return false;
+    }
+    return keySet.isEmpty;
+  }
+}
diff --git a/lib/src/list_extensions.dart b/lib/src/list_extensions.dart
index c1f4af0..8191760 100644
--- a/lib/src/list_extensions.dart
+++ b/lib/src/list_extensions.dart
@@ -13,6 +13,23 @@
 
 /// Various extensions on lists of arbitrary elements.
 extension ListExtensions<E> on List<E> {
+  /// Whether this list has the same elements as [elements].
+  ///
+  /// Checks whether this list has the same elements as [elements],
+  /// at the same positions.
+  ///
+  /// Uses [equality] for determining if the values are the same.
+  /// If [equality] is omitted, the default `==` behavior is used.
+  bool listEquals(List<E> elements, [Equality<E>? equality]) {
+    if (identical(this, elements)) return true;
+    if (length != elements.length) return false;
+    equality ??= const DefaultEquality<Never>();
+    for (var i = 0; i < length; i++) {
+      if (!equality.equals(this[i], elements[i])) return false;
+    }
+    return true;
+  }
+
   /// Returns the index of [element] in this sorted list.
   ///
   /// Uses binary search to find the location of [element].
diff --git a/test/equality_test.dart b/test/equality_test.dart
index b2109b6..3639543 100644
--- a/test/equality_test.dart
+++ b/test/equality_test.dart
@@ -77,10 +77,10 @@
   });
 
   test('SetEquality', () {
-    var set1 = HashSet.from(list1);
-    var set2 = LinkedHashSet.from(list3);
+    var set1 = HashSet.of(list1);
+    var set2 = LinkedHashSet.of(list3);
     expect(const SetEquality().equals(set1, set2), isTrue);
-    Equality setId = const SetEquality(IdentityEquality());
+    var setId = const SetEquality(IdentityEquality());
     expect(setId.equals(set1, set2), isFalse);
   });
 
@@ -202,10 +202,16 @@
   });
 
   test('Equality accepts null', () {
-    var ie = IterableEquality();
-    var le = ListEquality();
-    var se = SetEquality();
-    var me = MapEquality();
+    var ie = IterableEquality<int>();
+    var le = ListEquality<int>();
+    var se = SetEquality<int>();
+    var me = MapEquality<int, int>();
+
+    expect(ie, isA<Equality<Iterable<int>>>());
+    expect(le, isA<Equality<List<int>>>());
+    expect(se, isA<Equality<Set<int>>>());
+    expect(me, isA<Equality<Map<int, int>>>());
+
     expect(ie.equals(null, null), true);
     expect(ie.equals([], null), false);
     expect(ie.equals(null, []), false);
diff --git a/test/extensions_test.dart b/test/extensions_test.dart
index 5d36020..322c437 100644
--- a/test/extensions_test.dart
+++ b/test/extensions_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:collection';
 import 'dart:math' show pow, Random;
 
 import 'package:test/test.dart';
@@ -11,6 +12,97 @@
 void main() {
   group('Iterable', () {
     group('of any', () {
+      group('.iterableEquals', () {
+        test('default equality', () {
+          expect(mkIterable(10).iterableEquals(mkIterable(10)), true);
+          expect(mkIterable(10).iterableEquals(mkIterable(5)), false);
+          expect(mkIterable(5).iterableEquals(mkIterable(10)), false);
+          expect(mkIterable(0).iterableEquals(mkIterable(5)), false);
+          expect(mkIterable(5).iterableEquals(mkIterable(0)), false);
+
+          expect(mkList(10).iterableEquals(mkIterable(10)), true);
+          expect(mkIterable(10).iterableEquals(mkList(10)), true);
+          expect(mkSet(10).iterableEquals(mkIterable(10)), true);
+          expect(mkIterable(10).iterableEquals(mkSet(10)), true);
+          expect(mkSet(10).iterableEquals(mkList(10)), true);
+          expect(mkList(10).iterableEquals(mkSet(10)), true);
+
+          // Repeated elements.
+          expect([1, 1, 1].iterableEquals([1]), false);
+          expect([1].iterableEquals([1, 1, 1]), false);
+          expect([1, 1, 1].iterableEquals([1, 1, 1]), true);
+        });
+        test('custom equality', () {
+          var eq = CustomEquality(7);
+          expect(mkIterable(10).iterableEquals(mkIterable(10), eq), true);
+          expect(mkIterable(10).iterableEquals(mkIterable(5), eq), false);
+          expect(mkIterable(5).iterableEquals(mkIterable(10), eq), false);
+          expect(mkIterable(0).iterableEquals(mkIterable(5), eq), false);
+          expect(mkIterable(5).iterableEquals(mkIterable(0), eq), false);
+
+          expect(mkList(10).iterableEquals(mkIterable(10), eq), true);
+          expect(mkIterable(10).iterableEquals(mkList(10), eq), true);
+          expect(mkSet(10).iterableEquals(mkIterable(10), eq), true);
+          expect(mkIterable(10).iterableEquals(mkSet(10), eq), true);
+          expect(mkSet(10).iterableEquals(mkList(10), eq), true);
+          expect(mkList(10).iterableEquals(mkSet(10), eq), true);
+
+          // Repeated elements.
+          expect([1, 1, 1].iterableEquals([1], eq), false);
+          expect([1].iterableEquals([1, 1, 1], eq), false);
+          expect([1, 1, 1].iterableEquals([1, 1, 1], eq), true);
+
+          expect([0, 1, 2].iterableEquals([7, 8, 9], eq), true);
+        });
+      });
+
+      group('.iterableUnorderedEquals', () {
+        test('default equality', () {
+          expect(mkIterable(10).iterableUnorderedEquals(mkIterable(10)), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkIterable(10, 5)), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkIterable(5)), false);
+          expect(mkIterable(10, 5).iterableUnorderedEquals(mkIterable(5)), false);
+          expect(mkIterable(0).iterableUnorderedEquals(mkIterable(5)), false);
+          expect(mkIterable(5).iterableUnorderedEquals(mkIterable(0)), false);
+
+          expect(mkList(10).iterableUnorderedEquals(mkIterable(10)), true);
+          expect(mkList(10).iterableUnorderedEquals(mkIterable(10, 5)), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkList(10)), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkList(10, 5)), true);
+          expect(mkSet(10).iterableUnorderedEquals(mkIterable(10, 5)), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkSet(10, 5)), true);
+          expect(mkSet(10).iterableUnorderedEquals(mkList(10, 5)), true);
+          expect(mkList(10).iterableUnorderedEquals(mkSet(10, 5)), true);
+
+          // Repeated elements.
+          expect([1, 1, 1].iterableUnorderedEquals([1]), false);
+          expect([1].iterableUnorderedEquals([1, 1, 1]), false);
+          expect([1, 1, 1].iterableUnorderedEquals([1, 1, 1]), true);
+        });
+        test('custom equality', () {
+          var eq = CustomEquality(7);
+          expect(mkIterable(10).iterableUnorderedEquals(mkIterable(10), eq), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkIterable(10, 5), eq), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkIterable(5), eq), false);
+          expect(mkIterable(10, 5).iterableUnorderedEquals(mkIterable(5), eq), false);
+          expect(mkIterable(0).iterableUnorderedEquals(mkIterable(5), eq), false);
+          expect(mkIterable(5).iterableUnorderedEquals(mkIterable(0), eq), false);
+
+          expect(mkList(10).iterableUnorderedEquals(mkIterable(10, 5), eq), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkList(10, 5), eq), true);
+          expect(mkSet(10).iterableUnorderedEquals(mkIterable(10, 5), eq), true);
+          expect(mkIterable(10).iterableUnorderedEquals(mkSet(10, 5), eq), true);
+          expect(mkSet(10).iterableUnorderedEquals(mkList(10, 5), eq), true);
+          expect(mkList(10).iterableUnorderedEquals(mkSet(10, 5), eq), true);
+
+          // Repeated elements.
+          expect([1, 1, 1].iterableUnorderedEquals([1], eq), false);
+          expect([1].iterableUnorderedEquals([1, 1, 1], eq), false);
+          expect([1, 1, 1].iterableUnorderedEquals([1, 1, 1], eq), true);
+
+          expect([0, 1, 2].iterableUnorderedEquals([7, 9, 8], eq), true);
+        });
+      });
       group('.whereNot', () {
         test('empty', () {
           expect(iterable([]).whereNot(unreachable), isEmpty);
@@ -1081,6 +1173,30 @@
 
   group('List', () {
     group('of any', () {
+      group('.listEquals', () {
+        test('default equality', () {
+          expect(mkList(10).listEquals(mkList(10)), true);
+          expect(mkList(5).listEquals(mkList(10)), false);
+          expect(mkList(10).listEquals(mkList(5)), false);
+          expect(mkList(5).listEquals(mkList(0)), false);
+          expect(mkList(0).listEquals(mkList(5)), false);
+          expect(mkList(0).listEquals(mkList(0)), true);
+          expect([1, 1, 1].listEquals([1]), false);
+          expect([1].listEquals([1, 1, 1]), false);
+        });
+        test('custom equality', () {
+          var eq = CustomEquality(7);
+          expect(mkList(10).listEquals([for (var v in mkList(10)) v + 7], eq), true);
+          expect(mkList(5).listEquals(mkList(10), eq), false);
+          expect(mkList(10).listEquals(mkList(5), eq), false);
+          expect(mkList(5).listEquals(mkList(0), eq), false);
+          expect(mkList(0).listEquals(mkList(5), eq), false);
+          expect(mkList(0).listEquals(mkList(0), eq), true);
+
+          expect([1, 1, 1].listEquals([1], eq), false);
+          expect([1].listEquals([1, 1, 1], eq), false);
+        });
+      });
       group('.binarySearch', () {
         test('empty', () {
           expect(<int>[].binarySearch(1, unreachable), -1);
@@ -1663,6 +1779,104 @@
       });
     });
   });
+  group('Set', () {
+    group('.setEquals', () {
+      test('default equality', () {
+        expect(mkSet(5).setEquals(mkSet(5)), true);
+        expect(mkSet(5).setEquals(mkSet(0)), false);
+        expect(mkSet(0).setEquals(mkSet(5)), false);
+        expect(mkSet(0).setEquals(mkSet(0)), true);
+        expect(mkSet(5).setEquals(mkSet(5, 3)), true);
+
+        var o1 = CustomEqualityClass(1);
+        var o2 = CustomEqualityClass(2);
+        var o7 = CustomEqualityClass(7);
+
+        var idSet = HashSet<CustomEqualityClass>.identity();
+        idSet..add(o1)..add(o2)..add(o7);
+        expect(o1, equals(o7));
+        expect(idSet, hasLength(3));
+
+        expect(idSet.setEquals({o1, o2, o7}), false);
+        expect(idSet.setEquals(idSet), true);
+        expect(idSet.setEquals(idSet.toSet()), true);
+      });
+      test('custom equality', () {
+        var eq = CustomEquality(3);
+        expect(mkSet(5).setEquals(mkSet(5), eq), true);
+        expect(mkSet(5).setEquals(mkSet(0), eq), false);
+        expect(mkSet(0).setEquals(mkSet(5), eq), false);
+        expect(mkSet(5).setEquals(mkSet(5, 2), eq), true);
+
+        expect(mkSet(5).setEquals({3, 4, 5, 6, 7}, eq), true);
+
+        var o1 = CustomEqualityClass(1);
+        var o2 = CustomEqualityClass(2);
+        var o7 = CustomEqualityClass(7);
+
+        var idSet = HashSet<CustomEqualityClass>.identity();
+        idSet..add(o1)..add(o2)..add(o7);
+        expect(o1, equals(o7));
+        expect(idSet, hasLength(3));
+
+        expect(idSet.setEquals(idSet.toSet(), const DefaultEquality<Never>()),
+            true);
+      });
+    });
+  });
+
+  group('Map', () {
+    group('.mapEquals', () {
+      test('default equality', () {
+        expect(mkMap(10).mapEquals(mkMap(5)), false);
+        expect(mkMap(5).mapEquals(mkMap(10)), false);
+        expect(mkMap(5).mapEquals(mkMap(5)), true);
+        expect(mkMap(0).mapEquals(mkMap(5)), false);
+        expect(mkMap(0).mapEquals(mkMap(0)), true);
+
+        expect(mkMap(5).mapEquals(mkMap(5, 3)), true);
+
+        var o1 = CustomEqualityClass(1);
+        var o2 = CustomEqualityClass(2);
+        var o7 = CustomEqualityClass(7);
+
+        var idMap1 = HashMap<CustomEqualityClass, int>.identity();
+        var idMap2 = HashMap<CustomEqualityClass, int>.identity();
+        idMap1..[o1] = 1..[o2] = 2..[o7] = 3;
+        idMap2..addAll(idMap1);
+        expect(o1, equals(o7));
+        expect(idMap1, hasLength(3));
+        expect(idMap2, hasLength(3));
+
+        expect(idMap1.mapEquals(idMap2), true);
+      });
+      test('custom equality', () {
+        var eq = CustomEquality(3);
+        expect(mkMap(10).mapEquals(mkMap(5), keys: eq), false);
+        expect(mkMap(5).mapEquals(mkMap(10), keys: eq), false);
+        expect(mkMap(5).mapEquals(mkMap(5), keys: eq), true);
+        expect(mkMap(0).mapEquals(mkMap(5), keys: eq), false);
+        expect(mkMap(0).mapEquals(mkMap(0), keys: eq), true);
+
+        expect(mkMap(10).mapEquals(mkMap(5), values: eq), false);
+        expect(mkMap(5).mapEquals(mkMap(10), values: eq), false);
+        expect(mkMap(5).mapEquals(mkMap(5), values: eq), true);
+        expect(mkMap(0).mapEquals(mkMap(5), values: eq), false);
+        expect(mkMap(0).mapEquals(mkMap(0), values: eq), true);
+
+        expect({1: 3}.mapEquals({4: 3}), false);
+        expect({1: 3}.mapEquals({4: 3}, keys: eq), true);
+        expect({1: 3}.mapEquals({1: 6}), false);
+        expect({1: 3}.mapEquals({1: 6}, values: eq), true);
+        expect({1: 3}.mapEquals({4: 6}), false);
+        expect({1: 3}.mapEquals({4: 6}, keys: eq), false);
+        expect({1: 3}.mapEquals({4: 6}, values: eq), false);
+        expect({1: 3}.mapEquals({4: 6}, keys: eq, values: eq), true);
+
+        expect(mkMap(5).mapEquals(mkMap(5, 3), keys: eq, values : eq), true);
+      });
+    });
+  });
 }
 
 /// Creates a plain iterable not implementing any other class.
@@ -1704,3 +1918,46 @@
 
 /// Tests an integer for being odd.
 bool isOdd(int x) => x.isOdd;
+
+class CustomEquality implements Equality<int> {
+  final int _mod;
+
+  CustomEquality(int mod) : _mod = mod;
+
+  @override
+  bool equals(int e1, int e2) {
+    return e1 % _mod == e2 % _mod;
+  }
+
+  @override
+  int hash(int e) => (e % _mod).hashCode;
+
+  @override
+  bool isValidKey(Object? o) => o is int;
+}
+
+Iterable<int> mkIterable(int n, [int offset = 0]) sync* {
+  for (var i = 0; i < n; i++) {
+    yield (i + offset) % n;
+  }
+}
+
+List<int> mkList(int n, [int offset = 0]) =>
+    [for (var i = 0; i < n; i++) (i + offset) % n];
+Set<int> mkSet(int n, [int offset = 0]) =>
+    {for (var i = 0; i < n; i++) (i + offset) % n};
+Map<int, int> mkMap(int n, [int offset = 0]) => {
+      for (var i = 0; i < n; i++)
+        (i + offset) % n: (i + offset) % n
+    };
+
+class CustomEqualityClass {
+  static const int _mod = 6;
+  final int value;
+  CustomEqualityClass(this.value);
+  @override
+  int get hashCode => value % _mod;
+  bool operator ==(Object value) =>
+      value is CustomEqualityClass &&
+      (this.value - value.value) % _mod == 0;
+}