¿Cómo puedo hacer un producto cartesiano con secuencias de Java 8?
java-8 java-stream (9)
Aquí hay otra solución, que no utiliza tantas características de
Streams
como el ejemplo de Tagir;
Sin embargo, creo que es más sencillo:
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;
}
}
Puede eliminar el prefijo ordenado si no está interesado en ordenar elementos; Sin embargo, creo que es más fácil depurar si todo está ordenado.
Uso:
Permutations p = new Permutations();
List<Collection<String>> plist = p.list(map);
plist.forEach((s) -> System.out.println(s));
¡Disfrutar!
Tengo el siguiente tipo de colección:
Map<String, Collection<String>> map;
Me gustaría crear combinaciones únicas de cada uno de
map.size()
partir de un único valor en la colección para cada Clave.
Por ejemplo, suponga que el mapa tiene el siguiente aspecto:
A, {a1, a2, a3, ..., an}
B, {b1, b2, b3, ..., bn}
C, {c1, c2, c3, ..., cn}
El resultado que me gustaría obtener sería un resultado
List<Set<String>>
, similar a (ordenar no es importante, solo necesita ser un resultado ''completo'' que conste de todas las combinaciones posibles):
{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}
Esto es básicamente un problema de conteo, pero me gustaría ver si es posible una solución usando flujos Java 8.
En bucle crear lista combinada
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;
}
Escribí una clase implementando
Iterable
, y guardando solo el elemento actual en la memoria.
Iterable
como
Iterator
se pueden convertir a
Stream
si se desea.
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;
}
}
Producto cartesiano en Java 8 con 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
Puede resolver esto usando la cadena recursiva
flatMap
.
Primero, ya que necesitamos movernos hacia adelante y hacia atrás por los valores del mapa, es mejor copiarlos a la
ArrayList
(esta no es la copia profunda, en su caso es solo
ArrayList
de 3 elementos, por lo que el uso de memoria adicional es bajo).
En segundo lugar, para mantener un prefijo de elementos visitados anteriormente,
Prefix
clase de
Prefix
inmutable auxiliar:
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;
}
}
Esta es una lista enlazada inmutable muy simple que se puede usar así:
List<String> list = new Prefix<>(new Prefix<>(new Prefix<>(null, "a"), "b"), "c")
.addTo(new ArrayList<>()); // [a, b, c];
A continuación, creemos el método interno que encadena 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));
}
Parece recurrencia, pero es más complejo: no se llama a sí mismo directamente, sino que pasa lambda que llama al método externo. Parámetros:
-
valores: la
List
de valores originales (new ArrayList<>(map.values)
en su caso). - offset: el desplazamiento actual dentro de esta lista
-
prefijo: el prefijo actual de desplazamiento de longitud (o
null
sioffset == 0
). Contiene elementos actualmente seleccionados de las coleccioneslist.get(0)
,list.get(1)
hastalist.get(offset-1)
. - proveedor: el método de fábrica para crear la colección resultante.
Cuando llegamos al final de la lista de valores (
offset == values.size() - 1
),
offset == values.size() - 1
los elementos de la última colección de los valores a la combinación final utilizando el proveedor.
De lo contrario, usamos el
flatMap
que para cada elemento intermedio amplía el prefijo y vuelve a llamar al método
comb
para el próximo desplazamiento.
Finalmente, aquí hay un método público para usar esta función:
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 ejemplo de uso:
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);
Recopilamos combinaciones individuales en
LinkedHashSet
nuevamente para preservar el pedido.
Puede utilizar cualquier otra colección en su lugar (por ejemplo,
ArrayList::new
).
Si bien no es una solución Stream, com.google.common.collect.Sets de Guava lo hace por usted
Set<List<String>> result = Sets.cartesianProduct(Set.of("a1","a2"), Set.of("b1","b2"), Set.of("c1","c2" ))
Una respuesta más simple, para una situación más simple en la que solo desea tener el producto cartesiano de los elementos de dos colecciones.
Aquí hay un código que usa
flatMap
para generar el producto cartesiano de dos listas cortas:
public static void main(String[] args) {
List<Integer> aList = Arrays.asList(1,2,3);
List<Integer> bList = Arrays.asList(4,5,6);
Stream<List<Integer>> product = aList.stream().flatMap(a ->
bList.stream().flatMap(b ->
Stream.of(Arrays.asList(a, b)))
);
product.forEach(p -> { System.out.println(p); });
// prints:
// [1, 4]
// [1, 5]
// [1, 6]
// [2, 4]
// [2, 5]
// [2, 6]
// [3, 4]
// [3, 5]
// [3, 6]
}
Si desea agregar más colecciones, simplemente anide las transmisiones un poco más:
aList.stream().flatMap(a ->
bList.stream().flatMap(b ->
cList.stream().flatMap(c ->
Stream.of(Arrays.asList(a, b, c))))
);
Una solución que opera principalmente en listas, haciendo las cosas mucho más simples.
Realiza una llamada recursiva en
flatMap
, realiza un seguimiento de los elementos que ya se han combinado y las colecciones de elementos que aún faltan, y ofrece los resultados de esta construcción recursiva anidada como una secuencia de listas:
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);
});
}
}
Use una clase de función de consumidor, una lista y 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);
}