2017-01-08 30 views
7

Jak mogę zaimplementować funkcję wykorzystującą Javę 8 do pobrania pewnej liczby strumieni i utworzyć strumień, w którym każdy element jest listą składającą się z jednego członka kartezjańskiego produktu strumieni?Strumień iloczynu kartezjańskiego z innych strumieni, każdy element jako lista?

Przyjrzałem się this question - to pytanie używa agregatora, który jest BinaryOperator (biorąc dwa przedmioty podobnego typu i produkując przedmiot tego samego typu). Chciałbym, aby elementy w wyniku końcowym były raczej typami elementów w strumieniach wejściowych.

Konkretnie, zakładając mój żądaną funkcję nazywa product, co następuje:

Stream<List<String>> result = 
    product(
     Stream.of("A", "B", "C", "D"), 
     Stream.of("I", "J", "K), 
     Stream.of("Y", "Z") 
    ); 

result.forEach(System.out::println); 

należy wydrukować:

[A, I, Y] 
[A, I, Z] 
[A, J, Y] 
[A, J, Z] 
[A, K, Y] 
[A, K, Z] 
[B, I, Y] 
... 
[D, K, Y] 
[D, K, Z] 

Idealnie, chciałbym za to operacja być tak leniwy, jak to możliwe. Na przykład, jeśli strumienie wejściowe są produkowane przez Stream.generate(), byłoby świetnie, gdyby dostawcy tych strumieni nie byli uruchamiani, dopóki nie będą absolutnie potrzebni.

+2

Czy to pomoże? http://stackoverflow.com/a/40202722/5629413 –

+5

Strumień może zostać zużyty tylko jeden raz, więc jeśli podasz 'product()' tylko trzy strumienie jednorazowego użytku, to będzie musiał przechowywać wszystkie wartości dwóch z nich, w takim przypadku możesz lepiej dać trzy obiekty "Iterable", aby zapobiec duplikowaniu danych wejściowych. – Andreas

+0

To dość skomplikowane. W JDK nie ma nic, co by to wesprzeć. Będziesz musiał napisać kod ... – Bohemian

Odpowiedz

1

Możliwym rozwiązaniem jest następujący:

private static <T> Stream<List<T>> product(Stream<T>... streams) { 
    if (streams.length == 0) { 
     return Stream.empty(); 
    } 
    List<List<T>> cartesian = streams[streams.length - 1].map(x -> Collections.singletonList(x)).collect(Collectors.toList()); 
    for (int i = streams.length - 2; i >= 0; i--) { 
     final List<List<T>> previous = cartesian; 
     cartesian = streams[i].flatMap(x -> previous.stream().map(p -> { 
      final List<T> list = new ArrayList<T>(p.size() + 1); 
      list.add(x); 
      list.addAll(p); 
      return list; 
     })).collect(Collectors.toList()); 
    } 
    return cartesian.stream(); 
} 

public static void main(String... args) { 
    final Stream<List<String>> result = 
      product(
        Stream.of("A", "B", "C", "D"), 
        Stream.of("I", "J", "K"), 
        Stream.of("Y", "Z") 
      ); 

    result.forEach(System.out::println); 
} 

Produkt dyżuru zwraca Stream<List<String>> wynik, który drukuje jak

[A, I, Y] 
[A, I, Z] 
[A, J, Y] 
[A, J, Z] 
[A, K, Y] 
[A, K, Z] 
[B, I, Y] 
[B, I, Z] 
[B, J, Y] 
[B, J, Z] 
[B, K, Y] 
[B, K, Z] 
[C, I, Y] 
[C, I, Z] 
[C, J, Y] 
[C, J, Z] 
[C, K, Y] 
[C, K, Z] 
[D, I, Y] 
[D, I, Z] 
[D, J, Y] 
[D, J, Z] 
[D, K, Y] 
[D, K, Z] 
+0

Moja odpowiedź zawierała propozycję, aby zwrócić listę bezpośrednio zamiast strumienia --- nie jestem ogólnie przeciwny tej modyfikacji, ponieważ prawdopodobnie jest lepiej. Musiałem jednak odrzucić to z dwóch powodów: (i) moja odpowiedź dokładnie odpowiada pytaniu, które zostało zadane, tj. Zwraca strumień, i (ii) proponowana odpowiedź nie została skompilowana - proszę wpisz swój kod w IDE i uruchom zanim uczciwie napiszesz! – marco