Thu thập các cặp liên tiếp từ một luồng


102

Đưa ra một luồng chẳng hạn như { 0, 1, 2, 3, 4 },

làm cách nào để tôi có thể biến đổi nó thành dạng nhất định một cách trang nhã nhất:

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

(giả sử, tất nhiên, tôi đã xác định Cặp lớp)?

Chỉnh sửa: Đây không hoàn toàn là về int hoặc luồng nguyên thủy. Câu trả lời phải chung chung cho một luồng thuộc bất kỳ loại nào.


2
Thuật ngữ từ FP là "phân vùng", nhưng tôi không tìm thấy một phương pháp với ngữ nghĩa mong muốn trong Java. Nó có phân vùng trên một vị từ.
Marko Topolnik

1
Điển hình là trình phân tách trong JDK 8 được dùng cho các mục đích chuyển hướng và phân vùng. Tôi cũng sẽ cố gắng đưa ra một ví dụ.
Olimpiu POP

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

2
Đối với câu hỏi không phải luồng tương đương, hãy xem stackoverflow.com/questions/17453022/…
Raedwald

Nhân tiện, một số người sử dụng một trong hai việc triển khai Map.Entrynhư một lớp Cặp. (Cấp, một số có thể xem xét rằng một hack, nhưng sử dụng một built-in Class là tiện dụng.)
Basil Bourque

Câu trả lời:


33

Thư viện StreamEx của tôi mở rộng các luồng tiêu chuẩn cung cấp một pairMapphương pháp cho tất cả các loại luồng. Đối với các luồng nguyên thủy, nó không thay đổi kiểu luồng, nhưng có thể được sử dụng để thực hiện một số tính toán. Cách sử dụng phổ biến nhất là để tính toán sự khác biệt:

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

Đối với luồng đối tượng, bạn có thể tạo bất kỳ loại đối tượng nào khác. Thư viện của tôi không cung cấp bất kỳ cấu trúc dữ liệu mới nào mà người dùng có thể nhìn thấy như Pair(đó là một phần của khái niệm thư viện). Tuy nhiên, nếu bạn có Pairlớp học của riêng mình và muốn sử dụng nó, bạn có thể làm như sau:

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

Hoặc nếu bạn đã có một số Stream:

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

Chức năng này được triển khai bằng cách sử dụng trình phân tách tùy chỉnh . Nó có chi phí khá thấp và có thể song song một cách độc đáo. Tất nhiên nó hoạt động với bất kỳ nguồn luồng nào, không chỉ danh sách / mảng truy cập ngẫu nhiên như nhiều giải pháp khác. Trong nhiều thử nghiệm, nó hoạt động rất tốt. Đây là điểm chuẩn JMH nơi chúng tôi tìm thấy tất cả các giá trị đầu vào đứng trước giá trị lớn hơn bằng cách sử dụng các cách tiếp cận khác nhau (xem câu hỏi này ).


Cảm ơn bạn! Càng nghiên cứu thư viện này, tôi càng yêu thích nó. Cuối cùng thì tôi cũng có thể bắt đầu sử dụng luồng. ( StreamExthực hiện Iterable! Hurray!)
Aleksandr Dubinsky

Để làm cho câu trả lời của bạn hoàn thành 100%, bạn có thể chỉ ra cách viết một Streamthành một StreamEx?
Aleksandr Dubinsky

3
@AleksandrDubinsky: chỉ cần sử dụng StreamEx.of(stream). Có các phương thức tĩnh thuận tiện khác để tạo luồng từ Collection, mảng Reader, v.v. Đã chỉnh sửa câu trả lời.
Tagir Valeev

@TagirValeev được pairMapsắp xếp trên các luồng tuần tự? Trên thực tế, tôi muốn có forPairsOrdered (), nhưng vì không có phương thức nào như vậy, tôi có thể mô phỏng nó bằng cách nào đó không? stream.ordered().forPairs()hoặc stream().pairMap().forEachOrdered()?
Askar Kalykov

