Zbieraj kolejne pary ze strumienia


103

Biorąc pod uwagę strumień, taki jak { 0, 1, 2, 3, 4 },

jak mogę najbardziej elegancko nadać mu daną formę:

{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }

(zakładając oczywiście, że zdefiniowałem parę klas)?

Edycja: nie dotyczy to wyłącznie ints ani strumieni pierwotnych. Odpowiedź powinna być ogólna dla dowolnego typu strumienia.


2
Termin z FP to „partycja”, ale nie znajduję metody z pożądaną semantyką w Javie. Ma partycjonowanie na predykacie.
Marko Topolnik

1
Zwykle rozdzielacz w JDK 8 jest uważany za przechodzenie i partycjonowanie. Postaram się również podać przykład.
Olimpiu POP

list.stream().map(i -> new Pair(i, i+1));
aepurniet

2
Odpowiednie pytanie niezwiązane z strumieniami można znaleźć na stronie stackoverflow.com/questions/17453022/…
Raedwald

Nawiasem mówiąc, niektórzy ludzie używają jednej z implementacji Map.Entryklasy pary. (To prawda, niektórzy mogą uważać, że hack, ale użycie wbudowanej klasy jest przydatne).
Basil Bourque

Odpowiedzi:


33

Moja biblioteka StreamEx, która rozszerza standardowe strumienie, zapewnia pairMapmetodę dla wszystkich typów strumieni. W przypadku strumieni pierwotnych nie zmienia typu strumienia, ale może służyć do wykonywania pewnych obliczeń. Najczęstszym zastosowaniem jest obliczanie różnic:

int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();

Dla strumienia obiektów można utworzyć dowolny inny typ obiektu. Moja biblioteka nie dostarcza żadnych nowych struktur danych widocznych dla użytkownika, takich jak Pair(to jest część koncepcji biblioteki). Jeśli jednak masz własną Pairklasę i chcesz z niej korzystać, możesz wykonać następujące czynności:

Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);

Lub jeśli już masz Stream:

Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);

Ta funkcja jest realizowana przy użyciu niestandardowego rozdzielacza . Ma dość niski narzut i może ładnie pracować równolegle. Oczywiście działa z każdym źródłem strumienia, a nie tylko z listami / tablicami o swobodnym dostępie, jak wiele innych rozwiązań. W wielu testach spisuje się naprawdę dobrze. Oto test porównawczy JMH, w którym wszystkie wartości wejściowe poprzedzające większą wartość przy użyciu różnych podejść (zobacz to pytanie).


Dziękuję Ci! Im więcej uczę się tej biblioteki, tym bardziej ją kocham. Mogę wreszcie zacząć używać strumieni. ( StreamExrealizuje Iterable! Hurra!)
Aleksandr Dubinsky

Aby Twoja odpowiedź była w 100% kompletna, czy możesz pokazać, jak zawinąć a Streamw StreamEx?
Aleksandr Dubinsky

3
@AleksandrDubinsky: po prostu użyj StreamEx.of(stream). Istnieją inne wygodne statyczne metody tworzenia strumienia Collection, tablic Readeritp. Edytowano odpowiedź.
Tagir Valeev

@TagirValeev jest pairMapzamawiany w sekwencyjnych strumieniach? Właściwie chciałbym mieć forPairsOrdered (), ale skoro nie ma takiej metody, czy mogę to jakoś zasymulować? stream.ordered().forPairs()czy stream().pairMap().forEachOrdered()?
Askar Kalykov

1
@AskarKalykov, pairMapjest to operacja pośrednia z niezakłócającą bezstanową funkcją mapowania, kolejność nie jest określona dla niej w taki sam sposób, jak dla prostego map. Specyfikacja nie forPairsjest uporządkowana, ale operacje nieuporządkowane są de facto uporządkowane dla strumieni sekwencyjnych. Byłoby miło, gdybyś sformułował swój pierwotny problem jako oddzielne pytanie dotyczące przepływu stosu, aby zapewnić więcej kontekstu.
Tagir Valeev,

74

Biblioteka strumieni Java 8 jest głównie nastawiona na dzielenie strumieni na mniejsze fragmenty w celu przetwarzania równoległego, więc etapy stanowe potoku są dość ograniczone, a wykonywanie takich czynności, jak uzyskiwanie indeksu bieżącego elementu strumienia i uzyskiwanie dostępu do sąsiednich elementów strumienia, nie jest obsługiwane.

