107

In JDK 8 con lambda b93 c'era una classe java.util.stream.Streams.zip in b93 che poteva essere utilizzata per i flussi zip (questo è illustrato nel tutorial Exploring Java8 Lambdas. Part 1 by Dhananjay Nene). Questa funzione:Zippare i flussi utilizzando JDK8 con lambda (java.util.stream.Streams.zip)

crea un flusso artificiale e combinata sequenziale i cui elementi sono il risultato di combinando gli elementi di due flussi.

Tuttavia in b98 questo è scomparso. Infatti la classe Streams non è nemmeno accessibile in java.util.stream in b98.

Questa funzionalità è stata spostata e, in caso affermativo, come faccio a comprimere i flussi in modo conciso utilizzando b98?

L'applicazione che ho in mente è in this java implementation of Shen, dove ho sostituito la funzionalità zip nella

  • static <T> boolean every(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred)
  • static <T> T find(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred)

funzioni con codice piuttosto dettagliato (che non utilizza funzionalità da b98).

+0

Ah appena scoperto che sembra essere stato rimosso completamente: http://mail.openjdk.java.net/pipermail/lambda-libs-spec-observers/2013-June/002029.html – artella

+0

possibile duplicato di [Java 8 java.util.stream.Streams] (http://stackoverflow.com/questions/16780647/java-8-java-util-stream-streams) – assylias

+0

"Esplorazione di Java8 Lambdas. Parte 1" - nuovo collegamento per questo articolo è http://blog.dhananjaynene.com/2013/02/exploring-java8-lambdas-part-1/ –

risposta

58

avevo bisogno di questo pure così ho preso il codice sorgente da B93 e metterlo in una classe "util". Ho dovuto modificarlo leggermente per lavorare con l'API corrente.

Per riferimento qui è il codice di lavoro (prendere a vostro rischio ...):

public static<A, B, C> Stream<C> zip(Stream<? extends A> a, 
            Stream<? extends B> b, 
            BiFunction<? super A, ? super B, ? extends C> zipper) { 
    Objects.requireNonNull(zipper); 
    Spliterator<? extends A> aSpliterator = Objects.requireNonNull(a).spliterator(); 
    Spliterator<? extends B> bSpliterator = Objects.requireNonNull(b).spliterator(); 

    // Zipping looses DISTINCT and SORTED characteristics 
    int characteristics = aSpliterator.characteristics() & bSpliterator.characteristics() & 
      ~(Spliterator.DISTINCT | Spliterator.SORTED); 

    long zipSize = ((characteristics & Spliterator.SIZED) != 0) 
      ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown()) 
      : -1; 

    Iterator<A> aIterator = Spliterators.iterator(aSpliterator); 
    Iterator<B> bIterator = Spliterators.iterator(bSpliterator); 
    Iterator<C> cIterator = new Iterator<C>() { 
     @Override 
     public boolean hasNext() { 
      return aIterator.hasNext() && bIterator.hasNext(); 
     } 

     @Override 
     public C next() { 
      return zipper.apply(aIterator.next(), bIterator.next()); 
     } 
    }; 

    Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics); 
    return (a.isParallel() || b.isParallel()) 
      ? StreamSupport.stream(split, true) 
      : StreamSupport.stream(split, false); 
} 
+1

Il flusso risultante non dovrebbe essere "SIZED" se _either_ stream è 'SIZED', non entrambi? –

+4

Io non la penso così. Entrambi gli stream devono essere 'SIZED' perché questa implementazione funzioni. In realtà dipende da come si definisce lo zippamento. Dovresti essere in grado di comprimere due stream di dimensioni diverse, ad esempio? Come sarebbe il flusso risultante allora? Credo che questo sia il motivo per cui questa funzione è stata effettivamente omessa dall'API. Ci sono molti modi per farlo e spetta all'utente decidere quale comportamento dovrebbe essere quello "corretto". Vuoi eliminare gli elementi dal flusso più lungo o riempire l'elenco più breve?Se sì, con quale valore? – siki

+0

A meno che non mi manchi qualcosa, non è necessario alcun cast (ad esempio, 'Spliterator '). – Jubobs

6

La libreria Lazy-Seq fornisce funzionalità zip.

https://github.com/nurkiewicz/LazySeq

Questa libreria è fortemente ispirato scala.collection.immutable.Stream e mira a fornire, implementazione sequenza immutabile thread-safe e facile da usare pigri, forse infinita.

