Bạn có thể chia một luồng thành hai luồng không?


146

Tôi có một bộ dữ liệu được đại diện bởi một luồng Java 8:

Stream<T> stream = ...;

Tôi có thể xem cách lọc nó để có một tập hợp con ngẫu nhiên - ví dụ:

Random r = new Random();
PrimitiveIterator.OfInt coin = r.ints(0, 2).iterator();   
Stream<T> heads = stream.filter((x) -> (coin.nextInt() == 0));

Tôi cũng có thể xem làm thế nào tôi có thể giảm luồng này để lấy, ví dụ, hai danh sách đại diện cho hai nửa ngẫu nhiên của tập dữ liệu và sau đó biến chúng trở lại thành luồng. Nhưng, có cách nào trực tiếp để tạo hai luồng từ luồng ban đầu không? Cái gì đó như

(heads, tails) = stream.[some kind of split based on filter]

Cảm ơn cho bất kỳ cái nhìn sâu sắc.


Câu trả lời của Mark hữu ích hơn câu trả lời của Louis nhưng tôi phải nói rằng Louis liên quan nhiều hơn đến câu hỏi ban đầu. Câu hỏi khá tập trung vào khả năng chuyển đổi Streamthành nhiều Streams mà không cần chuyển đổi trung gian , mặc dù tôi nghĩ rằng những người đạt được câu hỏi này thực sự đang tìm cách để đạt được điều đó bất kể ràng buộc đó là câu trả lời của Mark. Điều này có thể do thực tế là câu hỏi trong tiêu đề không giống như trong mô tả .
devildelta

Câu trả lời:


9

Không chính xác. Bạn không thể có được hai Streamtrong số một; Điều này không có ý nghĩa - làm thế nào bạn lặp đi lặp lại một cái mà không cần phải tạo cái khác cùng một lúc? Một luồng chỉ có thể được vận hành hơn một lần.

Tuy nhiên, nếu bạn muốn kết xuất chúng vào danh sách hoặc thứ gì đó, bạn có thể làm

stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));

65
Tại sao nó không có ý nghĩa? Vì một luồng là một đường ống, không có lý do gì nó không thể tạo ra hai nhà sản xuất của luồng gốc, tôi có thể thấy điều này được xử lý bởi một nhà sưu tập cung cấp hai luồng.
Brett Ryan

36
Không chủ đề an toàn. Lời khuyên tồi là cố gắng thêm trực tiếp vào bộ sưu tập, đó là lý do tại sao chúng tôi có tính năng stream.collect(...)bảo mật luồng được xác định trước Collectors, hoạt động tốt ngay cả trên Bộ sưu tập không an toàn luồng (không có tranh chấp khóa được đồng bộ hóa). Câu trả lời hay nhất của @MarkJeronimus.
YoYo

1
@JoD Nó an toàn cho chủ đề nếu đầu và đuôi an toàn cho chủ đề. Ngoài ra, giả sử sử dụng các luồng không song song, chỉ có thứ tự không được đảm bảo, vì vậy chúng an toàn cho luồng. Tùy thuộc vào lập trình viên để khắc phục các sự cố đồng thời, vì vậy câu trả lời này hoàn toàn phù hợp nếu các bộ sưu tập là luồng an toàn.
Nicolas

1
@Nixon nó không phù hợp với sự có mặt của một giải pháp tốt hơn, mà chúng tôi có ở đây. Có mã như vậy có thể dẫn đến tiền lệ xấu, khiến người khác sử dụng nó sai cách. Ngay cả khi không có luồng song song được sử dụng, nó chỉ là một bước. Thực hành mã hóa tốt yêu cầu chúng tôi không duy trì trạng thái trong các hoạt động truyền phát. Điều tiếp theo chúng tôi làm là mã hóa trong một khung như tia lửa Apache và các thực tiễn tương tự sẽ thực sự dẫn đến kết quả không mong muốn. Đó là một giải pháp sáng tạo, tôi đưa ra rằng, một trong những điều mà tôi có thể đã tự viết cách đây không lâu.
YoYo

1
@JoD Đó không phải là một giải pháp tốt hơn, nó thực sự kém hiệu quả hơn. Dòng suy nghĩ đó cuối cùng kết thúc với kết luận rằng tất cả các Bộ sưu tập nên được xử lý an toàn theo mặc định để ngăn ngừa hậu quả không mong muốn, điều này đơn giản là sai.
Nicolas

301

Một bộ sưu tập có thể được sử dụng cho việc này.

  • Đối với hai loại, sử dụng Collectors.partitioningBy()nhà máy.