Typowym sposobem rozwiązania tych problemów, oczywiście z pewnymi ograniczeniami, jest kierowanie strumienia za pomocą indeksów i poleganie na przetwarzaniu wartości w jakiejś strukturze danych o swobodnym dostępie, takiej jak ArrayList, z której można pobrać elementy. Gdyby wartości były w arrayList, można by wygenerować pary zgodnie z żądaniem, wykonując coś takiego:

    IntStream.range(1, arrayList.size())
             .mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
             .forEach(System.out::println);

Oczywiście ograniczeniem jest to, że wejście nie może być nieskończonym strumieniem. Ten potok może jednak przebiegać równolegle.


5
„Dane wejściowe nie mogą być nieskończonym strumieniem”. W rzeczywistości wejście nie może być w ogóle strumieniem. Input ( arrayList) jest w rzeczywistości zbiorem, dlatego nie oznaczyłem go jako odpowiedź. (Ale gratulacje dla twojej złotej odznaki!)
Aleksandr Dubinsky

16

To nie jest eleganckie, to hakerskie rozwiązanie, ale działa dla nieskończonych strumieni

Stream<Pair> pairStream = Stream.iterate(0, (i) -> i + 1).map( // natural numbers
    new Function<Integer, Pair>() {
        Integer previous;

        @Override
        public Pair apply(Integer integer) {
            Pair pair = null;
            if (previous != null) pair = new Pair(previous, integer);
            previous = integer;
            return pair;
        }
    }).skip(1); // drop first null

Teraz możesz ograniczyć swój strumień do żądanej długości

pairStream.limit(1_000_000).forEach(i -> System.out.println(i));

PS Mam nadzieję, że jest lepsze rozwiązanie, coś w rodzaju clojure(partition 2 1 stream)


6
Uznanie za wskazanie, że klasy anonimowe są czasami przydatną alternatywą dla lambd.
Aleksandr Dubinsky

2
@aepurniet Zakładam, że nie będzie działać poprawnie. Według parallelStreamdokumentu: „Aby zachować prawidłowe zachowanie, te parametry behawioralne muszą być niezakłócające, aw większości przypadków muszą być bezpaństwowe”
mishadoff,

14
Jest to całkowicie sprzeczne z projektem struktury strumieni i bezpośrednio narusza kontrakt interfejsu API mapy, ponieważ funkcja anonimowa nie jest bezstanowa. Spróbuj uruchomić to z równoległym strumieniem i większą ilością danych, aby struktura strumienia utworzyła więcej działających wątków, a zobaczysz wynik: rzadkie losowe „błędy” prawie niemożliwe do odtworzenia i trudne do wykrycia, dopóki nie masz wystarczającej ilości danych (w produkcji?). To może być katastrofalne.
Mario Rossi,

4
@AleksandrDubinsky Mylisz się co do możliwości równoległości ograniczenia / pominięcia; implementacja dostarczona w JDK faktycznie działa równolegle. Ponieważ operacja jest powiązana z kolejnością, zrównoleglenie może nie zawsze zapewniać korzyści w zakresie wydajności, ale w sytuacjach o wysokiej Q może.
Brian Goetz,

4
@AleksandrDubinsky Incorrect. To może pominąć element losowy, czy strumień jest nieuporządkowana (nie ma w określonej kolejności spotkanie, więc logicznie nie jest żaden „pierwszy” lub „n-ty” Element, elementy tylko). Ale czy strumień jest uporządkowane lub nieuporządkowane, pomiń zawsze był w stanie pracować równolegle. Jest tylko mniej równoległości do wyodrębnienia, jeśli strumień jest uporządkowany, ale nadal jest równoległy.
Brian Goetz,

15

Zaimplementowałem opakowanie spliteratora, które pobiera wszystkie nelementy Tz oryginalnego spliteratora i produkuje List<T>:

public class ConsecutiveSpliterator<T> implements Spliterator<List<T>> {

    private final Spliterator<T> wrappedSpliterator;

    private final int n;

    private final Deque<T> deque;

    private final Consumer<T> dequeConsumer;

    public ConsecutiveSpliterator(Spliterator<T> wrappedSpliterator, int n) {
        this.wrappedSpliterator = wrappedSpliterator;
        this.n = n;
        this.deque = new ArrayDeque<>();
        this.dequeConsumer = deque::addLast;
    }

