J'ai le type de collection suivant:
Map<String, Collection<String>> map;
J'aimerais créer des combinaisons uniques de chacune des map.size()
à partir d'une valeur unique de la collection pour chaque clé.
Par exemple, supposons que la carte ressemble à ceci:
A, {a1, a2, a3, ..., an}
B, {b1, b2, b3, ..., bn}
C, {c1, c2, c3, ..., cn}
Le résultat que j'aimerais obtenir serait un résultat List<Set<String>>
, ressemblant à (l'ordre n'est pas important, il doit simplement s'agir d'un résultat 'complet' comprenant toutes les combinaisons possibles):
{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}
Il s’agit essentiellement d’un problème de comptage, mais j’aimerais voir si une solution est possible avec les flux Java 8.
Vous pouvez résoudre ce problème en utilisant la chaîne récursive flatMap
.
Tout d'abord, car nous devons parcourir les valeurs de la carte, il est préférable de les copier dans la variable ArrayList
(il ne s'agit pas de la copie intégrale; dans votre cas, il s'agit de ArrayList
de 3 éléments uniquement, de sorte que l'utilisation supplémentaire de mémoire est faible).
Deuxièmement, pour conserver un préfixe des éléments précédemment visités, créons une classe immuable d'assistant Prefix
:
private static class Prefix<T> {
final T value;
final Prefix<T> parent;
Prefix(Prefix<T> parent, T value) {
this.parent = parent;
this.value = value;
}
// put the whole prefix into given collection
<C extends Collection<T>> C addTo(C collection) {
if (parent != null)
parent.addTo(collection);
collection.add(value);
return collection;
}
}
C'est une liste chaînée immuable très simple qui peut être utilisée comme ceci:
List<String> list = new Prefix<>(new Prefix<>(new Prefix<>(null, "a"), "b"), "c")
.addTo(new ArrayList<>()); // [a, b, c];
Ensuite, créons la méthode interne qui chaîne flatMaps:
private static <T, C extends Collection<T>> Stream<C> comb(
List<? extends Collection<T>> values, int offset, Prefix<T> prefix,
Supplier<C> 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));
}
Cela ressemble à de la récursivité, mais c'est plus complexe: il ne s'appelle pas directement, mais passe lambda, qui appelle la méthode externe. Paramètres:
List
des valeurs d'origine (new ArrayList<>(map.values)
dans votre cas).null
si offset == 0
). Il contient les éléments actuellement sélectionnés dans les collections list.get(0)
, list.get(1)
jusqu'à list.get(offset-1)
.Lorsque nous avons atteint la fin de la liste de valeurs (offset == values.size() - 1
), nous mappons les éléments de la dernière collection des valeurs à la combinaison finale à l'aide du fournisseur. Sinon, nous utilisons la variable flatMap
qui, pour chaque élément intermédiaire, agrandit le préfixe et appelle à nouveau la méthode comb
pour le décalage suivant.
Enfin, voici une méthode publique pour utiliser cette fonctionnalité:
public static <T, C extends Collection<T>> Stream<C> ofCombinations(
Collection<? extends Collection<T>> values, Supplier<C> supplier) {
if (values.isEmpty())
return Stream.empty();
return comb(new ArrayList<>(values), 0, null, supplier);
}
Un exemple d'utilisation:
Map<String, Collection<String>> 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);
Nous recueillons à nouveau les combinaisons individuelles dans LinkedHashSet
afin de préserver l'ordre. Vous pouvez utiliser toute autre collection à la place (par exemple ArrayList::new
).
Une solution qui fonctionne principalement sur des listes, ce qui simplifie beaucoup les choses. Il effectue un appel récursif dans flatMap
, en gardant une trace des éléments déjà combinés et des collections d'éléments manquants, et offre les résultats de cette construction récursive imbriquée sous forme de flux de listes:
import Java.util.*;
import Java.util.stream.Stream;
public class CartesianProduct {
public static void main(String[] args) {
Map<String, Collection<String>> map =
new LinkedHashMap<String, Collection<String>>();
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 <T> Stream<List<T>> ofCombinations(
Collection<? extends Collection<T>> collections) {
return ofCombinations(
new ArrayList<Collection<T>>(collections),
Collections.emptyList());
}
private static <T> Stream<List<T>> ofCombinations(
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<T>(current);
list.add(e);
return ofCombinations(
collections.subList(1, collections.size()), list);
});
}
}
Produit cartésien en Java 8 avec forEach:
List<String> listA = new ArrayList<>();
listA.add("0");
listA.add("1");
List<String> listB = new ArrayList<>();
listB.add("a");
listB.add("b");
List<String> cartesianProduct = new ArrayList<>();
listA.forEach(a -> listB.forEach(b -> cartesianProduct.add(a + b)));
cartesianProduct.forEach(System.out::println);
//Output : 0a 0b 1a 1b
Voici une autre solution, qui n'utilise pas autant de fonctionnalités de Streams
que l'exemple de Tagir; Cependant, je crois que c'est plus simple:
public class Permutations {
transient List<Collection<String>> perms;
public List<Collection<String>> list(Map<String, Collection<String>> map) {
SortedMap<String, Collection<String>> sortedMap = new TreeMap<>();
sortedMap.putAll(map);
sortedMap.values().forEach((v) -> perms = expand(perms, v));
return perms;
}
private List<Collection<String>> expand(List<Collection<String>> list, Collection<String> elements) {
List<Collection<String>> newList = new LinkedList<>();
if (list == null) {
elements.forEach((e) -> {
SortedSet<String> set = new TreeSet<>();
set.add(e);
newList.add(set);
});
} else {
list.forEach((set) ->
elements.forEach((e) -> {
SortedSet<String> newSet = new TreeSet<>();
newSet.addAll(set);
newSet.add(e);
newList.add(newSet);
}));
}
return newList;
}
}
Vous pouvez supprimer le préfixe Sorted
si vous n'êtes pas intéressé par la commande d'éléments. Cependant, je pense qu'il est plus facile de déboguer si tout est réglé.
Usage:
Permutations p = new Permutations();
List<Collection<String>> plist = p.list(map);
plist.forEach((s) -> System.out.println(s));
Prendre plaisir!
En boucle créer une liste combinée
List<String> cartesianProduct(List<List<String>> wordLists) {
List<String> cp = wordLists.get(0);
for (int i = 1; i < wordLists.size(); i++)
{
List<String> secondList = wordLists.get(i);
List<String> combinedList = cp.stream().flatMap(s1 -> secondList.stream().map(s2 -> s1 + s2))
.collect(Collectors.toList());
cp = combinedList;
}
return cp;
}
J'ai écrit une classe implémentant Iterable
et ne conservant que l'élément actuel en mémoire. La Iterable
ainsi que la Iterator
peuvent être converties en Stream
si vous le souhaitez.
class CartesianProduct<T> implements Iterable<List<T>> {
private final Iterable<? extends Iterable<T>> factors;
public CartesianProduct(final Iterable<? extends Iterable<T>> factors) {
this.factors = factors;
}
@Override
public Iterator<List<T>> iterator() {
return new CartesianProductIterator<>(factors);
}
}
class CartesianProductIterator<T> implements Iterator<List<T>> {
private final List<Iterable<T>> factors;
private final Stack<Iterator<T>> iterators;
private final Stack<T> current;
private List<T> next;
private int index = 0;
private void computeNext() {
while (true) {
if (iterators.get(index).hasNext()) {
current.add(iterators.get(index).next());
if (index == factors.size() - 1) {
next = new ArrayList<>(current);
current.pop();
return;
}
index++;
iterators.add(factors.get(index).iterator());
} else {
index--;
if (index < 0) {
return;
}
iterators.pop();
current.pop();
}
}
}
public CartesianProductIterator(final Iterable<? extends Iterable<T>> factors) {
this.factors = StreamSupport.stream(factors.spliterator(), false)
.collect(Collectors.toList());
if (this.factors.size() == 0) {
index = -1;
}
iterators = new Stack<>();
iterators.add(this.factors.get(0).iterator());
current = new Stack<>();
computeNext();
}
@Override
public boolean hasNext() {
if (next == null && index >= 0) {
computeNext();
}
return next != null;
}
@Override
public List<T> next() {
if (!hasNext()) {
throw new IllegalStateException();
}
var result = next;
next = null;
return result;
}
}
Utiliser une classe de fonction consommateur, une liste et un 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<String> result = new ArrayList<>();
Consumer<String> consumer = (String str) -> result.addAll(Arrays.stream(strs1).map(s -> s+str).collect(Collectors.toList()));
Arrays.stream(strs2).forEach(consumer);
System.out.println(result);
}