Nhận phần tử cuối cùng của Luồng / Danh sách trong một lớp lót


118

Làm cách nào để lấy phần tử cuối cùng của luồng hoặc danh sách trong đoạn mã sau?

Đâu data.careasList<CArea>:

CArea first = data.careas.stream()
                  .filter(c -> c.bbox.orientationHorizontal).findFirst().get();

CArea last = data.careas.stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .collect(Collectors.toList()).; //how to?

Như bạn có thể thấy để có được yếu tố đầu tiên, với một số yếu tố nhất định filter, không khó.

Tuy nhiên, nhận được phần tử cuối cùng trong một lớp lót là một nỗi đau thực sự:

  • Có vẻ như tôi không thể lấy nó trực tiếp từ a Stream. (Nó chỉ có ý nghĩa đối với các luồng hữu hạn)
  • Có vẻ như bạn không thể nhận được những thứ như first()last()từ Listgiao diện, đó thực sự là một điều khó khăn.

Tôi không thấy bất kỳ đối số nào về việc không cung cấp một first()last()phương thức trong Listgiao diện, vì các phần tử trong đó, được sắp xếp theo thứ tự và hơn nữa kích thước cũng được biết đến.

Nhưng theo câu trả lời ban đầu: Làm thế nào để lấy phần tử cuối cùng của một hữu hạn Stream?

Cá nhân, đây là gần nhất tôi có thể nhận được:

int lastIndex = data.careas.stream()
        .filter(c -> c.bbox.orientationHorizontal)
        .mapToInt(c -> data.careas.indexOf(c)).max().getAsInt();
CArea last = data.careas.get(lastIndex);

Tuy nhiên, nó liên quan đến việc sử dụng một indexOftrên mọi phần tử, điều mà bạn thường không muốn vì nó có thể làm giảm hiệu suất.


10
Guava cung cấp tính năng Iterables.getLastcó thể lặp lại được nhưng được tối ưu hóa để hoạt động List. Một con vật cưng là nó không có getFirst. Các StreamAPI nói chung là khủng khiếp hậu môn, bỏ qua rất nhiều phương pháp tiện lợi. LINQ của C #, theo ràng buộc, rất sẵn lòng cung cấp .Last()và thậm chí .Last(Func<T,Boolean> predicate), mặc dù nó cũng hỗ trợ các Enumerables vô hạn.
Aleksandr Dubinsky

@AleksandrDubinsky ủng hộ, nhưng có một lưu ý cho người đọc. StreamAPI không hoàn toàn có thể so sánh được LINQvì cả hai đều được thực hiện trong một mô hình rất khác nhau. Nó không tệ hơn hay tốt hơn mà chỉ là khác biệt. Và chắc chắn một số phương pháp vắng mặt không phải vì các nhà phát triển oracle không đủ năng lực hoặc xấu tính :)
fasth

1
Đối với một lớp lót thực sự, chủ đề này có thể được sử dụng.
lượng tử

Câu trả lời:


185

Có thể lấy phần tử cuối cùng bằng phương thức Stream :: Reduce . Danh sách sau đây chứa một ví dụ tối thiểu cho trường hợp chung:

Stream<T> stream = ...; // sequential or parallel stream
Optional<T> last = stream.reduce((first, second) -> second);

Việc triển khai này hoạt động cho tất cả các luồng có thứ tự (bao gồm cả các luồng được tạo từ Danh sách ). Đối với các luồng không có thứ tự , vì những lý do hiển nhiên không xác định được phần tử nào sẽ được trả về.

Việc triển khai hoạt động cho cả luồng tuần tựsong song . Thoạt nhìn điều đó có thể gây ngạc nhiên, và tiếc là tài liệu không trình bày rõ ràng. Tuy nhiên, đó là một tính năng quan trọng của luồng và tôi cố gắng làm rõ nó:

  • Javadoc cho phương thức Stream :: Reduce các trạng thái, rằng nó " không bị ràng buộc phải thực thi tuần tự " .
  • Javadoc cũng yêu cầu rằng "hàm tích lũy phải là một hàm liên kết , không can thiệp , không trạng thái để kết hợp hai giá trị" , điều này rõ ràng là trường hợp của biểu thức lambda (first, second) -> second.
  • Javadoc dành cho các hoạt động rút gọn cho biết: "Các lớp luồng có nhiều dạng hoạt động giảm tổng quát, được gọi là Reduce ()collect () [..]""hoạt động giảm được xây dựng đúng cách vốn có thể song song hóa , miễn là hàm (s ) được sử dụng để xử lý các phần tử là liên kếtkhông trạng thái . "

Tài liệu dành cho các Bộ sưu tập có liên quan chặt chẽ thậm chí còn rõ ràng hơn: "Để đảm bảo rằng việc thực thi tuần tựsong song tạo ra kết quả tương đương , các hàm bộ sưu tập phải thỏa mãn các ràng buộc về nhận dạng và tính liên kết ."


Quay lại câu hỏi ban đầu: Đoạn mã sau đây lưu trữ một tham chiếu đến phần tử cuối cùng trong biến lastvà ném một ngoại lệ nếu luồng trống. Độ phức tạp là tuyến tính theo chiều dài của luồng.

CArea last = data.careas
                 .stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .reduce((first, second) -> second).get();