    @Override
    public boolean tryAdvance(Consumer<? super List<T>> action) {
        deque.pollFirst();
        fillDeque();
        if (deque.size() == n) {
            List<T> list = new ArrayList<>(deque);
            action.accept(list);
            return true;
        } else {
            return false;
        }
    }

    private void fillDeque() {
        while (deque.size() < n && wrappedSpliterator.tryAdvance(dequeConsumer))
            ;
    }

    @Override
    public Spliterator<List<T>> trySplit() {
        return null;
    }

    @Override
    public long estimateSize() {
        return wrappedSpliterator.estimateSize();
    }

    @Override
    public int characteristics() {
        return wrappedSpliterator.characteristics();
    }
}

Do stworzenia kolejnego strumienia można użyć następującej metody:

public <E> Stream<List<E>> consecutiveStream(Stream<E> stream, int n) {
    Spliterator<E> spliterator = stream.spliterator();
    Spliterator<List<E>> wrapper = new ConsecutiveSpliterator<>(spliterator, n);
    return StreamSupport.stream(wrapper, false);
}

Przykładowe użycie:

consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
    .map(list -> new Pair(list.get(0), list.get(1)))
    .forEach(System.out::println);

Czy to powtarza każdy element dwa razy?
Aleksandr Dubinsky

Nie. Tworzy nowy strumień zawierający List<E>elementy. Każda lista zawiera nkolejne elementy z oryginalnego strumienia. Sprawdź sam;)
Tomek Rękawek

Czy możesz zmodyfikować swoją odpowiedź, tak aby każdy element (z wyjątkiem pierwszego i ostatniego) był powtarzany?
Aleksandr Dubinsky

4
+1 Myślę, że to dobra praca i powinna być uogólniona na dowolny rozmiar kroku oprócz rozmiaru partycji. Istnieje duże zapotrzebowanie na (partition size step)funkcję i jest to najlepszy sposób na jej uzyskanie.
Marko Topolnik

3
Rozważ użycie ArrayDequedla wydajności, zamiast LinkedList.
Marko Topolnik

14

Możesz to zrobić za pomocą metody Stream.reduce () (nie widziałem żadnych innych odpowiedzi wykorzystujących tę technikę).

public static <T> List<Pair<T, T>> consecutive(List<T> list) {
    List<Pair<T, T>> pairs = new LinkedList<>();
    list.stream().reduce((a, b) -> {
        pairs.add(new Pair<>(a, b));
        return b;
    });
    return pairs;
}

1
Zwróciłoby (1,2) (2,3) zamiast (1,2) (3,4). Nie jestem też pewien, czy zostałby zastosowany w kolejności (na pewno nie ma na to gwarancji).
Aleksandr Dubinsky

1
Proszę sprawdzić pytanie, czyli zamierzone zachowanie @Aleksandr Dubinsky
SamTebbs33

4
Ach, tak, przepraszam. I pomyśleć, napisałem to.
Aleksandr Dubinsky


6

Możesz to zrobić w Cyclops-React (współtworzę tę bibliotekę), używając przesuwanego operatora.

  LazyFutureStream.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

Lub

   ReactiveSeq.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

Zakładając, że konstruktor Pair może zaakceptować Collection z 2 elementami.

Jeśli chcesz pogrupować o 4 i zwiększyć o 2, to również jest obsługiwane.

     ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
                .sliding(4,2)
                .forEach(System.out::println);

Równoważne metody statyczne do tworzenia przesuwanego widoku w java.util.stream.Stream są również dostępne w klasie StreamUtils cyclops-streams .

       StreamUtils.sliding(Stream.of(1,2,3,4),2)
                  .map(Pair::new);

Uwaga: - dla operacji jednowątkowych bardziej odpowiednie byłoby ReactiveSeq. LazyFutureStream rozszerza ReactiveSeq, ale jest przede wszystkim nastawiony na jednoczesne / równoległe użycie (jest to strumień kontraktów futures).

LazyFutureStream rozszerza ReactiveSeq, który rozszerza Seq z niesamowitego jOOλ (który rozszerza java.util.stream.Stream), więc rozwiązania prezentowane przez Lukasa działałyby również z każdym typem Stream. Dla każdego zainteresowanego podstawowymi różnicami między operatorami okien / przesuwnych są oczywisty względny kompromis między mocą a złożonością oraz przydatność do użycia z nieskończonymi strumieniami (przesuwanie nie zużywa strumienia, ale buforuje podczas jego przepływu).


