I have a collection as follows
Map<String, Set<Long>> myMap = new HashMap<>();
I want to find out if any entry in this map has set which is contained in another entry of same map.
For example, lets say map has the following 5 entries
a - {1, 2, 3}
b - {4, 5}
c - {1}
d - {2, 3}
e - {5}
f - {6}
So, it has the following overlapping entries as set maybe
a - {1, 2, 3} and c - {1}
b - {4, 5} and e - {5}
a - {1, 2, 3} and d - {2, 3}
Or just list of Set for keys like
a and c
b and e
a and d
I could iterate each of the keyset and then use disjoint or anyMatch for each set, but I was wondering if there is an optimized way (Java 8, 9, 10, 11).
Compare the solution as nested loops or stream.
Edit: Reduced the code to what is relevant
import java.util.*;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
class Main {
public static void main(String[] args) {
Map<String, Set<Long>> myMap = new HashMap<>();
myMap.put("a", Set.of( 1l, 2l, 3l ));
myMap.put("b", Set.of( 4l, 5l ));
myMap.put("c", Set.of( 1l ));
myMap.put("d", Set.of( 2l, 3l ));
myMap.put("e", Set.of( 5l ));
myMap.put("f", Set.of( 6l ));
Set<String> keys = myMap.keySet();
BiPredicate<String, String> condition = (a, b) -> !a.equals(b) &&
myMap.get(a).size() >= myMap.get(b).size() &&
myMap.get(a).containsAll(myMap.get(b));
// nested Loop
Set<Map.Entry<String, String>> nested = new HashSet<>();
for (String a : keys)
for (String b : keys)
if (condition.test(a, b)) nested.add(Map.entry(a, b));
System.out.println(nested);
// stream
Set<Map.Entry<String, String>> collect = keys.stream()
.flatMap(a -> keys.stream()
.filter(b -> condition.test(a, b))
.map(b -> Map.entry(a, b)))
.collect(Collectors.toSet());
System.out.println(collect);
}
}
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Map<String, Set<Long>> map = new HashMap<>();
map.put("a", Set.of(1l, 2l, 3l));
map.put("b", Set.of(4l, 5l));
map.put("c", Set.of(1l));
map.put("d", Set.of(2l, 3l));
map.put("e", Set.of(5l));
map.put("f", Set.of(6l));
Set<Map.Entry<String, String>> result = map.entrySet()
.stream().map(
(source) -> map.entrySet().stream()
.takeWhile((pair) -> pair.getValue() != source.getValue())
.filter((pair) -> pair.getValue().containsAll(source.getValue()))
.map((pair) -> Map.entry(pair.getKey(), source.getKey()))
).reduce(Stream.empty(), Stream::concat).collect(Collectors.toSet());
System.out.println(result);
}
First, the entire map is iterated over and converted into a Stream<Stream<Map.Entry<String, String>>. By reduce(Stream.empty(), Stream::concat).collect(Collectors.toSet()); this becomes the output type in which all streams are merged together. To get the individual Stream<Map.Entry<String, String>> for each entry in the source map, we again iterate over each entry in the source list, but this time takeWhile((pair) -> pair.getValue() != source.getValue()) ignores all values that have not already appeared in the first loop, thus preventing duplicate results like (a, b) and (b, a) and also those that contain the same value twice like (a, a). Now pair.getValue().containsAll(source.getValue()) can be used to check if the entry from the inner stream contains the entry from the outer stream and remove it from the stream if there is no match, this result then only has to be converted into a Stream<Map.Entry<String, String>>, which is achieved by map((pair) -> Map.entry(pair.getKey(), source.getKey())).
The output of this code is:
[a=c, a=d, b=e]