Điều này sẽ tạo một Maptừ Booleanđến Listvà đặt các mục vào một hoặc danh sách khác dựa trên a Predicate.

Lưu ý: Vì luồng cần được tiêu thụ toàn bộ, nên luồng này không thể hoạt động trên các luồng vô hạn. Và bởi vì luồng được tiêu thụ bằng mọi giá, phương thức này chỉ đơn giản là đặt chúng vào Danh sách thay vì tạo luồng mới với bộ nhớ. Bạn luôn có thể truyền phát các danh sách đó nếu bạn yêu cầu các luồng làm đầu ra.

Ngoài ra, không cần trình lặp, thậm chí không có trong ví dụ chỉ dành cho người đứng đầu mà bạn cung cấp.

  • Tách nhị phân trông như thế này:
Random r = new Random();

Map<Boolean, List<String>> groups = stream
    .collect(Collectors.partitioningBy(x -> r.nextBoolean()));

System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
  • Đối với nhiều loại, sử dụng một Collectors.groupingBy()nhà máy.
Map<Object, List<String>> groups = stream
    .collect(Collectors.groupingBy(x -> r.nextInt(3)));
System.out.println(groups.get(0).size());
System.out.println(groups.get(1).size());
System.out.println(groups.get(2).size());

Trong trường hợp các luồng không phải Stream, nhưng một trong những luồng nguyên thủy như thế IntStream, thì .collect(Collectors)phương thức này không có sẵn. Bạn sẽ phải làm theo cách thủ công mà không cần nhà máy thu gom. Đó là cách thực hiện như thế này:

[Ví dụ 2.0 kể từ 2020-04-16]

    IntStream    intStream = IntStream.iterate(0, i -> i + 1).limit(100000).parallel();
    IntPredicate predicate = ignored -> r.nextBoolean();

    Map<Boolean, List<Integer>> groups = intStream.collect(
            () -> Map.of(false, new ArrayList<>(100000),
                         true , new ArrayList<>(100000)),
            (map, value) -> map.get(predicate.test(value)).add(value),
            (map1, map2) -> {
                map1.get(false).addAll(map2.get(false));
                map1.get(true ).addAll(map2.get(true ));
            });

Trong ví dụ này, tôi khởi tạo ArrayLists với kích thước đầy đủ của bộ sưu tập ban đầu (nếu điều này được biết hoàn toàn). Điều này ngăn việc thay đổi kích thước các sự kiện ngay cả trong trường hợp xấu nhất, nhưng có khả năng ngấu nghiến không gian 2 * N * T (N = số phần tử ban đầu, T = số luồng). Để đánh đổi không gian cho tốc độ, bạn có thể loại bỏ nó hoặc sử dụng dự đoán có giáo dục tốt nhất của bạn, như số phần tử cao nhất dự kiến ​​trong một phân vùng (thường chỉ hơn N / 2 cho một phân chia cân bằng).

Tôi hy vọng tôi không xúc phạm bất cứ ai bằng cách sử dụng phương thức Java 9. Đối với phiên bản Java 8, hãy xem lịch sử chỉnh sửa.


2
Xinh đẹp. Tuy nhiên, giải pháp cuối cùng cho IntStream sẽ không an toàn cho luồng trong trường hợp luồng song song. Giải pháp đơn giản hơn nhiều so với bạn nghĩ đó là ... stream.boxed().collect(...);! Nó sẽ làm như quảng cáo: chuyển đổi nguyên thủy IntStreamsang Stream<Integer>phiên bản đóng hộp .
YoYo

32
Đây phải là câu trả lời được chấp nhận vì nó trực tiếp giải quyết câu hỏi OP.
tinh

27
Tôi muốn Stack Overflow sẽ cho phép cộng đồng ghi đè câu trả lời đã chọn nếu tìm thấy câu trả lời tốt hơn.
GuiSim

Tôi không chắc điều này trả lời câu hỏi. Câu hỏi yêu cầu chia luồng thành luồng - không phải Danh sách.
AlikElzin-kilaka

1
Hàm tích lũy dài dòng không cần thiết. Thay vì (map, x) -> { boolean partition = p.test(x); List<Integer> list = map.get(partition); list.add(x); }bạn chỉ có thể sử dụng (map, x) -> map.get(p.test(x)).add(x). Hơn nữa, tôi không thấy bất kỳ lý do tại sao collecthoạt động không nên an toàn cho luồng. Nó hoạt động chính xác như nó được cho là hoạt động và rất gần với cách thức Collectors.partitioningBy(p)hoạt động. Nhưng tôi sẽ sử dụng IntPredicatethay vì Predicate<Integer>khi không sử dụng boxed(), để tránh đấm bốc hai lần.
Holger

