I'm trying to create keys of id numbers and values of Sets, but I also need to write generic methods that iterate(requiring Iterable, thus why I'm attempting to use sets) over those Sets stuck in values. What is an efficient method for implementing an Iterator over some Generic values that are part of a Map object?
-
2Did you mean something like : Map
> myMap = new HashMap – Kartic Mar 04 '15 at 09:50>(); -
Use the Stream API? You can `map = stream.collect(groupingBy(..., toSet())` and then `map.values().stream().flatMap(Collection::stream)` – Christoffer Hammarström Mar 04 '15 at 09:51
-
Why is `HashSet` unsuitable for your needs? – meskobalazs Mar 04 '15 at 09:54
-
Yeah, I'm trying to do what Kartic has over there. Is that how I should set up my Generic variables? It makes sense, since Sets implement Iterable, so technically any Set
should work for traversal and access to its String values. I'm going to try that. Thank you. – Dave Liu Mar 04 '15 at 09:59
2 Answers
What your are describing, a Map<X, Set<Y>>
, is often refered to as a "Set based MultiMap".
A popular implementation of such a Map is Google Guava
Aksing such a Map its values()
will return a Collection that will contain all values of Y
(and not all Set<Y>
instances).
One can implement "by hand" such a Collection using basic Maps, but this proves surprisingly difficult to actualy implement right for all use cases, and preserve the Java Colletions Framework semantics. So using a library's implementation is usually the way to go.
Another option would be using the Stream API (Java8), with collectors such as groupBy
to build the Map instance, and other stream operations to "flatten" the map's values when you want to iterate over the values (see Flattening a collection for example).
Below, find a SetMultiMap code sample
public static void main(String[] args) {
Multimap<Integer, String> multiMap = HashMultimap.create();
multiMap.put(0, "key0_value0");
// Twice the same value for the same key, the Set<String> will "ignore" this
multiMap.put(0, "key0_value0");
multiMap.put(1, "key1_value0");
multiMap.put(1, "key1_value1");
System.out.println("Values for key 0");
System.out.println("----------------");
System.out.println(multiMap.get(0));
System.out.println("\r\nValues for key 1");
System.out.println("----------------");
System.out.println(multiMap.get(1));
System.out.println("\r\nAll values");
System.out.println("------------");
System.out.println(multiMap.values());
// Entries are all Integer/String associations
Collection<Entry<Integer, String>> entries = multiMap.entries();
System.out.println("\r\nNumber of entries : " + entries.size());
// We can build a standard Java Map out of the Multimap
Map<Integer, Collection<String>> realJavaMap = multiMap.asMap();
// The map's values are actually guaranteed to be Sets
System.out.println("Multimap as Map, values implement Set : " + Set.class.isAssignableFrom(realJavaMap.values().iterator().next().getClass()));
// The java Map is a live view of the multimap
realJavaMap.get(0).add("key0_value1"); // Actions on realJavaMap will update multimap
System.out.println("\r\nValues for key 0");
System.out.println("----------------");
System.out.println(multiMap.get(0));
}
This program outputs:
Values for key 0
----------------
[key0_value0]
Values for key 1
----------------
[key1_value1, key1_value0]
All values
------------
[key0_value0, key1_value1, key1_value0]
Number of entries : 3
Multimap as Map, value class is set : true
Values for key 0
----------------
[key0_value1, key0_value0]
-
Do I have to declare a generic type for hashmap or can I just start using its methods with a basic HashMap hm = new HasMap(); – Dave Liu Mar 04 '15 at 10:07
-
I've added a code sample to my answer to show how the Guava based solution works. Unfortunately, I do not have access to a Java 8 editor right now so I can't post a stream API based example right now (I could guess and produce a half working implement but it probably wouldn't really work). – GPI Mar 04 '15 at 10:47
This seems to work:
class MapOfSetsIterable<V> implements Iterable<V> {
private final Map<?, Set<V>> map;
public MapOfSetsIterable(Map<?, Set<V>> map) {
this.map = map;
}
@Override
public Iterator<V> iterator() {
return new MapOfSetsIterator();
}
private class MapOfSetsIterator implements Iterator<V> {
final Iterator<Set<V>> sets = map.values().iterator();
Iterator<V> i = sets.hasNext() ? sets.next().iterator() : Collections.EMPTY_SET.iterator();
V next = null;
@Override
public boolean hasNext() {
while (next == null && (sets.hasNext() || i.hasNext())) {
if (!i.hasNext() && sets.hasNext()) {
i = sets.next().iterator();
}
if (i.hasNext()) {
next = i.next();
}
}
return next != null;
}
@Override
public V next() {
if (next == null) {
if (!hasNext()) {
throw new NoSuchElementException();
}
}
V n = next;
next = null;
return n;
}
}
}
enum E {
A, B
};
public void test() {
Map<Integer, Set<E>> map = new HashMap<>();
map.put(1, EnumSet.of(E.A));
map.put(2, EnumSet.of(E.B));
map.put(3, EnumSet.of(E.A, E.B));
for (E e : new MapOfSetsIterable<>(map)) {
System.out.println(e);
}
}

- 64,482
- 16
- 119
- 213