9

I metodi della classe menzionata sono stati spostati nell'interfaccia Stream in favore dei metodi predefiniti. Ma sembra che il metodo zip sia stato rimosso. Forse perché non è chiaro quale dovrebbe essere il comportamento predefinito per i flussi di dimensioni diverse. Ma l'attuazione del comportamento desiderato è straight-forward:

static <T> boolean every(
    Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred) { 
    Iterator<T> it=c2.iterator(); 
    return c1.stream().allMatch(x->!it.hasNext()||pred.test(x, it.next())); 
} 
static <T> T find(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred) { 
    Iterator<T> it=c2.iterator(); 
    return c1.stream().filter(x->it.hasNext()&&pred.test(x, it.next())) 
     .findFirst().orElse(null); 
} 
+0

Il predicato non è passato al filtro * stateful *? Ciò viola il contratto del metodo e in particolare non funzionerà durante l'elaborazione del flusso in parallelo. – Andreas

+1

@Andreas: nessuna soluzione qui supporta l'elaborazione parallela. Poiché i miei metodi non restituiscono un flusso, si assicurano che i flussi non vengano eseguiti in parallelo. Allo stesso modo, il codice della risposta accettata restituisce un flusso che può essere trasformato in parallelo ma in realtà non farà nulla in parallelo. Detto questo, i predicati affermati sono scoraggiati ma non violano il contratto. Potrebbero essere utilizzati anche in un contesto parallelo se si assicura che l'aggiornamento dello stato sia thread-safe. In alcune situazioni sono inevitabili, ad es. trasformare un flusso in distinto è un predicato affermativo * di per sé *. – Holger

+2

@Andreas: puoi indovinare perché queste operazioni sono state rimosse dall'API Java ... – Holger

37

zip è una delle funzioni fornite dal protonpack library.

Stream<String> streamA = Stream.of("A", "B", "C"); 
Stream<String> streamB = Stream.of("Apple", "Banana", "Carrot", "Doughnut"); 

List<String> zipped = StreamUtils.zip(streamA, 
             streamB, 
             (a, b) -> a + " is for " + b) 
           .collect(Collectors.toList()); 

assertThat(zipped, 
      contains("A is for Apple", "B is for Banana", "C is for Carrot")); 
+0

trovato anche in StreamEx: http://amaembo.github.io/streamex/javadoc/one/util/streamex/StreamEx.html#zip-java.util.List-java.util.List-java.util.function. BiFunction- – tokland

1
public class Tuple<S,T> { 
    private final S object1; 
    private final T object2; 

    public Tuple(S object1, T object2) { 
     this.object1 = object1; 
     this.object2 = object2; 
    } 

    public S getObject1() { 
     return object1; 
    } 

    public T getObject2() { 
     return object2; 
    } 
} 


public class StreamUtils { 

    private StreamUtils() { 
    } 

    public static <T> Stream<Tuple<Integer,T>> zipWithIndex(Stream<T> stream) { 
     Stream<Integer> integerStream = IntStream.range(0, Integer.MAX_VALUE).boxed(); 
     Iterator<Integer> integerIterator = integerStream.iterator(); 
     return stream.map(x -> new Tuple<>(integerIterator.next(), x)); 
    } 
} 
19

Zipping due flussi usando JDK8 con lambda (gist).

public static <A, B, C> Stream<C> zip(Stream<A> streamA, Stream<B> streamB, BiFunction<A, B, C> zipper) { 
    final Iterator<A> iteratorA = streamA.iterator(); 
    final Iterator<B> iteratorB = streamB.iterator(); 
    final Iterator<C> iteratorC = new Iterator<C>() { 
     @Override 
     public boolean hasNext() { 
      return iteratorA.hasNext() && iteratorB.hasNext(); 
     } 

     @Override 
     public C next() { 
      return zipper.apply(iteratorA.next(), iteratorB.next()); 
     } 
    }; 
    final boolean parallel = streamA.isParallel() || streamB.isParallel(); 
    return iteratorToFiniteStream(iteratorC, parallel); 
} 

public static <T> Stream<T> iteratorToFiniteStream(Iterator<T> iterator, boolean parallel) { 
    final Iterable<T> iterable =() -> iterator; 
    return StreamSupport.stream(iterable.spliterator(), parallel); 
} 
+1

