2015-05-05 27 views
12

Giả sử tôi có một số CollectionPredicate khớp với các yếu tố tôi muốn xóa khỏi Collection. Nhưng tôi không chỉ muốn loại bỏ chúng, tôi muốn di chuyển các yếu tố phù hợp vào một bộ sưu tập mới. Tôi muốn làm một cái gì đó như this trong Java 7:Xóa và thu thập các phần tử bằng luồng Java

List<E> removed = new LinkedList<>(); 
for (Iterator<E> i = data.iterator(); i.hasNext();) { 
    E e = i.next(); 
    if (predicate.test(e)) { 
     removed.add(e); 
     i.remove(); 
    } 
} 

Tôi tò mò nếu có một luồng/Java 8 cách để làm điều đó. Collections.removeIf() may đơn giản là trả về một boolean (thậm chí không đếm số lượng các yếu tố loại bỏ Quá xấu?). Tôi hình dung một cái gì đó như thế này (mặc dù dĩ nhiên .removeAndYield(Predicate) không tồn tại):

List<E> removed = data.removeAndYield(predicate).collect(Collectors.toList()); 

Lưu ý: Câu hỏi này là lấy cảm hứng từ a similar question; câu hỏi này là về trường hợp tổng quát hơn khi nhận luồng trên các mục bị xóa khỏi bộ sưu tập. Như đã chỉ ra trong câu hỏi được liên kết, giải pháp bắt buộc có thể dễ đọc hơn, nhưng tôi tò mò nếu điều này thậm chí có thể với các luồng.

Chỉnh sửa: Rõ ràng chúng tôi có thể chia nhiệm vụ thành hai bước riêng biệt và giả định cấu trúc dữ liệu thích hợp sẽ hiệu quả. Câu hỏi đặt ra là điều này có thể được thực hiện trên các bộ sưu tập tùy ý (có thể không có hiệu quả .contains() vv).

+0

Khó khăn là bạn không thể xóa các phần tử từ một 'Bộ' trong khi lặp lại nó mà không nhận được' ConcurrentModificationException', vì vậy bất kỳ cách Java 8 nào cũng sẽ yêu cầu ít nhất hai lần lặp lại như trong câu trả lời của Misha. AFAIK cách duy nhất để làm điều này với một lần lặp duy nhất của 'set' là sử dụng một' Iterator' rõ ràng. –

+0

Bạn có thể với một 'Iterator' trên' Bộ sưu tập', điều này cho thấy nó có thể có khái niệm với một 'Dòng'. Rõ ràng điều đó không có nghĩa là nó có sẵn trong hộp, nhưng có lẽ nó không phức tạp để làm. Hoặc có thể có lý do chính đáng mà nó không có trong JDK. – dimo414

+0

Ví dụ "Java 7" trong câu hỏi của tôi thể hiện việc loại bỏ các phần tử giữa lần lặp lại. Nếu chúng ta có thể làm điều đó trong một đường chuyền một cách bất hợp pháp, nó có thể được thực hiện một cách có chức năng không? – dimo414

Trả lời

11

tôi muốn giữ nó đơn giản:

Set<E> removed = set.stream() 
    .filter(predicate) 
    .collect(Collectors.toSet()); 

set.removeAll(removed); 
+0

Tôi thích điều này (+1) mặc dù 2 trong số 3 lựa chọn cuối cùng của bạn tốt hơn lần đầu tiên vì chúng chỉ lặp qua 'xoá' thay vì tất cả' bộ'. –

+1

Tôi đồng ý trong trường hợp cấu trúc ban đầu là một bộ. Tuy nhiên, nếu cấu trúc nguồn không phải là một tập hợp, 'remove.forEach' sẽ chỉ đưa ra sự xuất hiện đầu tiên của phần tử so khớp từ bộ sưu tập, trong khi' removeIf' và 'removeAll' sẽ loại bỏ tất cả chúng. Nói chung, tôi nghĩ rằng 'removeAll' là sự lựa chọn tốt nhất. – Misha

+0

@pbabcdefp Tôi đã sửa đổi câu trả lời để xóa các tùy chọn không cần thiết. – Misha

4

Nếu bạn muốn có một cách chức năng để làm điều này, bạn có thể viết phương pháp riêng của mình.

static <E> Set<E> removeIf(Collection<? extends E> collection, Predicate<? super E> predicate) { 
    Set<E> removed = new HashSet<>(); 
    for (Iterator<? extends E> i = collection.iterator(); i.hasNext();) { 
     E e = i.next(); 
     if (predicate.test(e)) { 
      removed.add(e); 
      i.remove(); 
     } 
    } 
    return removed; 
} 

Điều này có thể được sử dụng để xóa tất cả các số lẻ khỏi List.

Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6)); 
Set<Integer> removed = removeIf(set, i -> i % 2 != 0); 
System.out.println(set); 
System.out.println(removed); 
16

Nếu bạn không phiền, hãy để tôi uốn cong yêu cầu của bạn một chút. :-)

Một đặc điểm của kết quả mong muốn là các phần tử phù hợp sẽ kết thúc trong một bộ sưu tập và các phần tử không khớp sẽ kết thúc trong một bộ sưu tập khác. Trong thế giới đột biến trước Java-8, cách dễ nhất để suy nghĩ về việc thu thập các yếu tố không phù hợp là xóa các yếu tố phù hợp khỏi bộ sưu tập gốc.

Nhưng là loại bỏ - sửa đổi các danh sách ban đầu - một phần nội tại của yêu cầu?

Nếu nó không phải là, thì kết quả có thể đạt được thông qua một hoạt động phân vùng đơn giản:

Map<Boolean, List<E>> map = data.stream().collect(partitioningBy(predicate)); 

Bản đồ kết quả thực chất là hai danh sách, trong đó có chứa các hợp (key = true) và không phù hợp (key = false) phần tử.

Lợi thế là kỹ thuật này có thể được thực hiện trong một lần truyền và song song nếu cần. Tất nhiên, điều này tạo ra một danh sách trùng lặp của các yếu tố không phù hợp so với loại bỏ các trận đấu từ bản gốc, nhưng đây là giá phải trả cho bất biến. Sự cân bằng có thể đáng giá.

+2

Đề xuất tốt. Bộ sưu tập có thể thay đổi thường gặp nhiều rắc rối hơn chúng đáng giá, vì vậy thật tuyệt khi thấy có một cách dễ dàng để thực hiện việc này với các bộ sưu tập không thể thay đổi. – dimo414

+1

@ dimo414 Cảm ơn, và oh có, cảm ơn bạn đã sửa groupingBy thành partitioningBy. Rất tiếc! –

0

Chỉ cần viết cho mình một chức năng tái sử dụng như thế này:

/** 
* Removes all matching element from iterator 
* 
* @param it 
* @param predicate 
*/ 
public static <E> void removeMatching(final Iterator<E> it, final Predicate<E> predicate) { 
    while (it.hasNext()) { 
     final E e = it.next(); 
     if (predicate.test(e)) { 
      it.remove(); 
     } 
    } 
} 

Tôi cũng không tìm thấy một giải pháp tồn tại trước đó cho Streams. Sử dụng iterator.remove() yêu cầu bộ nhớ ít hơn một bộ tạm thời.

Các vấn đề liên quan