@@ -13,14 +13,16 @@ import 'combined_iterable.dart';
13
13
/// accessing individual map instances. In the occasion where a key occurs in
14
14
/// multiple maps the first value is returned.
15
15
///
16
- /// The resulting map has an index operator (`[]` ) and `length` property that
17
- /// are both `O(maps)` , rather than `O(1)` , and the map is unmodifiable - but
18
- /// underlying changes to these maps are still accessible from the resulting
19
- /// map.
16
+ /// The resulting map has an index operator (`[]` ) that is `O(maps)` , rather
17
+ /// than `O(1)` , and the map is unmodifiable, but underlying changes to these
18
+ /// maps are still accessible from the resulting map.
19
+ ///
20
+ /// The `length` getter is `O(M)` where M is the total number of entries in
21
+ /// all maps, since it has to remove duplicate entries.
20
22
class CombinedMapView <K , V > extends UnmodifiableMapBase <K , V > {
21
23
final Iterable <Map <K , V >> _maps;
22
24
23
- /// Create a new combined view into multiple maps.
25
+ /// Create a new combined view of multiple maps.
24
26
///
25
27
/// The iterable is accessed lazily so it should be collection type like
26
28
/// [List] or [Set] rather than a lazy iterable produced by `map()` et al.
@@ -39,14 +41,57 @@ class CombinedMapView<K, V> extends UnmodifiableMapBase<K, V> {
39
41
40
42
/// The keys of [this] .
41
43
///
42
- /// The returned iterable has efficient `length` and `contains` operations,
43
- /// based on [length] and [containsKey] of the individual maps.
44
+ /// The returned iterable has efficient `contains` operations, assuming the
45
+ /// iterables returned by the wrapped maps have efficient `contains` operations
46
+ /// for their `keys` iterables.
47
+ ///
48
+ /// The `length` must do deduplication and thus is not optimized.
44
49
///
45
50
/// The order of iteration is defined by the individual `Map` implementations,
46
51
/// but must be consistent between changes to the maps.
47
52
///
48
53
/// Unlike most [Map] implementations, modifying an individual map while
49
54
/// iterating the keys will _sometimes_ throw. This behavior may change in
50
55
/// the future.
51
- Iterable <K > get keys => CombinedIterableView <K >(_maps.map ((m) => m.keys));
56
+ Iterable <K > get keys => _DeduplicatingIterableView (
57
+ CombinedIterableView (_maps.map ((m) => m.keys)));
58
+ }
59
+
60
+ /// A view of an iterable that skips any duplicate entries.
61
+ class _DeduplicatingIterableView <T > extends IterableBase <T > {
62
+ final Iterable <T > _iterable;
63
+
64
+ const _DeduplicatingIterableView (this ._iterable);
65
+
66
+ Iterator <T > get iterator => _DeduplicatingIterator (_iterable.iterator);
67
+
68
+ // Special cased contains/isEmpty since many iterables have an efficient
69
+ // implementation instead of running through the entire iterator.
70
+ //
71
+ // Note: We do not do this for `length` because we have to remove the
72
+ // duplicates.
73
+
74
+ bool contains (Object element) => _iterable.contains (element);
75
+
76
+ bool get isEmpty => _iterable.isEmpty;
77
+ }
78
+
79
+ /// An iterator that wraps another iterator and skips duplicate values.
80
+ class _DeduplicatingIterator <T > implements Iterator <T > {
81
+ final Iterator <T > _iterator;
82
+
83
+ final _emitted = HashSet <T >();
84
+
85
+ _DeduplicatingIterator (this ._iterator);
86
+
87
+ T get current => _iterator.current;
88
+
89
+ bool moveNext () {
90
+ while (_iterator.moveNext ()) {
91
+ if (_emitted.add (current)) {
92
+ return true ;
93
+ }
94
+ }
95
+ return false ;
96
+ }
52
97
}
0 commit comments