21

Tôi đã tự mình vấp phải câu hỏi này và tôi cảm thấy rằng một luồng rẽ nhánh có một số trường hợp sử dụng có thể chứng minh được giá trị. Tôi đã viết mã dưới đây với tư cách là người tiêu dùng để nó không làm gì cả nhưng bạn có thể áp dụng nó cho các chức năng và bất cứ điều gì khác mà bạn có thể gặp phải.

class PredicateSplitterConsumer<T> implements Consumer<T>
{
  private Predicate<T> predicate;
  private Consumer<T>  positiveConsumer;
  private Consumer<T>  negativeConsumer;

  public PredicateSplitterConsumer(Predicate<T> predicate, Consumer<T> positive, Consumer<T> negative)
  {
    this.predicate = predicate;
    this.positiveConsumer = positive;
    this.negativeConsumer = negative;
  }

  @Override
  public void accept(T t)
  {
    if (predicate.test(t))
    {
      positiveConsumer.accept(t);
    }
    else
    {
      negativeConsumer.accept(t);
    }
  }
}

Bây giờ việc thực thi mã của bạn có thể giống như thế này:

personsArray.forEach(
        new PredicateSplitterConsumer<>(
            person -> person.getDateOfBirth().isPresent(),
            person -> System.out.println(person.getName()),
            person -> System.out.println(person.getName() + " does not have Date of birth")));

20

Thật không may, những gì bạn yêu cầu được trực tiếp nhăn mặt trong JavaDoc of Stream :

Một luồng nên được vận hành trên (gọi một hoạt động luồng trung gian hoặc đầu cuối) chỉ một lần. Điều này loại trừ, ví dụ, các luồng "rẽ nhánh", trong đó cùng một nguồn cung cấp hai hoặc nhiều đường ống hoặc nhiều đường truyền của cùng một luồng.

Bạn có thể giải quyết vấn đề này bằng cách sử dụng peek hoặc các phương pháp khác nếu bạn thực sự mong muốn loại hành vi đó. Trong trường hợp này, điều bạn nên làm là thay vì cố gắng sao lưu hai luồng từ cùng một nguồn Stream ban đầu bằng bộ lọc forking, bạn sẽ sao chép luồng của mình và lọc từng luồng trùng lặp một cách thích hợp.

Tuy nhiên, bạn có thể muốn xem xét lại nếu a Streamlà cấu trúc phù hợp cho trường hợp sử dụng của bạn.


6
Từ ngữ javadoc không loại trừ phân vùng thành nhiều luồng miễn là một mục luồng duy nhất chỉ đi vào một trong số đó
Thorbjørn Ravn Andersen

2
@ ThorbjørnRavnAndersen Tôi không chắc chắn việc sao chép một mục luồng là trở ngại chính cho luồng phân nhánh. Vấn đề chính là hoạt động forking về cơ bản là hoạt động của thiết bị đầu cuối, vì vậy khi bạn quyết định rẽ nhánh, về cơ bản bạn đang tạo ra một bộ sưu tập nào đó. Ví dụ: tôi có thể viết một phương thức List<Stream> forkStream(Stream s)nhưng các luồng kết quả của tôi ít nhất sẽ được hỗ trợ một phần bởi các bộ sưu tập và không trực tiếp bởi luồng bên dưới, trái ngược với việc filterkhông phải là hoạt động của luồng cuối.
Trevor Freeman

7
Đây là một trong những lý do khiến tôi cảm thấy các luồng Java hơi bị phân nửa so với github.com/ReactiveX/RxJava/wiki vì mục đích của luồng là áp dụng các hoạt động trên một tập hợp các phần tử có khả năng vô hạn và các hoạt động trong thế giới thực thường yêu cầu chia tách , trùng lặp và hợp nhất các luồng.
Usman Ismail

8

Điều này là trái với cơ chế chung của Stream. Giả sử bạn có thể chia Stream S0 thành Sa và Sb như bạn muốn. Thực hiện bất kỳ hoạt động đầu cuối nào, giả sử count(), trên Sa sẽ nhất thiết phải "tiêu thụ" tất cả các yếu tố trong S0. Do đó Sb bị mất nguồn dữ liệu.

Trước đây, Stream có một tee()phương thức, tôi nghĩ, nó nhân đôi một luồng thành hai. Bây giờ nó đã được gỡ bỏ.

