如何使用Java 8流制作笛卡尔积?

我有以下集合类型:

Map<String, Collection> map; 

我想从每个Key的集合中的单个值创建每个map.size()唯一组合。

例如,假设地图如下所示:

 A, {a1, a2, a3, ..., an} B, {b1, b2, b3, ..., bn} C, {c1, c2, c3, ..., cn} 

我想得到的结果是List<Set>结果,看起来类似于(排序并不重要,它只需要是一个由所有可能组合组成的’完整’结果):

 {a1, b1, c1}, {a1, b1, c2}, {a1, b1, c3}, {a1, b2, c1}, {a1, b2, c2}, {a1, b2, c3}, ... {a2, b1, c1}, {a2, b1, c2}, ... {a3, b1, c1}, {a3, b1, c2}, ... {an, bn, cn} 

这基本上是一个计数问题,但我想看看是否可以使用Java 8流解决方案。

您可以使用递归flatMap链来解决此问题。

首先,因为我们需要通过map值来回移动,所以最好将它们复制到ArrayList (这不是深层副本,在你的情况下它只是3个元素的ArrayList ,所以额外的内存使用率很低)。

其次,为了维护以前访问过的元素的前缀,让我们创建一个帮助器不可变的Prefix类:

 private static class Prefix { final T value; final Prefix parent; Prefix(Prefix parent, T value) { this.parent = parent; this.value = value; } // put the whole prefix into given collection > C addTo(C collection) { if (parent != null) parent.addTo(collection); collection.add(value); return collection; } } 

这是一个非常简单的不可变链表,可以像这样使用:

 List list = new Prefix<>(new Prefix<>(new Prefix<>(null, "a"), "b"), "c") .addTo(new ArrayList<>()); // [a, b, c]; 

接下来,让我们创建链接flatMaps的内部方法:

 private static > Stream comb( List> values, int offset, Prefix prefix, Supplier supplier) { if (offset == values.size() - 1) return values.get(offset).stream() .map(e -> new Prefix<>(prefix, e).addTo(supplier.get())); return values.get(offset).stream() .flatMap(e -> comb(values, offset + 1, new Prefix<>(prefix, e), supplier)); } 

看起来像递归,但它更复杂:它不直接调用自身,但传递lambda调用外部方法。 参数:

  • values:原始值new ArrayList<>(map.values)在您的情况下为new ArrayList<>(map.values) )。
  • offset:此列表中的当前偏移量
  • prefix:长度偏移量的当前前缀(如果offset == 0则为null )。 它包含集合list.get(0)list.get(1)list.get(offset-1)中当前选定的元素。
  • supplier:用于创建结果集合的工厂方法。

当我们到达值列表的末尾( offset == values.size() - 1 )时,我们使用供应商将最后一个集合的元素从值映射到最终组合。 否则,我们使用flatMap为每个中间元素放大前缀,并再次为下一个偏移调用comb方法。

最后,这是使用此function的公共方法:

 public static > Stream ofCombinations( Collection> values, Supplier supplier) { if (values.isEmpty()) return Stream.empty(); return comb(new ArrayList<>(values), 0, null, supplier); } 

一个用法示例:

 Map> map = new LinkedHashMap<>(); // to preserve the order map.put("A", Arrays.asList("a1", "a2", "a3", "a4")); map.put("B", Arrays.asList("b1", "b2", "b3")); map.put("C", Arrays.asList("c1", "c2")); ofCombinations(map.values(), LinkedHashSet::new).forEach(System.out::println); 

我们再次收集LinkedHashSet单个组合以保留订单。 您可以使用任何其他集合(例如ArrayList::new )。

一种主要在列表上运行的解决方案,使事情变得更加简单。 它在flatMap中执行递归调用,跟踪已经组合的元素以及仍然缺少的元素集合,并将此嵌套递归构造的结果作为列表流提供:

 import java.util.*; import java.util.stream.Stream; public class CartesianProduct { public static void main(String[] args) { Map> map = new LinkedHashMap>(); map.put("A", Arrays.asList("a1", "a2", "a3", "a4")); map.put("B", Arrays.asList("b1", "b2", "b3")); map.put("C", Arrays.asList("c1", "c2")); ofCombinations(map.values()).forEach(System.out::println); } public static  Stream> ofCombinations( Collection> collections) { return ofCombinations( new ArrayList>(collections), Collections.emptyList()); } private static  Stream> ofCombinations( List> collections, List current) { return collections.isEmpty() ? Stream.of(current) : collections.get(0).stream().flatMap(e -> { List list = new ArrayList(current); list.add(e); return ofCombinations( collections.subList(1, collections.size()), list); }); } } 

这是另一种解决方案,它不像Streir那样使用Streams许多function; 但我相信它更直接:

 public class Permutations { transient List> perms; public List> list(Map> map) { SortedMap> sortedMap = new TreeMap<>(); sortedMap.putAll(map); sortedMap.values().forEach((v) -> perms = expand(perms, v)); return perms; } private List> expand(List> list, Collection elements) { List> newList = new LinkedList<>(); if (list == null) { elements.forEach((e) -> { SortedSet set = new TreeSet<>(); set.add(e); newList.add(set); }); } else { list.forEach((set) -> elements.forEach((e) -> { SortedSet newSet = new TreeSet<>(); newSet.addAll(set); newSet.add(e); newList.add(newSet); })); } return newList; } } 

如果您对元素的Sorted不感兴趣,可以删除Sorted前缀; 但是,我认为如果所有内容都已排序,调试会更容易。

用法:

 Permutations p = new Permutations(); List> plist = p.list(map); plist.forEach((s) -> System.out.println(s)); 

请享用!

Java 8中的笛卡尔积与forEach:

 List listA = new ArrayList<>(); listA.add("0"); listA.add("1"); List listB = new ArrayList<>(); listB.add("a"); listB.add("b"); List cartesianProduct = new ArrayList<>(); listA.forEach(a -> listB.forEach(b -> cartesianProduct.add(a + b))); cartesianProduct.forEach(System.out::println); //Output : 0a 0b 1a 1b 

使用消费者函数类,列表和foreach

  public void tester(){ String[] strs1 = {"2","4","9"}; String[] strs2 = {"9","0","5"}; //Final output is {"29", "49, 99", "20", "40", "90", "25", "45", "95"} List result = new ArrayList<>(); Consumer consumer = (String str) -> result.addAll(Arrays.stream(strs1).map(s -> s+str).collect(Collectors.toList())); Arrays.stream(strs2).forEach(consumer); System.out.println(result); } 

在循环中创建组合列表

 List cartesianProduct(List> wordLists) { List cp = wordLists.get(0); for (int i = 1; i < wordLists.size(); i++) { List secondList = wordLists.get(i); List combinedList = cp.stream().flatMap(s1 -> secondList.stream().map(s2 -> s1 + s2)) .collect(Collectors.toList()); cp = combinedList; } return cp; }