W ten sposób otrzymujesz [(0,1) (2,3) ...], ale pytanie zadaje [(0,1) (1,2) ...]. Proszę zobaczyć moją odpowiedź z RxJava ...
frhack

1
Masz rację, moja wina, źle odczytałem pytanie - napęd przesuwny jest tutaj właściwy. Zaktualizuję odpowiedź - dzięki!
John McClean,

4

Biblioteka proton-pack zapewnia okienkiem functionnality. Biorąc pod uwagę klasę pary i strumień, możesz to zrobić w następujący sposób:

Stream<Integer> st = Stream.iterate(0 , x -> x + 1);
Stream<Pair<Integer, Integer>> pairs = StreamUtils.windowed(st, 2, 1)
                                                  .map(l -> new Pair<>(l.get(0), l.get(1)))
                                                  .moreStreamOps(...);

Teraz pairsstrumień zawiera:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on

Wygląda jednak na to, że musisz tworzyć stdwa razy! Czy ta biblioteka może rozwiązać problem za pomocą jednego strumienia?
Aleksandr Dubinsky

@AleksandrDubinsky Myślę, że nie jest to dostępne z obecnymi spliteratorami. Zgłosiłem
Alexis C.

@AleksandrDubinsky Dodano windowedfunkcjonalność! Zobacz edycję.
Alexis C.

1
Dlaczego nie usuniesz starej odpowiedzi, aby inni użytkownicy mogli zobaczyć rozwiązanie, a nie historię.
Aleksandr Dubinsky

4

Znajdowanie kolejnych par

Jeśli chcesz skorzystać z biblioteki innej firmy i nie potrzebujesz równoległości, jOOλ oferuje następujące funkcje okna w stylu SQL

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window()
   .filter(w -> w.lead().isPresent())
   .map(w -> tuple(w.value(), w.lead().get())) // alternatively, use your new Pair() class
   .toList()
);

Wydajność

[(0, 1), (1, 2), (2, 3), (3, 4)]

lead()Funkcja uzyskuje dostęp do kolejnej wartości w porządku przemierzania z okna.

Znajdowanie kolejnych potrójnych / poczwórnych / n-krotek

Pytanie w komentarzach dotyczyło bardziej ogólnego rozwiązania, w którym nie należy gromadzić par, ale n-krotek (lub ewentualnie list). Oto więc alternatywne podejście:

int n = 3;

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window(0, n - 1)
   .filter(w -> w.count() == n)
   .map(w -> w.window().toList())
   .toList()
);

Udostępnianie listy list

[[0, 1, 2], [1, 2, 3], [2, 3, 4]]

Bez tego filter(w -> w.count() == n)wynik byłby

[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4], [4]]

Zastrzeżenie: pracuję dla firmy stojącej za jOOλ


Ciekawy. A jeśli muszę zgrupować 3 lub więcej elementów? Użyj w.lead().lead()?
Raul Santelices,

1
@RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))byłaby opcją. Zaktualizowałem moją odpowiedź o bardziej ogólne rozwiązanie dlalength = n
Lukas Eder,

1
Dobrze rozumiem, że .window()nie jest to leniwa operacja, która zbiera cały strumień wejściowy do jakiejś kolekcji pośredniej, a następnie tworzy z niej nowy strumień?
Tagir Valeev,

@TagirValeev: Tak, to jest obecna implementacja. W powyższym przypadku (nie Comparatorsłuży do zmiany kolejności okien), a następnie optymalizacja jak to będzie możliwe, a to może być realizowane w przyszłości.
Lukas Eder,


2

Możemy użyć RxJava (bardzo potężna reaktywna biblioteka rozszerzeń )

IntStream intStream  = IntStream.iterate(1, n -> n + 1);

Observable<List<Integer>> pairObservable = Observable.from(intStream::iterator).buffer(2,1);

pairObservable.take(10).forEach(b -> {
            b.forEach(n -> System.out.println(n));
            System.out.println();
        });

Bufor operator zmienia zauważalnemu emitujący elementów do możliwego do zaobserwowania emitujący buforowane zbiory tych elementów ..


1
Observable.zip(obs, obs.skip(1), pair->{...})Do tej pory używałem ! Nie wiedziałem, Observable.bufferże mam wersję z krokiem (i jestem przyzwyczajony do zipsztuczki z Pythona). +1
Reut Sharabani

