简体   繁体   English

用Java Stream实现几个集合的笛卡尔积

[英]Implement Cartesian product of several collections by Java Stream

Right now I can only implement the Cartesian product of two collections, here is the code:现在我只能实现两个集合的笛卡尔积,代码如下:

public static <T1, T2, R extends Collection<Pair<T1, T2>>>
R getCartesianProduct(
        Collection<T1> c1, Collection<T2> c2,
        Collector<Pair<T1, T2>, ?, R> collector) {
    return c1.stream()
            .flatMap(e1 -> c2.stream().map(e2 -> new Pair<>(e1, e2)))
            .collect(collector);
}

This code works fine in IntelliJ , but not in Eclipse .此代码在IntelliJ 中工作正常,但在Eclipse 中无效。 Both with compiler compliance level of 1.8:两者的编译器合规性级别均为 1.8:

The method collect(Collector<? super Object,A,R>)
in the type Stream<Object> is not applicable for
the arguments (Collector<Pair<T1,T2>,capture#5-of ?,R>)

Here is Pair.java:这是 Pair.java:

public class Pair<T1, T2> implements Serializable {
    protected T1 first;
    protected T2 second;
    private static final long serialVersionUID = 1360822168806852921L;

    public Pair(T1 first, T2 second) {
        this.first = first;
        this.second = second;
    }

    public Pair(Pair<T1, T2> pair) {
        this(pair.getFirst(), pair.getSecond());
    }

    public T1 getFirst() {
        return this.first;
    }

    public T2 getSecond() {
        return this.second;
    }

    public void setFirst(T1 o) {
        this.first = o;
    }

    public void setSecond(T2 o) {
        this.second = o;
    }

    public String toString() {
        return "(" + this.first + ", " + this.second + ")";
    }

    @Override
    public boolean equals(Object o) {
        if(!(o instanceof Pair))
            return false;
        Pair p = (Pair) o;
        if(!this.first.equals(p.first))
            return false;
        if(!this.second.equals(p.second))
            return false;
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 1;
        hash = hash * 31 + this.first.hashCode();
        hash = hash * 31 + this.second.hashCode();
        return hash;
    }
}

How to fix this error?如何修复此错误?

Is there an elegant way to implement the Cartesian product of several collections?有没有一种优雅的方式来实现几个集合的笛卡尔积? Suppose we have class tuple .假设我们有类tuple

Eclipse has problems with type inference. Eclipse 在类型推断方面存在问题。 If you add a type hint .<Pair<T1,T2>>flatMap , it compiles fine.如果你添加一个类型提示.<Pair<T1,T2>>flatMap ,它编译得很好。

If I may suggest a different approach, consider making your cartesianProduct not do the entire stream and collection but merely be a helper for flatMap :如果我可以建议一种不同的方法,请考虑让您的 cartesianProduct 不执行整个流和集合,而只是作为flatMap的帮手:

static <T1, T2, R> Function<T1, Stream<R>> crossWith(
         Supplier<? extends Stream<T2>> otherSup, 
         BiFunction<? super T1, ? super T2, ? extends R> combiner
) {
    return t1 -> otherSup.get().map(t2 -> combiner.apply(t1, t2));
}

Now you only need to create a Pair if you want the result to contains Pair s and you can do a higher-order cartesian product by applying flatMap several times:现在你只需要创建一个Pair如果你希望结果包含Pair s,你可以通过多次应用flatMap来做一个高阶笛卡尔积:

List<String> letters = Arrays.asList("A", "B", "C");
List<Integer> numbers = Arrays.asList(1, 2, 3);

List<Pair<String, Integer>> board = letters.stream()
                .flatMap(crossWith(numbers::stream, Pair::new))
                .collect(toList());


List<String> ops = Arrays.asList("+", "-", "*", "/");

List<String> combinations = letters.stream()
                .flatMap(crossWith(ops::stream, String::concat))
                .flatMap(crossWith(letters::stream, String::concat))
                .collect(toList());   // triple cartesian product

Here's a solution that generalizes to the case that the number of requisite flatMap-applications (ie the order of the product) is not known at compile time.这是一个解决方案,它概括了在编译时不知道必需的 flatMap 应用程序的数量(即产品的顺序)的情况。

BinaryOperator<Function<String,Stream<String>>> kleisli = (f,g) -> s -> f.apply(s).flatMap(g);

List<String> cartesian(int n, Collection<String> coll) {
    return coll.stream()
           .flatMap( IntStream.range(1, n).boxed()
                     .map(_any -> crossWith(coll::stream, String::concat)) // create (n-1) appropriate crossWith instances
                     .reduce(s -> Stream.of(s), kleisli)                   // compose them sequentially
                    )                                                      // flatMap the stream with the entire function chain
           .collect(toList());
}

You'll find details of how this works at my own blog .您可以在我自己的博客中找到有关其工作原理的详细信息。

You can use Supplier as a parameter for the Collectors.toCollection method.您可以将Supplier商用作Collectors.toCollection方法的参数。 This simplifies the code and makes it more generic.这简化了代码并使其更通用。 The Cartesian product of different types and numbers of collections and their elements.不同类型和数量的集合及其元素的笛卡尔积

Try it online! 在线试试吧!

public static void main(String[] args) {
    Set<Integer> a = Set.of(1, 2);
    Set<Character> b = Set.of('*', '+');
    List<String> c = List.of("A", "B");

    Set<Set<Object>> cpSet = cartesianProduct(HashSet::new, a, b, c);
    List<List<Object>> cpList = cartesianProduct(ArrayList::new, a, b, c);

    // output, order may vary
    System.out.println(cpSet);
    System.out.println(cpList);
}
/**
 * @param nCol the supplier of the output collection
 * @param cols the input array of collections
 * @param <R>  the type of the return collection
 * @return the cartesian product of the multiple collections
 */
@SuppressWarnings("unchecked")
public static <R extends Collection<?>> R cartesianProduct(
        Supplier nCol, Collection<?>... cols) {
    // check if supplier is not null
    if (nCol == null) return null;
    return (R) Arrays.stream(cols)
        // non-null and non-empty collections
        .filter(col -> col != null && col.size() > 0)
        // represent each element of a collection as a singleton collection
        .map(col -> (Collection<Collection<?>>) col.stream()
            .map(e -> Stream.of(e).collect(Collectors.toCollection(nCol)))
            .collect(Collectors.toCollection(nCol)))
        // summation of pairs of inner collections
        .reduce((col1, col2) -> (Collection<Collection<?>>) col1.stream()
            // combinations of inner collections
            .flatMap(inner1 -> col2.stream()
                // concatenate into a single collection
                .map(inner2 -> Stream.of(inner1, inner2)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toCollection(nCol))))
            // collection of combinations
            .collect(Collectors.toCollection(nCol)))
        // otherwise an empty collection
        .orElse((Collection<Collection<?>>) nCol.get());
}

Output (order may vary):输出(顺序可能会有所不同):

[[1,A,*],[1,B,*],[1,A,+],[A,2,*],[1,B,+],[2,B,*],[A,2,+],[2,B,+]]
[[2,+,A],[2,+,B],[2,*,A],[2,*,B],[1,+,A],[1,+,B],[1,*,A],[1,*,B]]

See also the less generic version: Finding cartesian product in Java另请参阅不太通用的版本:在 Java 中查找笛卡尔积

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM