Produit cartésien utilisant des flux Java


J'ai une fonction produit cartésienne en JavaScript:

function cartesianProduct(arr) {
    return arr.reduce(function(a,b) {
        return a.map(function(x) {
            return b.map(function(y) {
                return x.concat(y);
            });
        }).reduce(function(a,b) { return a.concat(b); }, []);
    }, [[]]);
}

De sorte que si j'ai un tableau 3D:

var data = [[['D']], [['E'],['L','M','N']]];

Le résultat de cartesianProduct(data) serait le tableau 2D:

[['D','E'], ['D','L','M','N']]

Ce que j'essaie de faire, c'est d'écrire cette fonction de produit cartésienne en Java à l'aide de Streams.

Jusqu'à présent, j'ai ce qui suit en Java:

public Collection<Collection<String>> cartesianProduct(Collection<Collection<Collection<String>>> arr) {

    return arr.stream().reduce(new ArrayList<Collection<String>>(), (a, b) -> {
        return a.stream().map(x -> {
            return b.stream().map(y -> {
                return Stream.concat(x.stream(), y.stream());
            });
        }).reduce(new ArrayList<String>(), (c, d) -> {
            return Stream.concat(c, d);
        });
    });
}

J'ai une erreur de vérification de type qui indique:

ArrayList<String> n'est pas compatible avec Stream<Stream<String>>

Mes suppositions quant à ce qui ne va pas:

  • j'ai besoin d'utiliser un collecteur de quelque part (peut-être après le Stream.concat)
  • Le type de données pour l'identité est incorrect
Author: Craig, 2016-07-27

1 answers

Cela est possible avec un peu de magie de programmation fonctionnelle. Voici la méthode qui accepte Collection<Collection<Collection<T>>> et produit Stream<Collection<T>>:

static <T> Stream<Collection<T>> cartesianProduct(Collection<Collection<Collection<T>>> arr)
{
    return arr.stream()
        .<Supplier<Stream<Collection<T>>>> map(c -> c::stream)
        .reduce((s1, s2) -> () -> s1.get().flatMap(
                a -> s2.get().map(b -> Stream.concat(a.stream(), b.stream())
                        .collect(Collectors.toList()))))
        .orElseGet(() -> () -> Stream.<Collection<T>>of(Collections.emptyList()))
        .get();
}

Exemple d'Utilisation:

cartesianProduct(
    Arrays.asList(Arrays.asList(Arrays.asList("D")),
        Arrays.asList(Arrays.asList("E"), Arrays.asList("L", "M", "N"))))
            .forEach(System.out::println);

Sortie:

[D, E]
[D, L, M, N]

Bien sûr, au lieu de .forEach(), vous pouvez collecter les résultats dans le List si vous voulez retourner Collection<Collection<T>> à la place, mais retourner Stream me semble plus flexible.

Un peu d'explication:

Ici, nous créons un flux de fournisseurs de flux via map(c -> c::stream). Chaque fonction de ce flux peut produire à la demande un flux des éléments de collection correspondants. Nous faisons cela parce que les flux une fois off (sinon avoir un flux de flux serait suffisant). Après cela, nous réduisons ce flux de fournisseurs en créant un nouveau fournisseur pour chaque paire qui aplatit deux flux et mappe leurs éléments aux listes concaténées. La partie orElseGet est nécessaire pour gérer l'entrée vide. Le dernier .get() appelle simplement le fournisseur de flux final pour obtenir le flux résultant.

 2
Author: Tagir Valeev, 2016-08-04 03:52:17