1
@AskarKalykov, pairMaplà hoạt động trung gian với chức năng ánh xạ không trạng thái không can thiệp, thứ tự không được chỉ định cho nó theo cách tương tự như đơn giản map. Không forPairscó thứ tự theo đặc điểm kỹ thuật, nhưng các hoạt động không có thứ tự được sắp xếp trên thực tế cho các luồng tuần tự. Sẽ rất tuyệt nếu bạn hình thành vấn đề ban đầu của mình dưới dạng câu hỏi stackoverflow riêng biệt để cung cấp thêm ngữ cảnh.
Tagir Valeev

74

Thư viện luồng Java 8 chủ yếu hướng đến việc chia các luồng thành các phần nhỏ hơn để xử lý song song, vì vậy các giai đoạn đường ống trạng thái khá hạn chế và những việc như lấy chỉ mục của phần tử luồng hiện tại và truy cập các phần tử luồng liền kề không được hỗ trợ.

Một cách điển hình để giải quyết những vấn đề này, tất nhiên, với một số hạn chế, là điều khiển luồng theo chỉ mục và dựa vào việc các giá trị được xử lý trong một số cấu trúc dữ liệu truy cập ngẫu nhiên như ArrayList mà từ đó các phần tử có thể được truy xuất. Nếu các giá trị ở trong arrayList, người ta có thể tạo các cặp theo yêu cầu bằng cách làm như sau:

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

Tất nhiên hạn chế là đầu vào không thể là một dòng vô hạn. Tuy nhiên, đường ống này có thể chạy song song.


5
"Đầu vào không thể là một luồng vô hạn." Trên thực tế, đầu vào không thể là một luồng. Đầu vào ( arrayList) trên thực tế là một tập hợp, đó là lý do tại sao tôi không đánh dấu nó là câu trả lời. (Nhưng chúc mừng bạn đã nhận được huy hiệu vàng!)
Aleksandr Dubinsky

16

Đây không phải là một giải pháp thanh lịch, đó là một giải pháp hack, nhưng hoạt động cho các luồng vô hạn

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

Giờ đây, bạn có thể giới hạn luồng của mình ở độ dài bạn muốn

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

Tái bút Tôi hy vọng có giải pháp tốt hơn, một cái gì đó giống như áo choàng(partition 2 1 stream)


6
Kudos vì đã chỉ ra rằng các lớp ẩn danh đôi khi là một sự thay thế hữu ích cho lambdas.
Aleksandr Dubinsky

2
@aepurniet Tôi cho rằng nó sẽ không hoạt động chính xác. Theo parallelStreamdoc: "Để duy trì hành vi đúng, các thông số về hành vi phải không can thiệp, và trong nhiều trường hợp phải được quốc tịch"
mishadoff

14
Điều này hoàn toàn trái với thiết kế của khung luồng và trực tiếp vi phạm hợp đồng của API bản đồ, vì chức năng ẩn danh không phảikhông trạng thái. Hãy thử chạy điều này với một luồng song song và nhiều dữ liệu hơn để khung công tác luồng tạo ra nhiều luồng hoạt động hơn và bạn sẽ thấy kết quả: "lỗi" ngẫu nhiên không thường xuyên gần như không thể tái tạo và khó phát hiện cho đến khi bạn có đủ dữ liệu (trong sản xuất?). Điều này có thể là thảm họa.
Mario Rossi

4
@AleksandrDubinsky Bạn không chính xác về giới hạn / bỏ qua có thể song song hóa; việc triển khai được cung cấp trong JDK trên thực tế hoạt động song song. Bởi vì hoạt động được gắn với thứ tự gặp phải, song song có thể không phải lúc nào cũng mang lại lợi ích về hiệu suất, nhưng trong các tình huống Q cao, nó có thể.
Brian Goetz

4
@AleksandrDubinsky Không chính xác. Nó có thể bỏ qua một yếu tố ngẫu nhiên nếu dòng là không có thứ tự (đã không được xác định để gặp gỡ, vì vậy một cách hợp lý có không "đầu tiên" hay "thứ n" yếu tố, chỉ cần yếu tố này.) Nhưng dù dòng được ra lệnh hoặc có thứ tự, bỏ qua luôn thể để làm việc song song. Chỉ có ít song song hơn để trích xuất nếu luồng được sắp xếp, nhưng nó vẫn song song.
Brian Goetz