1

Operacja jest zasadniczo stanowa, więc nie do końca, jakie strumienie mają rozwiązać - zobacz sekcję „Zachowania bezstanowe” w javadoc :

Najlepszym podejściem jest unikanie stanowych parametrów behawioralnych w celu całkowitego przesyłania strumieniowego operacji

Jednym z rozwiązań jest wprowadzenie stanu do strumienia przez zewnętrzny licznik, chociaż będzie to działać tylko ze strumieniem sekwencyjnym.

public static void main(String[] args) {
    Stream<String> strings = Stream.of("a", "b", "c", "c");
    AtomicReference<String> previous = new AtomicReference<>();
    List<Pair> collect = strings.map(n -> {
                            String p = previous.getAndSet(n);
                            return p == null ? null : new Pair(p, n);
                        })
                        .filter(p -> p != null)
                        .collect(toList());
    System.out.println(collect);
}


static class Pair<T> {
    private T left, right;
    Pair(T left, T right) { this.left = left; this.right = right; }
    @Override public String toString() { return "{" + left + "," + right + '}'; }
}

Pytanie dotyczy zbierania kolejnych elementów strumienia wejściowego, a nie tylko zbierania kolejnych liczb całkowitych. Ważne wyjaśnienie terminologii Stream:! = "Lambdy".
Aleksandr Dubinsky

Możesz zastąpić AtomicInteger przez AtomicReference. Alternatywą jest zrolowanie własnego kolektora lub użycie bibliotek zewnętrznych, takich jak w tym przykładzie: stackoverflow.com/a/30090528/829571
assylias

Zobacz moją edycję. Nie jestem też pewien, czy rozumiem twój komentarz na temat lambda! = Stream. Druga odpowiedź, która używa klasy anonimowej, robi zasadniczo to samo, z wyjątkiem tego, że stan jest utrzymywany przez klasę anonimową zamiast być zewnętrznym ...
asylias

1
To działa. StreamExBiblioteka jest również dobre znaleźć i może być odpowiedź w sobie. Mój komentarz na temat „strumienie! = Lambdy” odnosi się do stwierdzenia: „Operacja jest zasadniczo stanowa, więc nie do końca, jakie lambdy mają rozwiązać”. Myślę, że chciałeś użyć słowa „strumienie”.
Aleksandr Dubinsky

Och, rozumiem - wyjaśniłem to.
assylias

0

W twoim przypadku napisałbym moją niestandardową funkcję IntFunction, która śledzi ostatni przekazany int i użyję jej do zmapowania oryginalnego IntStream.

import java.util.function.IntFunction;
import java.util.stream.IntStream;

public class PairFunction implements IntFunction<PairFunction.Pair> {

  public static class Pair {

    private final int first;
    private final int second;

    public Pair(int first, int second) {
      this.first = first;
      this.second = second;
    }

    @Override
    public String toString() {
      return "[" + first + "|" + second + "]";
    }
  }

  private int last;
  private boolean first = true;

  @Override
  public Pair apply(int value) {
    Pair pair = !first ? new Pair(last, value) : null;
    last = value;
    first = false;
    return pair;
  }

  public static void main(String[] args) {

    IntStream intStream = IntStream.of(0, 1, 2, 3, 4);
    final PairFunction pairFunction = new PairFunction();
    intStream.mapToObj(pairFunction)
        .filter(p -> p != null) // filter out the null
        .forEach(System.out::println); // display each Pair

  }

}

Problem polega na tym, że wyrzuca bezpaństwowość za okno.
Rob

@Rob i jaki jest z tym problem?
Aleksandr Dubinsky

Jednym z głównych punktów lambda jest brak mutowalnego stanu, aby wewnętrzne integratory mogły równolegle pracować.
Rob

@Rob: Tak, masz rację, ale podany przykładowy strumień i tak jest sprzeczny z paralelizmem, ponieważ każdy element (z wyjątkiem pierwszego i ostatniego) jest używany jako pierwszy i drugi element jakiejś pary.
jpvee

@jpvee yeah Doszedłem do wniosku, że o tym myślisz. Zastanawiam się jednak, czy nie ma sposobu, aby to zrobić z jakimś innym maperem. W zasadzie wszystko, czego potrzebowałbyś, byłoby równoważne zrobieniu z przyrostu pętli dwóch, a następnie funktorowi, aby pobierał 2 argumenty. To musi być możliwe.
Rob

