// uses hash sets as inner sets unless subclassed // uses a hash map as the outer map by default sclass MultiSetMapis IMultiMap { Map> data = new HashMap>(); int size; // number of values *() {} *(bool useTreeMap) { if (useTreeMap) data = new TreeMap; } *(MultiSetMap map) { putAll(map); } *(Map> *data) {} bool put(A key, B value) { synchronized(data) { Set set = data.get(key); if (set == null) data.put(key, set = _makeEmptySet()); if (!set.add(value)) false; ret true with ++size; }} bool add(A key, B value) { ret put(key, value); } void addAll(A key, Collection values) { synchronized(data) { putAll(key, values); }} void addAllIfNotThere(A key, Collection values) { synchronized(data) { for (B value : values) setPut(key, value); }} void setPut(A key, B value) { synchronized(data) { if (!containsPair(key, value)) put(key, value); }} boolean containsPair aka contains(A key, B value) { synchronized(data) { ret get(key).contains(value); }} void putAll(A key, Collection values) { synchronized(data) { for (B value : values) put(key, value); }} void removeAll(A key, Collection values) { synchronized(data) { for (B value : values) remove(key, value); }} public Set get(A key) { synchronized(data) { Set set = data.get(key); return set == null ? Collections. emptySet() : set; }} L getAndClear(A key) { synchronized(data) { L l = cloneList(data.get(key)); remove(key); ret l; }} // return null if empty Set getOpt(A key) { synchronized(data) { ret data.get(key); }} // returns actual mutable live set // creates the set if not there Set getActual(A key) { synchronized(data) { Set set = data.get(key); if (set == null) data.put(key, set = _makeEmptySet()); ret set; }} // TODO: this looks unnecessary void clean(A key) { synchronized(data) { Set list = data.get(key); if (list != null && list.isEmpty()) data.remove(key); }} public Set keySet aka keys() { synchronized(data) { return data.keySet(); }} void remove(A key) { synchronized(data) { size -= l(data.get(key)); data.remove(key); }} void remove(A key, B value) { synchronized(data) { Set set = data.get(key); if (set != null) { if (set.remove(value)) { --size; if (set.isEmpty()) data.remove(key); } } }} void clear() { synchronized(data) { data.clear(); size = 0; }} boolean containsKey(A key) { synchronized(data) { return data.containsKey(key); }} B getFirst(A key) { synchronized(data) { return first(get(key)); }} void addAll(MultiSetMap map) { putAll(map); } void putAll(MultiSetMap map) { synchronized(data) { for (A key : map.keySet()) putAll(key, map.get(key)); }} void putAll(Map map) { synchronized(data) { if (map != null) for (Map.Entry e : map.entrySet()) put(e.getKey(), e.getValue()); }} public int keysSize aka keyCount() { synchronized(data) { ret l(data); }} // full size public int size() { synchronized(data) { ret size; }} // count values for key int getSize(A key) { ret l(data.get(key)); } int count(A key) { ret getSize(key); } // expensive operation Set reverseGet(B b) { synchronized(data) { new Set l; for (A key : data.keySet()) if (data.get(key).contains(b)) l.add(key); ret l; }} // expensive operation A keyForValue(B b) { synchronized(data) { for (A key : data.keySet()) if (data.get(key).contains(b)) ret key; null; }} Map> asMap() { synchronized(data) { ret cloneMap(data); }} public bool isEmpty() { synchronized(data) { ret data.isEmpty(); }} // override in subclasses Set _makeEmptySet() { ret new HashSet; } Collection> allLists() { synchronized(data) { ret new Set(data.values()); } } L allValues() { ret concatLists(values(data)); } LPair allEntries() { synchronized(data) { LPair l = emptyList(size); for (A a, Set set : data) for (B b : set) l.add(pair(a, b)); ret l; }} O mutex() { ret data; } toString { ret "mm" + str(data); } Pair firstEntry() { synchronized(data) { if (empty(data)) null; Map.Entry> entry = data.entrySet().iterator().next(); ret pair(entry.getKey(), first(entry.getValue())); }} A firstKey() { synchronized(data) { ret main firstKey(data); }} A lastKey() { synchronized(data) { ret (A) ((NavigableMap) data).lastKey(); }} A higherKey(O a) { synchronized(data) { ret (A) ((NavigableMap) data).higherKey(a); }} }