Line data Source code
1 : // Copyright (c) 2017, 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 : import 'dart:collection'; 6 : 7 : import 'combined_iterable.dart'; 8 : 9 : /// Returns a new map that represents maps flattened into a single map. 10 : /// 11 : /// All methods and accessors treat the new map as-if it were a single 12 : /// concatenated map, but the underlying implementation is based on lazily 13 : /// accessing individual map instances. In the occasion where a key occurs in 14 : /// multiple maps the first value is returned. 15 : /// 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. 22 : class CombinedMapView<K, V> extends UnmodifiableMapBase<K, V> { 23 : final Iterable<Map<K, V>> _maps; 24 : 25 : /// Create a new combined view of multiple maps. 26 : /// 27 : /// The iterable is accessed lazily so it should be collection type like 28 : /// [List] or [Set] rather than a lazy iterable produced by `map()` et al. 29 0 : CombinedMapView(this._maps); 30 : 31 0 : @override 32 : V? operator [](Object? key) { 33 0 : for (var map in _maps) { 34 : // Avoid two hash lookups on a positive hit. 35 0 : var value = map[key]; 36 0 : if (value != null || map.containsKey(value)) { 37 : return value; 38 : } 39 : } 40 : return null; 41 : } 42 : 43 : /// The keys of [this]. 44 : /// 45 : /// The returned iterable has efficient `contains` operations, assuming the 46 : /// iterables returned by the wrapped maps have efficient `contains` operations 47 : /// for their `keys` iterables. 48 : /// 49 : /// The `length` must do deduplication and thus is not optimized. 50 : /// 51 : /// The order of iteration is defined by the individual `Map` implementations, 52 : /// but must be consistent between changes to the maps. 53 : /// 54 : /// Unlike most [Map] implementations, modifying an individual map while 55 : /// iterating the keys will _sometimes_ throw. This behavior may change in 56 : /// the future. 57 0 : @override 58 0 : Iterable<K> get keys => _DeduplicatingIterableView( 59 0 : CombinedIterableView(_maps.map((m) => m.keys))); 60 : } 61 : 62 : /// A view of an iterable that skips any duplicate entries. 63 : class _DeduplicatingIterableView<T> extends IterableBase<T> { 64 : final Iterable<T> _iterable; 65 : 66 0 : const _DeduplicatingIterableView(this._iterable); 67 : 68 0 : @override 69 0 : Iterator<T> get iterator => _DeduplicatingIterator(_iterable.iterator); 70 : 71 : // Special cased contains/isEmpty since many iterables have an efficient 72 : // implementation instead of running through the entire iterator. 73 : // 74 : // Note: We do not do this for `length` because we have to remove the 75 : // duplicates. 76 : 77 0 : @override 78 0 : bool contains(Object? element) => _iterable.contains(element); 79 : 80 0 : @override 81 0 : bool get isEmpty => _iterable.isEmpty; 82 : } 83 : 84 : /// An iterator that wraps another iterator and skips duplicate values. 85 : class _DeduplicatingIterator<T> implements Iterator<T> { 86 : final Iterator<T> _iterator; 87 : 88 : final _emitted = HashSet<T>(); 89 : 90 0 : _DeduplicatingIterator(this._iterator); 91 : 92 0 : @override 93 0 : T get current => _iterator.current; 94 : 95 0 : @override 96 : bool moveNext() { 97 0 : while (_iterator.moveNext()) { 98 0 : if (_emitted.add(current)) { 99 : return true; 100 : } 101 : } 102 : return false; 103 : } 104 : }