0

Do obliczenia kolejnych różnic w czasie (wartości x) z szeregów czasowych, używać stream„S collect(...)sposób:

final List< Long > intervals = timeSeries.data().stream()
                    .map( TimeSeries.Datum::x )
                    .collect( DifferenceCollector::new, DifferenceCollector::accept, DifferenceCollector::combine )
                    .intervals();

Gdzie DifferenceCollector wygląda mniej więcej tak:

public class DifferenceCollector implements LongConsumer
{
    private final List< Long > intervals = new ArrayList<>();
    private Long lastTime;

    @Override
    public void accept( final long time )
    {
        if( Objects.isNull( lastTime ) )
        {
            lastTime = time;
        }
        else
        {
            intervals.add( time - lastTime );
            lastTime = time;
        }
    }

    public void combine( final DifferenceCollector other )
    {
        intervals.addAll( other.intervals );
        lastTime = other.lastTime;
    }

    public List< Long > intervals()
    {
        return intervals;
    }
}

Prawdopodobnie możesz to zmodyfikować, aby dopasować je do swoich potrzeb.


0

W końcu znalazłem sposób na oszukanie Stream.reduce, aby móc starannie radzić sobie z parami wartości; istnieje wiele przypadków użycia, które wymagają tej funkcji, która nie pojawia się naturalnie w JDK 8:

public static int ArithGeo(int[] arr) {
    //Geometric
    List<Integer> diffList = new ArrayList<>();
    List<Integer> divList = new ArrayList<>();
    Arrays.stream(arr).reduce((left, right) -> {
        diffList.add(right-left);
        divList.add(right/left);
        return right;
    });
    //Arithmetic
    if(diffList.stream().distinct().count() == 1) {
        return 1;
    }
    //Geometric
    if(divList.stream().distinct().count() == 1) {
        return 2;
    }
    return -1;
}

Sztuczka, której używam, to właściwy powrót; komunikat.


1
Myślę, że nie reducedaje wystarczających gwarancji, aby to zadziałało.
Aleksandr Dubinsky

Chciałbym dowiedzieć się więcej o wystarczających gwarancjach . Czy możesz to rozwinąć? Może w guawie jest alternatywa ... ale jestem ograniczony i nie mogę jej użyć.
Beezer

-1

Eleganckim rozwiązaniem byłoby użycie zamka błyskawicznego . Coś jak:

List<Integer> input = Arrays.asList(0, 1, 2, 3, 4);
Stream<Pair> pairStream = Streams.zip(input.stream(),
                                      input.stream().substream(1),
                                      (a, b) -> new Pair(a, b)
);

Jest to dość zwięzłe i eleganckie, jednak używa listy jako danych wejściowych. Nieskończone źródło strumienia nie może być przetwarzane w ten sposób.

Innym (dużo bardziej kłopotliwym) problemem jest to, że zip wraz z całą klasą Streams został ostatnio usunięty z API. Powyższy kod działa tylko z wersjami b95 lub starszymi. Więc w przypadku najnowszego JDK powiedziałbym, że nie ma eleganckiego rozwiązania w stylu FP i teraz możemy mieć tylko nadzieję, że w jakiś sposób zip zostanie ponownie wprowadzony do API.


Rzeczywiście, zipzostał usunięty. Nie pamiętam wszystkiego, co było w Streamsklasie, ale niektóre rzeczy zostały przeniesione do statycznych metod w Streaminterfejsie, są też StreamSupporti Stream.Builderklasy.
Stuart Marks

Zgadza się. Niektóre inne metody, takie jak concat lub iterate, zostały przeniesione i stały się metodami domyślnymi w usłudze Stream. Niestety zip został właśnie usunięty z API. Rozumiem powody tego wyboru (np. Brak krotek), ale nadal była to fajna funkcja.
gadżet

2
@gadget Z czym mają wspólnego krotki zip? Jakikolwiek pedantyczny powód zostałby wymyślony, nie usprawiedliwia zabijania zip.
Aleksandr Dubinsky,

@AleksandrDubinsky W większości przypadków zip jest używany do tworzenia kolekcji par / krotek jako danych wyjściowych. Oni twierdzili , że jeśli będą trzymane zip ludzie pytali o krotki jako część JDK jak również. Nigdy nie usunąłbym jednak istniejącej funkcji.
gadżet

-1