15

Tôi đã triển khai một trình bao bọc của spliterator lấy mọi nphần tử Ttừ trình spliterator ban đầu và tạo ra 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();
    }
}

Phương pháp sau có thể được sử dụng để tạo một luồng liên tiếp:

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);
}

Sử dụng mẫu:

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

Điều đó có lặp lại mỗi phần tử hai lần không?
Aleksandr Dubinsky

Không. Nó tạo ra một luồng mới chứa List<E>các phần tử. Mỗi danh sách chứa ncác phần tử liên tiếp từ luồng gốc. Hãy tự mình kiểm tra;)
Tomek Rękawek

Bạn có thể sửa đổi câu trả lời của mình để mọi phần tử (trừ phần tử đầu tiên và cuối cùng) được lặp lại không?
Aleksandr Dubinsky

4
+1 Tôi nghĩ đây là công việc tốt và nên được tổng quát hóa thành bất kỳ kích thước bước nào ngoài kích thước phân vùng. Có rất nhiều nhu cầu cho một (partition size step)chức năng và đây là cách tốt nhất để có được nó.
Marko Topolnik

3
Cân nhắc sử dụng ArrayDequecho hiệu suất, ưu tiên LinkedList.
Marko Topolnik

14

Bạn có thể thực hiện điều này với phương thức Stream.reduce () (Tôi chưa thấy bất kỳ câu trả lời nào khác sử dụng kỹ thuật này).

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
Nó sẽ trả về (1,2) (2,3) thay vì (1,2) (3,4). Ngoài ra, tôi không chắc liệu nó có được áp dụng theo thứ tự hay không (chắc chắn không có gì đảm bảo điều đó).
Aleksandr Dubinsky

1
Vui lòng kiểm tra các câu hỏi, đó là hành vi có ý định @Aleksandr Dubinsky
SamTebbs33

3
Ahh, vâng, xin lỗi. Và để suy nghĩ, tôi đã viết nó.
Aleksandr Dubinsky


6

Bạn có thể làm điều này trong cyclops-react (tôi đóng góp vào thư viện này), sử dụng toán tử trượt.

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

Hoặc là

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

Giả sử hàm tạo Pair có thể chấp nhận một Bộ sưu tập có 2 phần tử.

Nếu bạn muốn nhóm 4 và tăng lên 2 cũng được hỗ trợ.

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

Các phương thức tĩnh tương đương để tạo dạng xem trượt qua java.util.stream.Stream cũng được cung cấp trong lớp StreamUtils của cyclops-stream .

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

Lưu ý: - đối với hoạt động đơn luồng, ReactiveSeq sẽ thích hợp hơn. LazyFutureStream mở rộng ReactiveSeq nhưng chủ yếu hướng đến việc sử dụng đồng thời / song song (nó là một Dòng tương lai).

LazyFutureStream mở rộng ReactiveSeq, mở rộng Seq từ jOOλ tuyệt vời (mở rộng java.util.stream.Stream), vì vậy các giải pháp mà Lukas trình bày cũng sẽ hoạt động với cả hai loại Stream. Đối với bất kỳ ai quan tâm, sự khác biệt chính giữa các toán tử cửa sổ / trượt là sự cân bằng sức mạnh / độ phức tạp tương đối rõ ràng và sự phù hợp để sử dụng với các luồng vô hạn (trượt không tiêu thụ luồng, nhưng bộ đệm khi nó chảy).


Bằng cách này, bạn nhận được [(0,1) (2,3) ...] nhưng câu hỏi yêu cầu [(0,1) (1,2) ...]. Hãy xem câu trả lời của tôi với RxJava ...
frhack

1
Bạn nói đúng, tôi nói sai, tôi đã đọc sai câu hỏi - toán tử trượt là toán tử chính xác để sử dụng ở đây. Tôi sẽ cập nhật câu trả lời của tôi - cảm ơn!
John McClean

4

