1

I have a map of lists like:

Map("a" -> [1,2,3], "b" -> [4,5,6], "c" -> [7])

I need to produce:

[
Map("a" -> 1, "b" -> 4, "c" -> 7),
Map("a" -> 1, "b" -> 5, "c" -> 7),
Map("a" -> 1, "b" -> 6, "c" -> 7),
Map("a" -> 2, "b" -> 4, "c" -> 7),
Map("a" -> 2, "b" -> 5, "c" -> 7),
Map("a" -> 2, "b" -> 6, "c" -> 7),
Map("a" -> 3, "b" -> 4, "c" -> 7),
Map("a" -> 3, "b" -> 5, "c" -> 7),
Map("a" -> 3, "b" -> 6, "c" -> 7),
]

I am using a Java library called Vavr for my container types but I don't mind seeing the implementation done in any language.

0

5 Answers 5

4
+50

You can first iterate through map entries and represent list elements as Map<String,Integer> and get a stream of lists of maps, and then reduce this stream to a single list.

Try it online!

Map<String, List<Integer>> mapOfLists = new LinkedHashMap<>();
mapOfLists.put("a", List.of(1, 2, 3));
mapOfLists.put("b", List.of(4, 5, 6));
mapOfLists.put("c", List.of(7));
List<Map<String, Integer>> listOfMaps = mapOfLists.entrySet().stream()
        // Stream<List<Map<String,Integer>>>
        .map(entry -> entry.getValue().stream()
                // represent list elements as Map<String,Integer>
                .map(element -> Map.of(entry.getKey(), element))
                // collect a list of maps
                .collect(Collectors.toList()))
        // intermediate output
        //[{a=1}, {a=2}, {a=3}]
        //[{b=4}, {b=5}, {b=6}]
        //[{c=7}]
        .peek(System.out::println)
        // reduce a stream of lists to a single list
        // by sequentially multiplying the list pairs
        .reduce((list1, list2) -> list1.stream()
                // combinations of elements,
                // i.e. maps, from two lists
                .flatMap(map1 -> list2.stream()
                        .map(map2 -> {
                            // join entries of two maps
                            Map<String, Integer> map =
                                    new LinkedHashMap<>();
                            map.putAll(map1);
                            map.putAll(map2);
                            return map;
                        }))
                // collect into a single list
                .collect(Collectors.toList()))
        .orElse(null);
// output
listOfMaps.forEach(System.out::println);
//{a=1, b=4, c=7}
//{a=1, b=5, c=7}
//{a=1, b=6, c=7}
//{a=2, b=4, c=7}
//{a=2, b=5, c=7}
//{a=2, b=6, c=7}
//{a=3, b=4, c=7}
//{a=3, b=5, c=7}
//{a=3, b=6, c=7}

See also:
Generate all possible string combinations by replacing the hidden # number sign
How to get all possible combinations from two arrays?

Sign up to request clarification or add additional context in comments.

Comments

2

Here is a solution using recursion:

public static void main(String[] args) {
    Map<String, int[]> map = new HashMap<>();
    map.put("a", new int[]{1, 2, 3});
    map.put("b", new int[]{4, 5, 6});
    map.put("c", new int[]{7});
    List<Map<String, Integer>> combinations = new ArrayList<>();
    System.out.println(getCombinations(
            map, combinations, 0,
            new HashMap<String, Integer>(), map.values().size()));
}
private static List<Map<String, Integer>> getCombinations(
        Map<String, int[]> map, List<Map<String, Integer>> combinations, int i,
        Map<String, Integer> current, int len) {
    if (i >= len) {
        combinations.add(current);
    } else {
        String key = (String) map.keySet().toArray()[i];
        int[] value = map.get(key);
        for (int num : value) {
            current.put(key, num);
            getCombinations(map, combinations, i + 1,
                    new HashMap<>(current), len);
        }
    }
    return combinations;
}

Output:

[
  {a=1, b=4, c=7}, {a=1, b=5, c=7}, {a=1, b=6, c=7}, 
  {a=2, b=4, c=7}, {a=2, b=5, c=7}, {a=2, b=6, c=7}, 
  {a=3, b=4, c=7}, {a=3, b=5, c=7}, {a=3, b=6, c=7}
]

Comments

1

Here is one way to do it:

