Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(327)

Side by Side Diff: pkg/collection_helpers/lib/equality.dart

Issue 23567044: Add collection-helpers library. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed some comments. Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Defines equality relations on collections.
7 */
8 library dart.collection_helper.equality;
9
10 import "dart:collection";
11
12 const int _HASH_MASK = 0x7fffffff;
13
14 /**
15 * A generic equality relation on objects.
16 */
17 abstract class Equality<E> {
18 const factory Equality() = DefaultEquality;
19
20 /**
21 * Compare two elements for being equal.
22 *
23 * This should be a proper equality relation.
24 */
25 bool equals(E e1, E e2);
26
27 /**
28 * Get a hashcode of an element.
29 *
30 * The hashcode should be compatible with [equals], so that if
31 * `equals(a, b)` then `hash(a) == hashCodeOf(b)`.
Lasse Reichstein Nielsen 2013/09/27 09:22:25 hashCodeOf(b) -> hash(b)
32 */
33 int hash(E e);
34
35 /**
36 * Test whether an object is a valid argument to [equals] and [hash].
37 *
38 * Some implementations may be restricted to only work on specific types
39 * of objects.
40 */
41 bool isValidKey(Object o);
42 }
43
44 /**
45 * Equality of objects that compares only the natural equality of the objects.
46 *
47 * This equality uses the objects' own [Object.==] and [Object.hashCode] for
48 * the equality.
49 */
50 class DefaultEquality implements Equality<Object> {
51 const DefaultEquality();
52 bool equals(Object e1, Object e2) => e1 == e2;
53 int hash(Object e) => e.hashCode;
54 bool isValidKey(Object o) => true;
55 }
56
57 /**
58 * Equality of objects that compares only the identity of the objects.
59 *
60 * This equality uses the objects' own [Object.hashCode] for the equality.
61 * Any hashcode is compatible with identity, so there is no need for a
62 * custom hashcode computation.
63 */
64 class IdentityEquality implements Equality<Object> {
65 const IdentityEquality();
66 bool equals(Object e1, Object e2) => identical(e1, e2);
67 int hash(Object e) => e.hashCode; // TODO: use Object.identityHashCode.
Lasse Reichstein Nielsen 2013/09/27 09:22:25 Possible now, so do it.
68 bool isValidKey(Object o) => true;
69 }
70
71 /**
72 * Equality on iterables.
73 *
74 * Two iterables are equal if they have the same elements in the same order.
75 */
76 class IterableEquality<E> implements Equality<Iterable<E>> {
77 final Equality<E> _elementEquality;
78 const IterableEquality([Equality<E> elementEquality =
79 const DefaultEquality()])
80 : _elementEquality = elementEquality;
81
82 bool equals(Iterable<E> elements1, Iterable<E> elements2) {
83 if (identical(elements1, elements2)) return true;
84 if (elements1 == null || elements2 == null) return false;
85 Iterator it1 = elements1.iterator;
86 Iterator it2 = elements2.iterator;
87 while (true) {
88 bool next1 = it1.moveNext();
89 bool next2 = it2.moveNext();
90 if (next1 != next2) return false;
91 if (!next1) return true;
92 if (!_elementEquality.equals(it1.current, it2.current)) return false;
93 }
94 }
95
96 int hash(Iterable<E> elements) {
97 // Jenkins's one-at-a-time hash function.
98 int hash = 0;
99 for (E element in elements) {
100 int c = _elementEquality.hash(element);
101 hash = (hash + c) & _HASH_MASK;
102 hash = (hash + (hash << 10)) & _HASH_MASK;
103 hash ^= (hash >> 6);
104 }
105 hash = (hash + (hash << 3)) & _HASH_MASK;
106 hash ^= (hash >> 11);
107 hash = (hash + (hash << 15)) & _HASH_MASK;
108 return hash;
109 }
110
111 bool isValidKey(Object o) => o is Iterable<E>;
112 }
113
114 /**
115 * Equality on lists.
116 *
117 * Two lists are equal if they have the same length and their elements
118 * at each index are equal.
119 *
120 * This is effectively the same as [IterableEquality] except that it
121 * accesses elements by index instead of through iteration.
122 */
123 class ListEquality<E> implements Equality<List<E>> {
124 final Equality<E> _elementEquality;
125 const ListEquality([Equality<E> elementEquality = const DefaultEquality()])
126 : _elementEquality = elementEquality;
127
128 bool equals(List<E> e1, List<E> e2) {
129 if (identical(e1, e2)) return true;
130 if (e1 == null || e2 == null) return false;
131 int length = e1.length;
132 if (length != e2.length) return false;
133 for (int i = 0; i < length; i++) {
134 if (!_elementEquality.equals(e1[i], e2[i])) return false;
135 }
136 return true;
137 }
138
139 int hash(List<E> e) {
140 // Jenkins's one-at-a-time hash function.
141 int hash = 0;
142 for (int i = 0; i < e.length; i++) {
143 int c = _elementEquality.hash(e[i]);
144 hash = (hash + c) & _HASH_MASK;
145 hash = (hash + (hash << 10)) & _HASH_MASK;
146 hash ^= (hash >> 6);
147 }
148 hash = (hash + (hash << 3)) & _HASH_MASK;
149 hash ^= (hash >> 11);
150 hash = (hash + (hash << 15)) & _HASH_MASK;
151 return hash;
152 }
153
154 bool isValidKey(Object o) => o is List<E>;
155 }
156
157 /**
158 * Equality of the elements of two iterables without considering order.
159 *
160 * Two iterables are considered equal if they have the same number of elements,
161 * and the elements of one set can be paired with the elements
162 * of the other set, so that each pair are equal.
163 */
164 class UnorderedIterableEquality<E> implements Equality<Iterable<E>> {
165 final Equality<E> _elementEquality;
166
167 const UnorderedIterableEquality(
168 [Equality<E> elementEquality = const DefaultEquality()])
169 : _elementEquality = elementEquality;
170
171 bool equals(Iterable<E> e1, Iterable<E> e2) {
172 if (identical(e1, e2)) return true;
173 if (e1 == null || e2 == null) return false;
174 HashMap<E, int> counts = new HashMap(
175 equals: _elementEquality.equals,
176 hashCode: _elementEquality.hash,
177 isValidKey: _elementEquality.isValidKey);
178 int length = 0;
179 for (E e in e1) {
180 int count = counts[e];
181 if (count == null) count = 0;
182 counts[e] = count + 1;
183 length++;
184 }
185 for (E e in e2) {
186 int count = counts[e];
187 if (count == null || count == 0) return false;
188 counts[e] = count - 1;
189 length--;
190 }
191 return length == 0;
192 }
193
194 int hash(Iterable<E> e) {
195 int hash = 0;
196 for (E element in e) {
197 int c = _elementEquality.hash(element);
198 hash = (hash + c) & _HASH_MASK;
199 }
200 hash = (hash + (hash << 3)) & _HASH_MASK;
201 hash ^= (hash >> 11);
202 hash = (hash + (hash << 15)) & _HASH_MASK;
203 return hash;
204 }
205
206 bool isValidKey(Object o) => o is Iterable<E>;
207 }
208
209 /**
210 * Equality of sets.
211 *
212 * Two sets are considered equal if they have the same number of elements,
213 * and the elements of one set can be paired with the elements
214 * of the other set, so that each pair are equal.
215 *
216 * This equality behaves the same as [UnorderedIterableEquality] except that
217 * it expects sets instead of iterables as arguments.
218 */
219 class SetEquality<E> implements Equality<Set<E>> {
Lasse Reichstein Nielsen 2013/09/27 09:22:25 Just extend UnorderedIterableEquality and have the
220 final Equality<E> _elementEquality;
221 const SetEquality(
222 [Equality<E> elementEquality = const DefaultEquality()])
223 : _elementEquality = elementEquality;
224
225 bool equals(Set<E> e1, Set<E> e2) {
226 if (identical(e1, e2)) return true;
227 if (e1 == null || e2 == null) return false;
228 int length = e1.length;
229 if (length != e2.length) return false;
230 HashMap<E, int> counts = new HashMap(
231 equals: _elementEquality.equals,
232 hashCode: _elementEquality.hash,
233 isValidKey: _elementEquality.isValidKey);
234 for (E e in e1) {
235 int count = counts[e];
236 if (count == null) count = 0;
237 counts[e] = count + 1;
238 }
239 for (E e in e2) {
240 int count = counts[e];
241 if (count == null || count == 0) return false;
242 counts[e] = count - 1;
243 }
244 return true;
245 }
246
247 int hash(Set<E> e) {
248 int hash = 0;
249 for (E element in e) {
250 int c = _elementEquality.hash(element);
251 hash = (hash + c) & _HASH_MASK;
252 }
253 hash = (hash + (hash << 3)) & _HASH_MASK;
254 hash ^= (hash >> 11);
255 hash = (hash + (hash << 15)) & _HASH_MASK;
256 return hash;
257 }
258
259 bool isValidKey(Object o) => o is Set<E>;
260 }
261
262 /**
263 * Internal class used by [MapEquality].
264 *
265 * The class represents a map entry as a single object,
266 * using a combined hashCode and equality of the key and value.
267 */
268 class _MapEntry {
269 final MapEquality equality;
270 final key;
271 final value;
272 _MapEntry(this.equality, this.key, this.value);
273
274 int get hashCode =>
275 (3 * equality._keyEquality.hash(key) +
276 7 * equality._valueEquality.hash(value)) & _HASH_MASK;
277
278 bool operator==(Object other) {
279 if (other is! _MapEntry) return false;
280 _MapEntry otherEntry = other;
281 return equality._keyEquality.equals(key, otherEntry.key) &&
282 equality._valueEquality.equals(value, otherEntry.value);
283
284 }
285 }
286
287 /**
288 * Equality on maps.
289 *
290 * Two maps are equal if they have the same number of entries, and if the
291 * entries of the two maps are pairwise equal on both key and value.
292 */
293 class MapEquality<K, V> implements Equality<Map<K, V>> {
294 final Equality<K> _keyEquality;
295 final Equality<V> _valueEquality;
296 const MapEquality({ Equality<K> keys : const DefaultEquality(),
297 Equality<K> values : const DefaultEquality() })
298 : _keyEquality = keys, _valueEquality = values;
299
300 bool equals(Map<K, V> e1, Map<K, V> e2) {
301 if (identical(e1, e2)) return true;
302 if (e1 == null || e2 == null) return false;
303 int length = e1.length;
304 if (length != e2.length) return false;
305 Map<_MapEntry, int> counts = new HashMap();
306 for (K key in e1.keys) {
307 _MapEntry entry = new _MapEntry(this, key, e1[key]);
308 int count = counts[entry];
309 if (count == null) count = 0;
310 counts[entry] = count + 1;
311 }
312 for (K key in e2.keys) {
313 _MapEntry entry = new _MapEntry(this, key, e2[key]);
314 int count = counts[entry];
315 if (count == null || count == 0) return false;
316 counts[entry] = count - 1;
317 }
318 return true;
319 }
320
321 int hash(Map<K, V> map) {
322 int hash = 0;
323 for (K key in map.keys) {
324 int keyHash = _keyEquality.hash(key);
325 int valueHash = _valueEquality.hash(map[key]);
326 hash = (hash + 3 * keyHash + 7 * valueHash) & _HASH_MASK;
327 }
328 hash = (hash + (hash << 3)) & _HASH_MASK;
329 hash ^= (hash >> 11);
330 hash = (hash + (hash << 15)) & _HASH_MASK;
331 return hash;
332 }
333
334 bool isValidKey(Object o) => o is Map<K, V>;
335 }
336
337 /**
338 * Combines several equalities into a single equality.
339 *
340 * Tries each equality in order, using [Equality.isValidKey], and returns
341 * the result of the first equality that applies to the argument or arguments.
342 *
343 * For `equals`, the first equality that maches the first argument is used,
344 * and if the second argument of `equals` is not valid for that equality,
345 * it returns false.
346 *
347 * The equalities should generally work on disjoint types, otherwise it may
348 * give inconsistent results for `equals(e1, e2)` and `equals(e2, e1)` if an
349 * equality allows `e1` and not `e2`, but another allows both.
350 */
351 class MultiEquality<E> implements Equality<E> {
352 final Iterable<Equality<E>> _equalities;
353
354 const MultiEquality(Iterable<Equality<E>> equalities)
355 : _equalities = equalities;
356
357 bool equals(E e1, E e2) {
358 for (Equality<E> eq in _equalities) {
359 if (eq.isValidKey(e1)) return eq.isValidKey(e2) && eq.equals(e1, e2);
360 }
361 return false;
362 }
363
364 int hash(E e) {
365 for (Equality<E> eq in _equalities) {
366 if (eq.isValidKey(e)) return eq.hash(e);
367 }
368 return -1;
369 }
370
371 bool isValidKey(Object o) {
372 for (Equality<E> eq in _equalities) {
373 if (eq.isValidKey(o)) return true;
374 }
375 return false;
376 }
377 }
378
379 /**
380 * Deep equality on collections.
381 *
382 * Recognizes lists, sets, iterables and maps and compares their elements using
383 * deep equality as well.
384 *
385 * Non-iterable/map objects are compared using a configurable base equality.
386 *
387 * Works in one of two modes: ordered or unordered.
388 *
389 * In ordered mode, lists and iterables are required to have equal elements
390 * in the same order. In unordered mode, the order of elements in iterables
391 * and lists are not importan.
392 *
393 * A list is only equal to another list, likewise for sets and maps. All other
394 * iterables are compared as iterables only.
395 */
396 class DeepCollectionEquality implements Equality {
397 final Equality _base;
398 final bool _unordered;
399 const DeepCollectionEquality([Equality base = const DefaultEquality()])
400 : _base = base, _unordered = false;
401
402 /**
403 * Creates a deep equality on collections where the order of lists and
404 * iterables are not considered important. That is, lists and iterables are
405 * treated as unordered iterables.
406 */
407 const DeepCollectionEquality.unordered(
408 [Equality base = const DefaultEquality()])
409 : _base = base, _unordered = true;
410
411 bool equals(e1, e2) {
412 if (e1 is Set) {
413 if (e2 is! Set) return false;
414 return new SetEquality(this).equals(e1, e2);
415 }
416 if (e1 is Map) {
417 if (e2 is! Map) return false;
418 return new MapEquality(keys: this, values: this).equals(e1, e2);
419 }
420 if (!_unordered) {
421 if (e1 is List) {
422 if (e2 is! List) return false;
423 return new ListEquality(this).equals(e1, e2);
424 }
425 if (e1 is Iterable) {
426 if (e2 is! Iterable) return false;
427 return new IterableEquality(this).equals(e1, e2);
428 }
429 } else if (e1 is Iterable) {
430 if (e2 is! Iterable) return false;
431 if (e1 is List != e2 is List) return false;
432 return new UnorderedIterableEquality(this).equals(e1, e2);
433 }
434 return _base.equals(e1, e2);
435 }
436
437 int hash(Object o) {
438 if (o is Set) return new SetEquality(this).hash(o);
439 if (o is Map) return new MapEquality(keys: this, values: this).hash(o);
440 if (!_unordered) {
441 if (o is List) return new ListEquality(this).hash(o);
442 if (o is Iterable) return new IterableEquality(this).hash(o);
443 } else if (o is Iterable) {
444 return new UnorderedIterableEquality(this).hash(o);
445 }
446 return _base.hash(o);
447 }
448
449 bool isValidKey(Object o) => o is Iterable || o is Map || _base.isValidKey(o);
450 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698