Các thư viện proton-pack cung cấp functionnality cửa sổ. Với một lớp Ghép nối và một Luồng, bạn có thể thực hiện như sau:

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(...);

Bây giờ pairsluồng chứa:

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

Tuy nhiên, có vẻ như bạn cần tạo sthai lần! Thư viện này có thể giải quyết vấn đề bằng cách sử dụng một luồng không?
Aleksandr Dubinsky

@AleksandrDubinsky Tôi không nghĩ là nó có sẵn với các trình phân tách hiện tại. Tôi đã gửi vấn đề github.com/poetix/protonpack/issues/9
Alexis C.

@AleksandrDubinsky windowedChức năng đã được thêm vào! Xem bản chỉnh sửa.
Alexis C.

1
Tại sao bạn không xóa câu trả lời cũ của mình để những người dùng khác có thể xem giải pháp chứ không phải lịch sử.
Aleksandr Dubinsky

4

Tìm các cặp liên tiếp

Nếu bạn sẵn sàng sử dụng thư viện bên thứ ba và không cần song song, thì jOOλ cung cấp các chức năng cửa sổ kiểu SQL như sau

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()
);

Năng suất

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

Các lead()chức năng truy cập các giá trị tiếp theo để traversal từ cửa sổ.

Tìm bộ ba / bộ bốn / n-tuples kế tiếp

Một câu hỏi trong các bình luận yêu cầu một giải pháp chung hơn, trong đó không phải các cặp mà là n bộ (hoặc có thể là danh sách) nên được thu thập. Do đó, đây là một cách tiếp cận thay thế:

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()
);

Nhường một danh sách

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

Nếu không có filter(w -> w.count() == n), kết quả sẽ là

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

Tuyên bố từ chối trách nhiệm: Tôi làm việc cho công ty đằng sau jOOλ


Hấp dẫn. Điều gì xảy ra nếu tôi cần nhóm 3 phần tử trở lên? Sử dụng w.lead().lead()?
Raul Santelices

1
@RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))sẽ là một tùy chọn. Tôi đã cập nhật câu trả lời của tôi với một giải pháp chung chung hơn cholength = n
Lukas Eder

1
Tôi hiểu chính xác đó .window()không phải là thao tác lười biếng thu thập toàn bộ luồng đầu vào vào một số tập hợp trung gian, sau đó tạo một luồng mới từ đó?
Tagir Valeev

@TagirValeev: Vâng, đó là cách triển khai hiện tại. Trong trường hợp trên (không Comparatorđược sử dụng để sắp xếp lại các cửa sổ), thì việc tối ưu hóa như thế này sẽ có thể thực hiện được và có khả năng được thực hiện trong tương lai.
Lukas Eder


2

Chúng ta có thể sử dụng RxJava ( thư viện tiện ích mở rộng phản ứng rất mạnh mẽ )

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();
        });

Các bộ đệm điều hành biến đổi một Quan sát mà phát ra các mục vào một Quan sát mà phát ra đệm bộ sưu tập của những mặt hàng ..


1
Tôi đã sử dụng Observable.zip(obs, obs.skip(1), pair->{...})cho đến bây giờ! Tôi không biết Observable.buffercó một phiên bản có một bước (và tôi đã quen với zipthủ thuật từ python). +1
Reut Sharabani

1

Hoạt động về cơ bản là trạng thái nên không thực sự là những gì các luồng có nghĩa là để giải quyết - hãy xem phần "Hành vi không trạng thái" trong javadoc :

Cách tiếp cận tốt nhất là tránh các tham số hành vi trạng thái để phát trực tiếp hoàn toàn các hoạt động

Một giải pháp ở đây là giới thiệu trạng thái trong luồng của bạn thông qua bộ đếm bên ngoài, mặc dù nó sẽ chỉ hoạt động với một luồng tuần tự.

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 + '}'; }
}

Câu hỏi yêu cầu thu thập các phần tử liên tiếp của một luồng đầu vào, không chỉ thu thập các số nguyên liên tiếp. Một giải thích quan trọng về thuật ngữ Stream:! = "Lambdas".
Aleksandr Dubinsky