public static void main(String[] args) {
    Map<String, List<Integer>> map = new HashMap<>();
    map.put("a", Arrays.asList(1, 2, 3));
    map.put("b", Arrays.asList(4, 5, 6));
    map.put("c", Arrays.asList(7));

    List<Map.Entry<String, List<Integer>>> list =
            new ArrayList<>(map.entrySet());

    List<Map<String, Integer>> result = new ArrayList<>();
    combine(list, 0, new HashMap<>(), result);
    System.out.println(result);
}
static void combine(List<Map.Entry<String, List<Integer>>> list, int n,
                    Map<String, Integer> part,
                    List<Map<String, Integer>> result) {
    if (n >= list.size()) {
        result.add(new HashMap<>(part));
    } else {
        Map.Entry<String, List<Integer>> e = list.get(n);
        for (Integer i : e.getValue()) {
            part.put(e.getKey(), i);
            combine(list, n + 1, part, result);
        }
        part.remove(e.getKey());
    }
}

Output: (edited for formatting)

[
  {a=1, b=4, c=7}, {a=1, b=5, c=7}, {a=1, b=6, c=7},
  {a=2, b=4, c=7}, {a=2, b=5, c=7}, {a=2, b=6, c=7},
  {a=3, b=4, c=7}, {a=3, b=5, c=7}, {a=3, b=6, c=7}
]

Comments

0

I ended up doing it differently to the answers here.

I already had a class for performing Cartesian Product like:

public class CartesianProduct {
    public static <T> Seq<Seq<T>> on(Seq<Seq<T>> sets) {
        if (sets.size() >= 1) {
            var javaList = sets.map(Value::toJavaList).toJavaList();
            var out = computeCombinations(javaList);
            return Vector.ofAll(out.stream().map(Vector::ofAll));
        }
        return Vector.empty();
    }

    private static <T> List<List<T>> appendElements(
            List<List<T>> combinations, List<T> extraElements) {
        return combinations.stream()
                .flatMap(oldCombination ->
                        extraElements.stream().map(extra -> {
                            List<T> combinationWithExtra =
                                    new ArrayList<>(oldCombination);
                            combinationWithExtra.add(extra);
                            return combinationWithExtra;
                        }))
                .collect(Collectors.toList());
    }

    private static <T> List<List<T>> computeCombinations(List<List<T>> lists) {
        List<List<T>> currentCombinations = List.of(List.of());
        for (List<T> list : lists) {
            currentCombinations = appendElements(currentCombinations, list);
        }
        return currentCombinations;
    }
}

So I did:

Seq<Seq<Integer>> allValuesProduct = CartesianProduct.on(mapOfLists.values());
Seq<Map<String, Integer>> listOfMaps =
        allValuesProduct.map(values -> HashMap.tabulate(
                values.length(),
                i -> Tuple.of(
                        mapOfLists.keySet().toList().get(i),
                        values.get(i))));

Although I'm sure there must be a cleaner way that doesn't involve mutation at all.

Comments

0

This is how I would solve this

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class molToLom {

    public static void main(String[] args) {
        //This is what we have: A map of lists (mol)
        TreeMap<String, List<Integer>> mol = new TreeMap<String, List<Integer>>();
        mol.put("a", List.of(1, 2, 3));
        mol.put("b", List.of(4, 5, 6));
        mol.put("c", List.of(7));
        //we want a list of maps (lom)
        ArrayList<Map<String, Integer>> lom = new ArrayList<>();
        //Add an empty element. We'll need that later
        lom.add(new TreeMap<String, Integer>());
        //Now run through our map
        mol.forEach((tag, list) -> {
            //We rebuild our lom on every iteration. Otherwise we would get incomplete maps in our list
            ArrayList<Map<String, Integer>> tLom = new ArrayList<>();
            //Run through our list of maps and ...
            lom.forEach(map -> {
                //... combine every element in this list with every element in the list from our initial map entry
                list.forEach(i -> {
                    Map<String, Integer> m = new TreeMap<>(map);
                    m.put(tag, i);
                    tLom.add(m);
                });
            });
            //replace our last lom with the newly created one. Could be lom = tLom if lom was a field
            lom.clear();
            lom.addAll(tLom);
        });
        System.out.println(lom);
    }
}

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.