2016-03-10 29 views
7

Tôi có lớp sau đây:Java 8 suối để tìm phần tử trong danh sách

public class Item { 
    int id; 
    String name; 
    // few other fields, contructor, getters and setters 
} 

Tôi có một danh sách các mục. Tôi muốn lặp qua danh sách và tìm cá thể có một id cụ thể. Tôi đang cố gắng làm điều đó thông qua các luồng.

public void foobar() { 

    List<Item> items = getItemList(); 
    List<Integer> ids = getIdsToLookup(); 
    int id, i = ids.size() - 1; 

    while (i >= 0) { 
     id = ids.get(i); 
     Optional<Item> item = items 
      .stream() 
      .filter(a -> a.getId() == id) 
      .findFirst(); 
     // do stuff 
     i--; 
    } 
} 

Đây có phải là cách tốt nhất để lặp qua danh sách và lấy yếu tố tôi cần không? Ngoài ra, tôi nhận được một lỗi trên dòng lọc cho id mà nói rằng các biến được sử dụng trong các biểu thức lambda phải là cuối cùng hoặc hiệu quả cuối cùng. Có lẽ tôi có thể định nghĩa id bên trong vòng lặp while, điều đó sẽ loại bỏ ngoại lệ. Cảm ơn.

+1

Bạn đang sử dụng cùng biến i cho chỉ mục trong danh sách và cho mục hiện tại trong lambda. Chọn một tên khác. Mã đó là tốt, nhưng khá kém hiệu quả. Nếu bạn có nhiều id và cần tìm mục tương ứng cho tất cả, hãy bắt đầu bằng cách tạo HashMap , và sau đó sử dụng HashMap. –

+0

Tôi đang sử dụng một biến khác trong mã của mình, tôi đã cố gắng đơn giản hóa mã ở đây. Tôi sẽ thay đổi nó. –

+1

Khai báo biến 'id' ** bên trong ** vòng lặp và nó sẽ có hiệu quả cuối cùng. Bằng cách ở bên ngoài, bạn khởi động lại nó ở mỗi lần lặp lại, và do đó không phải là cuối cùng. Việc khai báo các biến trong phạm vi nhỏ nhất có thể là một cách thực hành tốt nhất nói chung. –

Trả lời

5

Nếu bạn có rất nhiều id để tìm kiếm, nó được đề nghị sử dụng một giải pháp mà hiện nó trong một pass duy nhất chứ không phải là làm một tìm kiếm tuyến tính cho mỗi id:

Map<Integer,Optional<Item>> map=ids.stream() 
    .collect(Collectors.toMap(id -> id, id -> Optional.empty())); 
items.forEach(item -> 
    map.computeIfPresent(item.getId(), (i,o)->o.isPresent()? o: Optional.of(item))); 
for(ListIterator<Integer> it=ids.listIterator(ids.size()); it.hasPrevious();) { 
    map.get(it.previous()).ifPresent(item -> { 
     // do stuff 
    }); 
} 

Câu lệnh đầu tiên chỉ đơn giản là tạo ra một bản đồ trong danh sách id, ánh xạ từng id tìm kiếm đến một số trống Optional.

Câu lệnh thứ hai lặp lại các mục bằng cách sử dụng forEach và cho mỗi mục, nó sẽ kiểm tra xem có ánh xạ từ id của nó tới số trống Optional và sẽ thay thế nó bằng cách Optional đóng gói mục đó, nếu có ánh xạ như vậy trong một thao tác, computeIfPresent.

Vòng lặp for cuối cùng lặp lại ngược qua danh sách ids, khi bạn muốn xử lý chúng theo thứ tự đó và thực hiện hành động nếu có Optional không trống. Vì bản đồ đã được khởi tạo với tất cả các id được tìm thấy trong danh sách, get sẽ không bao giờ trả lại null, nó sẽ trả về một số trống Optional, nếu id không được tìm thấy trong danh sách items.

Bằng cách đó, giả định rằng tra cứu các 's MapO(1) thời gian phức tạp, đó là trường hợp trong việc triển khai điển hình, thời gian phức tạp ròng thay đổi từ O(m×n) để O(m+n) ...

+0

Tôi thích ý tưởng thực hiện một lần chuyền và nhận tất cả các mục được yêu cầu. Nhưng tôi cần phải xử lý các mục theo thứ tự tôi nhận được trong danh sách id. Tôi không nghĩ rằng mã trên quan tâm đến việc đặt hàng, đúng không? Ngoài ra, bạn có thể đưa ra một số giải thích về những gì bạn đang cố gắng làm trong đoạn mã trên không? Điều đó sẽ hữu ích. Cảm ơn. –

+0

@Gengis Khan: nó thực hiện chính xác những gì bạn muốn. Vòng lặp 'for' lặp lại ngược trên danh sách' ids' như bạn muốn. – Holger

8

Bạn có thể thử sử dụng một cái gì đó như thế này:

ids.forEach(id -> 
    list.stream() 
    .filter(p -> p.getId() == id) 
    .findFirst() 
    .ifPresent(p -> {//do stuff here}); 
); 

Tùy chọn ở đây cho thấy rằng phương pháp lọc của bạn có thể trở lại một dòng sản phẩm nào, vì vậy nếu bạn gọi FindFirst nó có thể tìm thấy một hoặc không có yếu tố này.

+0

thay thế 'findFirst()' bằng 'findAny()' sẽ cải thiện hiệu suất ở đây. https://stackoverflow.com/questions/35359112/difference-between-findany-and-findfirst-in-java-8 – stuart

2

Nếu bạn muốn gắn bó với những con suối và lặp ngược, bạn có thể làm theo cách này:

IntStream.iterate(ids.size() - 1, i -> i - 1) 
    .limit(ids.size()) 
    .map(ids::get) // or .map(i -> ids.get(i)) 
    .forEach(id -> items.stream() 
     .filter(item -> item.getId() == id) 
     .findFirst().ifPresent(item -> { 
      // do stuff 
     })); 

Mã này hoạt động tương tự như bạn.

Nó lặp lại, bắt đầu bằng một hạt giống: ids.size() - 1. Luồng ban đầu của int s bị giới hạn về kích thước với limit(), do đó không có âmgiây và luồng có cùng kích thước với danh sách ids. Sau đó, hoạt động map() chuyển đổi chỉ mục thành id thực tế ở vị trí thứ i tại danh sách ids (điều này được thực hiện bằng cách gọi ids.get(i)). Cuối cùng, mục được tìm kiếm trong danh sách items giống như trong mã của bạn.

+2

Hoạt động của luồng ngụ ý sự phức tạp 'O (n)' anyway… – Holger

+0

@Holger Tôi muốn nói về 'ids 'list –

+1

@Holger Bây giờ tôi hiểu ý của bạn, sẽ chỉnh sửa để xóa ghi chú đó. Cảm ơn! –

0

Bạn muốn tìm nhiều nhất một mục cho mỗi id đã cho và làm điều gì đó với mục tìm thấy, đúng không? Cải thiện hiệu suất nhiều hơn một chút:

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set 
items.stream().filter(e -> idsToLookup.remove(e.getId())).forEach(/* doing something */); 
Các vấn đề liên quan