Bạn có thể thay thế AtomicInteger bằng AtomicReference. Giải pháp thay thế là cuộn bộ sưu tập của riêng bạn hoặc sử dụng các thư viện bên ngoài, chẳng hạn như trong ví dụ này: stackoverflow.com/a/30090528/829571
assylias

Xem bản chỉnh sửa của tôi. Ngoài ra, tôi không chắc mình hiểu nhận xét của bạn về lambda! = Stream. Câu trả lời khác có sử dụng một lớp vô danh không cơ bản được điều tương tự, ngoại trừ rằng nhà nước được tổ chức bởi lớp vô danh thay vì bên ngoài ...
assylias

1
Điều đó hoạt động. Các StreamExthư viện cũng là một phát hiện tốt và có thể là một câu trả lời của riêng mình. Nhận xét của tôi về "Stream! = Lambdas" đề cập đến việc bạn nói rằng "Hoạt động về cơ bản là trạng thái nên không thực sự là những gì lambdas có nghĩa là để giải quyết." Tôi nghĩ bạn muốn sử dụng từ "suối".
Aleksandr Dubinsky

Ồ, hiểu rồi - Tôi đã làm rõ điều đó.
assylias

0

Trong trường hợp của bạn, tôi sẽ viết IntFunction tùy chỉnh của mình để theo dõi int cuối cùng được chuyển và sử dụng nó để ánh xạ IntStream ban đầu.

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

  }

}

Vấn đề với điều này là nó ném trạng thái không trạng thái ra ngoài cửa sổ.
Rob

@Rob và vấn đề với điều đó là gì?
Aleksandr Dubinsky

Một trong những điểm chính của lambda là không có trạng thái có thể thay đổi để các bộ tích hợp bên trong có thể song song công việc.
Rob

@Rob: Vâng, bạn nói đúng, nhưng luồng ví dụ đã cho dù sao cũng không có tính song song vì mỗi mục (trừ cái đầu tiên và cuối cùng) được sử dụng như một mục đầu tiên và một mục thứ hai của một số cặp.
jpvee 10/1213

@jpvee vâng, tôi hiểu đó là những gì bạn đang nghĩ. Tôi tự hỏi nếu không có cách nào để làm điều này với một số người lập bản đồ khác. Về bản chất, tất cả những gì bạn cần sẽ tương đương với việc làm cho bộ tăng vòng lặp đi theo hai bước sau đó để hàm chức năng nhận 2 đối số. Điều đó phải có thể.
Rob

0

Để tính toán sự khác biệt liên tiếp trong thời gian (x-giá trị) của một chuỗi thời gian, tôi sử dụng stream's collect(...)phương pháp:

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

Trường hợp DifferenceCollector giống như thế này:

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;
    }
}

Bạn có thể sửa đổi điều này cho phù hợp với nhu cầu của bạn.


0

Cuối cùng tôi đã tìm ra cách đánh lừa Stream.reduce để có thể xử lý gọn gàng các cặp giá trị; Có vô số trường hợp sử dụng yêu cầu cơ sở này không tự nhiên xuất hiện trong 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;
}

Thủ thuật tôi sử dụng là quyền trả lại; tuyên bố.


1
Tôi không nghĩ rằng reduceđảm bảo đủ để điều này hoạt động.
Aleksandr Dubinsky

Sẽ quan tâm để biết thêm về các đảm bảo đủ . Bạn có thể vui lòng nói rõ hơn? Có lẽ có một sự thay thế trong Ổi ... nhưng tôi bị hạn chế và không thể sử dụng nó.
Beezer

-1

Một giải pháp thanh lịch sẽ là sử dụng zip . Cái gì đó như:

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)
);

Điều này khá ngắn gọn và trang nhã, tuy nhiên nó sử dụng một danh sách làm đầu vào. Không thể xử lý nguồn luồng vô hạn theo cách này.

Một vấn đề khác (rắc rối hơn nhiều) là zip cùng với toàn bộ lớp Luồng gần đây đã bị xóa khỏi API. Đoạn mã trên chỉ hoạt động với các phiên bản b95 trở lên. Vì vậy, với JDK mới nhất, tôi sẽ nói rằng không có giải pháp kiểu FP thanh lịch và ngay bây giờ chúng ta chỉ có thể hy vọng rằng bằng một cách nào đó zip sẽ được giới thiệu lại cho API.