Soluzione piacevole e (relativamente) compatta! Richiede di inserire 'import java.util.function. *;' E 'import java.util.stream. *;' Nella parte superiore del file. – sffc

+0

Si noti che questa è un'operazione terminale sullo stream. Ciò significa che per flussi infiniti, questo metodo si rompe – smac89

1

AOL cyclops-react, a cui contribuisco, fornisce anche zippare funzionalità, sia tramite un extended Stream implementation, che implementa anche l'reattivi-stream interfaccia ReactiveSeq, e via StreamUtils che offre molte delle stesse funzionalità attraverso metodi statici a norma Stream Java.

List<Tuple2<Integer,Integer>> list = ReactiveSeq.of(1,2,3,4,5,6) 
                .zip(Stream.of(100,200,300,400)); 


    List<Tuple2<Integer,Integer>> list = StreamUtils.zip(Stream.of(1,2,3,4,5,6), 
                Stream.of(100,200,300,400)); 

offre anche più generalizzata zippare base applicativo. Per esempio.

ReactiveSeq.of("a","b","c") 
       .ap3(this::concat) 
       .ap(of("1","2","3")) 
       .ap(of(".","?","!")) 
       .toList(); 

    //List("a1.","b2?","c3!"); 

    private String concat(String a, String b, String c){ 
    return a+b+c; 
    } 

E anche la possibilità di abbinare ogni elemento in un unico flusso con ogni elemento in un altro

ReactiveSeq.of("a","b","c") 
       .forEach2(str->Stream.of(str+"!","2"), a->b->a+"_"+b); 

    //ReactiveSeq("a_a!","a_2","b_b!","b_2","c_c!","c2") 
0

Questo è grande. Ho dovuto zip due flussi in una mappa con un flusso di essere la chiave e altro è il valore

Stream<String> streamA = Stream.of("A", "B", "C"); 
Stream<String> streamB = Stream.of("Apple", "Banana", "Carrot", "Doughnut");  
final Stream<Map.Entry<String, String>> s = StreamUtils.zip(streamA, 
        streamB, 
        (a, b) -> { 
         final Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<String, String>(a, b); 
         return entry; 
        }); 

System.out.println(s.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()))); 

uscita: {A = Apple, B = Banana, C = Carota}

7

Dal momento che posso 't concepire qualsiasi utilizzo di zippare su collezioni diverse da quelle indicizzate (liste) e io sono un grande fan di semplicità, questa sarebbe la mia soluzione:

<A,B,C> Stream<C> zipped(List<A> lista, List<B> listb, BiFunction<A,B,C> zipper){ 
    int shortestLength = Math.min(lista.size(),listb.size()); 
    return IntStream.range(0,shortestLength).mapToObject(i -> { 
      return zipper.apply(lista.get(i), listb.get(i)); 
    });   
} 
12

Se si dispone di Guava nel progetto, è possibile utilizzare il Metodo Streams.zip (è stato aggiunto in Guava 21):

Restituisce un flusso in cui ogni elemento è il risultato del passaggio dell'elemento corrispondente di ciascuno di streamA e streamB per funzionare. Il flusso risultante sarà lungo solo quanto il più breve dei due flussi di input; se uno stream è più lungo, i suoi elementi extra verranno ignorati. Il flusso risultante non è divisibile in modo efficiente. Ciò potrebbe danneggiare le prestazioni parallele.

public class Streams { 
    ... 

    public static <A, B, R> Stream<R> zip(Stream<A> streamA, 
      Stream<B> streamB, BiFunction<? super A, ? super B, R> function) { 
     ... 
    } 
} 
0

umilmente suggerisco questa implementazione. Il flusso risultante viene troncato al più breve dei due flussi di input.

public static <L, R, T> Stream<T> zip(Stream<L> leftStream, Stream<R> rightStream, BiFunction<L, R, T> combiner) { 
    Spliterator<L> lefts = leftStream.spliterator(); 
    Spliterator<R> rights = rightStream.spliterator(); 
    return StreamSupport.stream(new AbstractSpliterator<T>(Long.min(lefts.estimateSize(), rights.estimateSize()), lefts.characteristics() & rights.characteristics()) { 
     @Override 
     public boolean tryAdvance(Consumer<? super T> action) { 
      return lefts.tryAdvance(left->rights.tryAdvance(right->action.accept(combiner.apply(left, right)))); 
     } 
    }, leftStream.isParallel() || rightStream.isParallel()); 
}