AKJ88
AKJ88

Reputation: 733

Java stream Convert list of maps to set

I have a data structure in the form of list of maps: List< Map<String, List<String>> >

I want to collect all the elements of lists (values of maps) in a single Set using java 8 features.

Example:

Input:  [ {"a" : ["b", "c", "d"], "b" : ["a", "b"]}, {"c" : ["a", "f"]} ]
Output: ["a", "b", "c", "d", "f"]

Thanks.

Upvotes: 9

Views: 17560

Answers (4)

Donald Raab
Donald Raab

Reputation: 6706

There are a lot of alternatives to this problem. Here are a few including two of the other answers provided using Eclipse Collections containers.

MutableList<MutableMap<String, MutableList<String>>> list =
        Lists.mutable.with(
                Maps.mutable.with(
                        "a", Lists.mutable.with("b", "c", "d"),
                        "b", Lists.mutable.with("a", "b")),
                Maps.mutable.with(
                        "c", Lists.mutable.with("a", "f")));

ImmutableSet<String> expected = Sets.immutable.with("a", "b", "c", "d", "f");

// Accepted Streams solution
Set<String> stringsStream = list.stream()
        .map(Map::values)
        .flatMap(Collection::stream)
        .flatMap(Collection::stream)
        .collect(Collectors.toSet());
Assert.assertEquals(expected, stringsStream);

// Lazy Eclipse Collections API solution
MutableSet<String> stringsLazy = list.asLazy()
        .flatCollect(MutableMap::values)
        .flatCollect(e -> e)
        .toSet();
Assert.assertEquals(expected, stringsLazy);

// Eager Eclipse Collections API solution
MutableSet<String> stringsEager =
        list.flatCollect(
                map -> map.flatCollect(e -> e),
                Sets.mutable.empty());
Assert.assertEquals(expected, stringsEager);

// Fused collect operation
Set<String> stringsCollect = list.stream()
        .collect(
                HashSet::new,
                (set, map) -> map.values().forEach(set::addAll),
                Set::addAll);
Assert.assertEquals(expected, stringsCollect);

// Fused injectInto operation
MutableSet<String> stringsInject =
        list.injectInto(
                Sets.mutable.empty(),
                (set, map) -> set.withAll(map.flatCollect(e -> e)));
Assert.assertEquals(expected, stringsInject);

Note: I am a committer for Eclipse Collections.

Upvotes: 4

Holger
Holger

Reputation: 298599

An alternative to the .flatMap based solutions, is to fuse these sub-iterations into the final collect operation:

Set<String> output = input.stream()
    .collect(HashSet::new, (set,map) -> map.values().forEach(set::addAll), Set::addAll);

Upvotes: 5

fabian
fabian

Reputation: 82531

Use flatMap for this purpose

List< Map<String, List<String>> > maps = ...
Set<String> result = maps.stream()
                         .flatMap(m -> m.values().stream())
                         .flatMap(List::stream)
                         .collect(Collectors.toSet());

Upvotes: 6

user6732794
user6732794

Reputation:

You can use a series of Stream.map and Stream.flatMap:

List<Map<String, List<String>>> input = ...;

Set<String> output = input.stream()    // -> Stream<Map<String, List<String>>>
    .map(Map::values)                  // -> Stream<List<List<String>>>
    .flatMap(Collection::stream)       // -> Stream<List<String>>
    .flatMap(Collection::stream)       // -> Stream<String>
    .collect(Collectors.toSet())       // -> Set<String>
    ;

Upvotes: 19

Related Questions