Stream có một phương thức peek (), bạn có thể sử dụng nó để đạt được yêu cầu của mình.


1
peekchính xác là những gì đã từng tee.
Louis Wasserman

5

không chính xác, nhưng bạn có thể hoàn thành những gì bạn cần bằng cách gọi Collectors.groupingBy(). bạn tạo Bộ sưu tập mới và sau đó có thể khởi tạo luồng trên bộ sưu tập mới đó.


2

Đây là câu trả lời ít tệ nhất tôi có thể đưa ra.

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

public class Test {

    public static <T, L, R> Pair<L, R> splitStream(Stream<T> inputStream, Predicate<T> predicate,
            Function<Stream<T>, L> trueStreamProcessor, Function<Stream<T>, R> falseStreamProcessor) {

        Map<Boolean, List<T>> partitioned = inputStream.collect(Collectors.partitioningBy(predicate));
        L trueResult = trueStreamProcessor.apply(partitioned.get(Boolean.TRUE).stream());
        R falseResult = falseStreamProcessor.apply(partitioned.get(Boolean.FALSE).stream());

        return new ImmutablePair<L, R>(trueResult, falseResult);
    }

    public static void main(String[] args) {

        Stream<Integer> stream = Stream.iterate(0, n -> n + 1).limit(10);

        Pair<List<Integer>, String> results = splitStream(stream,
                n -> n > 5,
                s -> s.filter(n -> n % 2 == 0).collect(Collectors.toList()),
                s -> s.map(n -> n.toString()).collect(Collectors.joining("|")));

        System.out.println(results);
    }

}

Cái này lấy một luồng số nguyên và chia chúng ở mức 5. Đối với những số lớn hơn 5, nó chỉ lọc các số chẵn và đặt chúng vào một danh sách. Đối với phần còn lại, nó kết hợp với chúng với |.

đầu ra:

 ([6, 8],0|1|2|3|4|5)

Nó không lý tưởng vì nó thu thập mọi thứ vào các bộ sưu tập trung gian phá vỡ luồng (và có quá nhiều đối số!)


1

Tôi tình cờ gặp câu hỏi này trong khi tìm cách lọc các yếu tố nhất định ra khỏi luồng và ghi lại chúng là lỗi. Vì vậy, tôi không thực sự cần phải phân chia luồng nhiều như đính kèm một hành động chấm dứt sớm với một vị ngữ với cú pháp không phô trương. Đây là những gì tôi nghĩ ra:

public class MyProcess {
    /* Return a Predicate that performs a bail-out action on non-matching items. */
    private static <T> Predicate<T> withAltAction(Predicate<T> pred, Consumer<T> altAction) {
    return x -> {
        if (pred.test(x)) {
            return true;
        }
        altAction.accept(x);
        return false;
    };

    /* Example usage in non-trivial pipeline */
    public void processItems(Stream<Item> stream) {
        stream.filter(Objects::nonNull)
              .peek(this::logItem)
              .map(Item::getSubItems)
              .filter(withAltAction(SubItem::isValid,
                                    i -> logError(i, "Invalid")))
              .peek(this::logSubItem)
              .filter(withAltAction(i -> i.size() > 10,
                                    i -> logError(i, "Too large")))
              .map(SubItem::toDisplayItem)
              .forEach(this::display);
    }
}

0

Phiên bản ngắn hơn sử dụng Lombok

import java.util.function.Consumer;
import java.util.function.Predicate;

import lombok.RequiredArgsConstructor;

/**
 * Forks a Stream using a Predicate into postive and negative outcomes.
 */
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PROTECTED)
public class StreamForkerUtil<T> implements Consumer<T> {
    Predicate<T> predicate;
    Consumer<T> positiveConsumer;
    Consumer<T> negativeConsumer;

    @Override
    public void accept(T t) {
        (predicate.test(t) ? positiveConsumer : negativeConsumer).accept(t);
    }
}

-3

Làm thế nào về:

Supplier<Stream<Integer>> randomIntsStreamSupplier =
    () -> (new Random()).ints(0, 2).boxed();

Stream<Integer> tails =
    randomIntsStreamSupplier.get().filter(x->x.equals(0));
Stream<Integer> heads =
    randomIntsStreamSupplier.get().filter(x->x.equals(1));

1
Vì nhà cung cấp được gọi hai lần, bạn sẽ nhận được hai bộ sưu tập ngẫu nhiên khác nhau. Tôi nghĩ rằng đó là tâm trí của OP để phân chia tỷ lệ cược từ các evens trong cùng một chuỗi được tạo
usr-local-
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.