Thật vậy, zipđã bị loại bỏ. Tôi không nhớ tất cả những gì có trên Streamslớp, nhưng một số thứ đã được chuyển thành các phương thức tĩnh trên Streamgiao diện, và cũng có StreamSupportStream.Buildercác lớp.
Stuart Marks

Đúng rồi. Một số phương thức khác như concat hoặc lặp đã được di chuyển và trở thành phương thức mặc định trong Stream. Đáng tiếc là zip vừa bị xóa khỏi API. Tôi hiểu lý do đằng sau sự lựa chọn này (ví dụ như thiếu Tuples) nhưng nó vẫn là một tính năng tốt.
tiện ích

2
@gadget Các bộ giá trị phải làm gì zip? Bất cứ lý do gì có thể được phát minh ra cũng không thể biện minh cho việc giết người zip.
Aleksandr Dubinsky

@AleksandrDubinsky Trong hầu hết các trường hợp, zip được sử dụng để tạo ra một bộ sưu tập Cặp / Bộ đôi làm đầu ra. Họ lập luận rằng nếu họ giữ zip, mọi người cũng sẽ yêu cầu Tuples như một phần của JDK. Tôi sẽ không bao giờ xóa một tính năng hiện có.
tiện ích

-1

Đây là một vấn đề thú vị. Nỗ lực lai của tôi dưới đây có tốt không?

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);
}

Tôi tin rằng nó không tự cho phép xử lý song song và do đó có thể bị loại.


Câu hỏi không yêu cầu xử lý song song, nhưng nó đã giả định rằng chúng ta chỉ có a Stream, không phải a List. Tất nhiên, chúng tôi cũng có thể mở một trình lặp từ Luồng, vì vậy đây có thể là một giải pháp hợp lệ. Tuy nhiên, đó là một cách tiếp cận ban đầu.
Aleksandr Dubinsky

-1

Như những người khác đã quan sát, do bản chất của vấn đề, cần phải có một số trạng thái rõ ràng.

Tôi đã phải đối mặt với một vấn đề tương tự, trong đó tôi muốn cái về cơ bản là hàm LEAD của Oracle SQL. Nỗ lực của tôi để thực hiện điều đó là bên dưới.

/**
 * 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

Bạn có thể đạt được điều đó bằng cách sử dụng hàng đợi có giới hạn để lưu trữ các phần tử chảy qua luồng (dựa trên ý tưởng mà tôi đã mô tả chi tiết ở đây: Có thể lấy phần tử tiếp theo trong Luồng không? )

Ví dụ Belows đầu tiên xác định phiên bản của lớp BoundQueue sẽ lưu trữ các phần tử đi qua luồng (nếu bạn không thích ý tưởng mở rộng LinkedList, hãy tham khảo liên kết được đề cập ở trên để có cách tiếp cận thay thế và chung chung hơn). Sau đó, bạn chỉ cần kết hợp hai phần tử tiếp theo thành thể hiện của Cặp:

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

Tôi đồng ý với @aepurniet nhưng thay vì lập bản đồ, bạn phải sử dụng mapToObj

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

1
Đúng. Nhưng điều này chỉ đơn giản là thu thập các cặp số nguyên, không phải các cặp phần tử của một luồng (thuộc bất kỳ loại nào).
Aleksandr Dubinsky

-5

Chạy một forvòng lặp chạy từ 0 đến length-1trong luồng của bạn

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
và phần lambda của giải pháp ở đâu?
Olimpiu POP

Nó không phải là trường hợp khi dòng là vô hạn
mishadoff

@Olimpiu - Bạn lấy lambda ở đâu là yêu cầu? Tôi đọc câu hỏi hai lần để chắc chắn rằng tôi không bỏ sót nó. Tôi cũng đã kiểm tra lịch sử chỉnh sửa. Và câu hỏi không được gắn thẻ với nó.
jww
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.