0

Using Java I am attempting to create the Cartesian Product of a map which contains String keys and List<String> values. I need to preserve the values of the keys when creating the list like so:

  • From a Map<String, List<String>> Object
  • To a List<Map<String, String>> Object

I also have the requirement that when a List<String> value in the original map is empty it should still be treated as a single value when creating the Cartesian Product so as to avoid multiplying by 0 and creating no maps. For example the following map:

{
    "Location Number" = {"100", "500"}
    "Department Number" = {"11", "22", "33"}
    "District Number" = {}
    "Preferred Language" = {"en-US"}
}

Is translated to:

{
    {
        "Location Number" = "100"
        "Department Number" = "11"
        "District Number" = {}
        "Preferred Language" = "en-US"
    },
    {
        "Location Number" = "100"
        "Department Number" = "22"
        "District Number" = {}
        "Preferred Language" = "en-US"
    },
    {
        "Location Number" = "100"
        "Department Number" = "33"
        "District Number" = {}
        "Preferred Language" = "en-US"
    },
    {
        "Location Number" = "500"
        "Department Number" = "11"
        "District Number" = {}
        "Preferred Language" = "en-US"
    },
    {
        "Location Number" = "500"
        "Department Number" = "22"
        "District Number" = {}
        "Preferred Language" = "en-US"
    },
    {
        "Location Number" = "500"
        "Department Number" = "33"
        "District Number" = {}
        "Preferred Language" = "en-US"
    }
}

Below is the code I am currently using to accomplish a similar translation, but it does not retain the key, which I need. I do not know if this is possible to accomplish using Java 8 Streams in general.

private static List<List<String>> createRuleListFromMap(Map<String, List<String>> ruleMap) {
    List<List<String>> ruleList = new ArrayList<>();
    cartesianProduct(ruleMap.values()).forEach(ruleList::add);
    return ruleList;
}

private static <T> Stream<List<T>> cartesianProduct(Collection<? extends Collection<T>> collections) {
    return cartesianProduct(new ArrayList<Collection<T>>(collections), Collections.emptyList());
}

private static <T> Stream<List<T>> cartesianProduct(List<? extends Collection<T>> collections, List<T> current) {
    return collections.isEmpty() ? Stream.of(current) : collections.get(0).stream().flatMap(e -> {
        List<T> list = new ArrayList<>(current);
        list.add(e);
        return cartesianProduct(collections.subList(1, collections.size()), list);
    });
}

2 Answers 2

2

Here is simple example how to transform Map<String,List<String>> to List<Map<String,String>>:

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class StreamTest {
    @Test
    public void test() {
        var map = new HashMap<String, List<String>>();
        map.put("key1", Arrays.asList("value1", "value2", "value3"));
        map.put("key2", Arrays.asList("value4", "value5", "value6"));

        var list = map.entrySet().stream()
                .flatMap(e -> e.getValue().stream()
                        .map(v -> Map.of(e.getKey(), v)))
                .collect(Collectors.toList());

        log.info(list.toString());
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

This provides a single map per entry (key mapped to elements in entry list), not a cartesian product.
0

You can first represent the elements of lists, each as Map<String,String>, and get a stream of lists of maps, and then reduce this stream to a single list by sequentially summing pairs of inner maps to get the Cartesian product.

Try it online!

Map<String, List<String>> map = new LinkedHashMap<>();
map.put("Location Number", List.of("100", "500"));
map.put("Department Number", List.of("11", "22", "33"));
map.put("District Number", List.of("")); // NOT an empty list!
map.put("Preferred Language", List.of("en-US"));
List<Map<String, String>> list = map.entrySet().stream()
        .map(entry -> entry.getValue().stream()
                // list elements as Map<String,String>
                .map(str -> Map.of(entry.getKey(), str))
                // List<Map<String,String>>
                .collect(Collectors.toList()))
        // intermediate output
        .peek(System.out::println)
        // stream of lists to a single list
        .reduce((list1, list2) -> list1.stream()
                // combinations of inner maps
                .flatMap(map1 -> list2.stream()
                        // join entries of two maps
                        .map(map2 -> {
                            Map<String, String> mp =
                                    new LinkedHashMap<>();
                            mp.putAll(map1);
                            mp.putAll(map2);
                            return mp;
                        }))
                // list of combinations
                .collect(Collectors.toList()))
        // returns List<Map<String,String>
        // otherwise an empty list
        .orElse(Collections.emptyList());
// final output
list.forEach(System.out::println);

Intermediate output:

[{Location Number=100}, {Location Number=500}]
[{Department Number=11}, {Department Number=22}, {Department Number=33}]
[{District Number=}]
[{Preferred Language=en-US}]

Final output:

{Location Number=100, Department Number=11, District Number=, Preferred Language=en-US}
{Location Number=100, Department Number=22, District Number=, Preferred Language=en-US}
{Location Number=100, Department Number=33, District Number=, Preferred Language=en-US}
{Location Number=500, Department Number=11, District Number=, Preferred Language=en-US}
{Location Number=500, Department Number=22, District Number=, Preferred Language=en-US}
{Location Number=500, Department Number=33, District Number=, Preferred Language=en-US}

See also: Convert a map of lists into a list of maps

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.