Tốt, cảm ơn! Nhân tiện, bạn có biết liệu có thể bỏ qua một tên (có thể bằng cách sử dụng một _hoặc tương tự) trong trường hợp bạn không cần tham số không? Vì vậy, sẽ là: .reduce((_, current) -> current)nếu chỉ có cú pháp aws hợp lệ.
skiwi

2
@skiwi bạn có thể sử dụng bất kỳ tên biến hợp pháp nào, ví dụ: .reduce(($, current) -> current)hoặc .reduce((__, current) -> current)(dấu gạch dưới kép).
assylias

2
Về mặt kỹ thuật, nó có thể không hoạt động đối với bất kỳ luồng nào. Tài liệu mà bạn trỏ đến, cũng như Stream.reduce(BinaryOperator<T>)không đề cập đến việc reducetuân theo thứ tự cuộc gặp gỡ và thao tác đầu cuối có thể bỏ qua thứ tự cuộc gặp gỡ ngay cả khi luồng được sắp xếp. Ngoài ra, từ "giao hoán" không xuất hiện trong Stream javadocs, vì vậy sự vắng mặt của nó không cho chúng ta biết nhiều điều.
Aleksandr Dubinsky

2
@AleksandrDubinsky: Chính xác, các tài liệu không đề cập đến giao hoán , bởi vì nó không có liên quan đối với giảm hoạt động. Phần quan trọng là: "[..] Một hoạt động giảm được xây dựng đúng cách vốn dĩ có thể song song hóa, miễn là (các) hàm được sử dụng để xử lý các phần tử là liên kết [..]."
nosid

2
@Aleksandr Dubinsky: tất nhiên, nó không phải là một "câu hỏi lý thuyết về thông số kỹ thuật". Nó tạo ra sự khác biệt giữa reduce((a,b)->b)việc trở thành một giải pháp chính xác để lấy phần tử cuối cùng (tất nhiên là của một luồng có thứ tự) hay không. Tuyên bố của Brian Goetz có ý nghĩa, thêm vào đó, tài liệu API nói rằng đó reduce("", String::concat)là một giải pháp không hiệu quả nhưng đúng đắn cho việc nối chuỗi, ngụ ý duy trì thứ tự cuộc gặp gỡ.
Holger

42

Nếu bạn có một Bộ sưu tập (hoặc tổng quát hơn một bộ Lặp lại), bạn có thể sử dụng Google Guava's

Iterables.getLast(myIterable)

như oneliner tiện dụng.


1
Và bạn có thể dễ dàng chuyển đổi một luồng thành một luồng có thể lặp lại:Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())
shmosel

10

Một lớp lót (không cần luồng;):

Object lastElement = list.get(list.size()-1);

30
Nếu danh sách trống, mã này sẽ ném ArrayIndexOutOfBoundsException.
Dragon

8

Ổi có phương pháp dành riêng cho trường hợp này:

Stream<T> stream = ...;
Optional<T> lastItem = Streams.findLast(stream);

Nó tương đương với stream.reduce((a, b) -> b)nhưng những người sáng tạo cho rằng nó có hiệu suất tốt hơn nhiều.

Từ tài liệu :

Thời gian chạy của phương pháp này sẽ nằm giữa O (log n) và O (n), hoạt động tốt hơn trên các luồng có thể phân chia hiệu quả.

Điều đáng nói là nếu luồng không có thứ tự thì phương thức này sẽ hoạt động như thế nào findAny().


1
@ZhekaKozlov loại ... Holger cho thấy một số sai sót với nó đây
Eugene

0

Nếu bạn cần lấy N số phần tử cuối cùng. Đóng cửa có thể được sử dụng. Đoạn mã dưới đây duy trì một hàng đợi bên ngoài có kích thước cố định cho đến khi luồng đi đến cuối.

    final Queue<Integer> queue = new LinkedList<>();
    final int N=5;
    list.stream().peek((z) -> {
        queue.offer(z);
        if (queue.size() > N)
            queue.poll();
    }).count();

Một tùy chọn khác có thể là sử dụng thao tác giảm sử dụng danh tính làm Hàng đợi.

    final int lastN=3;
    Queue<Integer> reduce1 = list.stream()
    .reduce( 
        (Queue<Integer>)new LinkedList<Integer>(), 
        (m, n) -> {
            m.offer(n);
            if (m.size() > lastN)
               m.poll();
            return m;
    }, (m, n) -> m);

    System.out.println("reduce1 = " + reduce1);

-1

Bạn cũng có thể sử dụng hàm bỏ qua () như bên dưới ...

long count = data.careas.count();
CArea last = data.careas.stream().skip(count - 1).findFirst().get();

nó rất đơn giản để sử dụng.


Lưu ý: bạn không nên dựa vào "bỏ qua" của luồng khi xử lý các bộ sưu tập khổng lồ (hàng triệu mục nhập), vì "bỏ qua" được thực hiện bằng cách lặp qua tất cả các phần tử cho đến khi đạt đến số thứ N. Thử nó. Rất thất vọng về hiệu suất, so với một hoạt động lấy theo chỉ mục đơn giản.
java.is.for.desktop

1
Ngoài ra nếu danh sách trống, nó sẽ némArrayIndexOutOfBoundsException
Jindra Vysocký
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.