To ciekawy problem. Czy moja próba hybrydy poniżej jest dobra?

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3);
    Iterator<Integer> first = list.iterator();
    first.next();
    if (first.hasNext())
        list.stream()
        .skip(1)
        .map(v -> new Pair(first.next(), v))
        .forEach(System.out::println);
}

Uważam, że nie nadaje się do przetwarzania równoległego, a zatem może zostać zdyskwalifikowany.


Pytanie nie wymagało równoległego przetwarzania, ale zakładało, że mamy tylko a Stream, a nie List. Oczywiście możemy również pobrać iterator ze strumienia, więc może to być prawidłowe rozwiązanie. Niemniej jednak jest to oryginalne podejście.
Aleksandr Dubinsky

-1

Jak zauważyli inni, ze względu na naturę problemu wymagana jest pewna stanowość.

Miałem podobny problem, w którym chciałem, aby w istocie była to funkcja Oracle SQL LEAD. Moja próba wdrożenia tego znajduje się poniżej.

/**
 * Stream that pairs each element in the stream with the next subsequent element.
 * The final pair will have only the first item, the second will be null.
 */
<T> Spliterator<Pair<T>> lead(final Stream<T> stream)
{
    final Iterator<T> input = stream.sequential().iterator();

    final Iterable<Pair<T>> iterable = () ->
    {
        return new Iterator<Pair<T>>()
        {
            Optional<T> current = getOptionalNext(input);

            @Override
            public boolean hasNext()
            {
                return current.isPresent();
            }

            @Override
            public Pair<T> next()
            {
                Optional<T> next = getOptionalNext(input);
                final Pair<T> pair = next.isPresent()
                    ? new Pair(current.get(), next.get())
                    : new Pair(current.get(), null);
                current = next;

                return pair;
            }
        };
    };

    return iterable.spliterator();
}

private <T> Optional<T> getOptionalNext(final Iterator<T> iterator)
{
    return iterator.hasNext()
        ? Optional.of(iterator.next())
        : Optional.empty();
}

-1

Możesz to osiągnąć, używając ograniczonej kolejki do przechowywania elementów, które przepływają przez strumień (co opiera się na pomyśle, który szczegółowo opisałem tutaj: Czy można pobrać następny element w Stream? )

Poniższy przykład najpierw definiuje instancję klasy BoundedQueue, która będzie przechowywać elementy przechodzące przez strumień (jeśli nie podoba ci się pomysł rozszerzenia LinkedList, odwołaj się do wspomnianego powyżej linku, aby uzyskać alternatywne i bardziej ogólne podejście). Później wystarczy połączyć dwa kolejne elementy w instancję Pair:

public class TwoSubsequentElems {
  public static void main(String[] args) {
    List<Integer> input = new ArrayList<Integer>(asList(0, 1, 2, 3, 4));

    class BoundedQueue<T> extends LinkedList<T> {
      public BoundedQueue<T> save(T curElem) {
        if (size() == 2) { // we need to know only two subsequent elements
          pollLast(); // remove last to keep only requested number of elements
        }

        offerFirst(curElem);

        return this;
      }

      public T getPrevious() {
        return (size() < 2) ? null : getLast();
      }

      public T getCurrent() {
        return (size() == 0) ? null : getFirst();
      }
    }

    BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();

    final List<Pair<Integer>> answer = input.stream()
      .map(i -> streamHistory.save(i))
      .filter(e -> e.getPrevious() != null)
      .map(e -> new Pair<Integer>(e.getPrevious(), e.getCurrent()))
      .collect(Collectors.toList());

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

-3

Zgadzam się z @aepurniet, ale zamiast map musisz użyć mapToObj

range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);

1
Dobrze. Ale to po prostu zbiera pary liczb całkowitych, a nie pary elementów strumienia (dowolnego typu).
Aleksandr Dubinsky

-5

Uruchom forpętlę, która biegnie od 0 do length-1strumienia

for(int i = 0 ; i < stream.length-1 ; i++)
{
    Pair pair = new Pair(stream[i], stream[i+1]);
    // then add your pair to an array
}

3
a gdzie jest część lambda rozwiązania?
Olimpiu POP

Nie jest tak, gdy strumień jest nieskończony
mishadoff

@Olimpiu - Skąd masz wymaganie lambda? Przeczytałem pytanie dwukrotnie, aby upewnić się, że go nie przegapiłem. Sprawdziłem też historię zmian. A pytanie nie jest nim oznaczone.
jww
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.