Giới hạn luồng theo vị ngữ


187

Có hoạt động luồng Java 8 nào giới hạn (có khả năng vô hạn) không Stream cho đến khi phần tử đầu tiên không khớp với một vị từ?

Trong Java 9, chúng ta có thể sử dụng takeWhilenhư trong ví dụ dưới đây để in tất cả các số nhỏ hơn 10.

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

Vì không có hoạt động như vậy trong Java 8, cách tốt nhất để triển khai nó theo cách chung là gì?


1
Có thể thông tin hữu ích tại: stackoverflow.com/q/19803058/248082
nobeh 24/12/13


Tôi tự hỏi làm thế nào các kiến ​​trúc sư có thể vượt qua "chúng ta thực sự có thể sử dụng cái này để làm gì" mà không cần chạy vào usecase này. Kể từ Java 8 Luồng chỉ thực sự hữu ích cho các cơ sở dữ liệu hiện có: - /
Thorbjørn Ravn Andersen


Với Java 9, nó sẽ dễ viết hơnIntStream.iterate(1, n->n<10, n->n+1).forEach(System.out::print);
Marc Dzaebel

Câu trả lời:


81

Một hoạt động như vậy phải có thể thực hiện được với Java 8 Stream, nhưng nó không nhất thiết phải được thực hiện một cách hiệu quả - ví dụ, bạn không nhất thiết phải song song hóa một hoạt động đó, khi bạn phải xem xét các yếu tố theo thứ tự.

API không cung cấp một cách dễ dàng để làm điều đó, nhưng có lẽ cách đơn giản nhất là thực hiện Stream.iterator(), bao bọc Iteratorđể thực hiện "mất thời gian" và sau đó quay lại a Spliteratorvà sau đó a Stream. Hoặc - có thể - bao bọc Spliterator, mặc dù nó thực sự không thể được phân chia nữa trong triển khai này.

Đây là một triển khai chưa được kiểm tra takeWhilevề Spliterator:

static <T> Spliterator<T> takeWhile(
    Spliterator<T> splitr, Predicate<? super T> predicate) {
  return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
    boolean stillGoing = true;
    @Override public boolean tryAdvance(Consumer<? super T> consumer) {
      if (stillGoing) {
        boolean hadNext = splitr.tryAdvance(elem -> {
          if (predicate.test(elem)) {
            consumer.accept(elem);
          } else {
            stillGoing = false;
          }
        });
        return hadNext && stillGoing;
      }
      return false;
    }
  };
}

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
   return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}

8
Về lý thuyết, song song TakeWhile với một vị từ không trạng thái là dễ dàng. Đánh giá điều kiện theo các đợt song song (giả sử vị ngữ không ném hoặc có tác dụng phụ nếu được thực hiện thêm một vài lần). Vấn đề là thực hiện nó trong bối cảnh phân rã đệ quy (khung fork / tham gia) mà Streams sử dụng. Thực sự, đó là những luồng không hiệu quả khủng khiếp.
Alexanderr Dubinsky

91
Các luồng sẽ tốt hơn rất nhiều nếu chúng không quá bận tâm với tính song song tự động. Song song là cần thiết chỉ trong một phần nhỏ của những nơi có thể sử dụng Luồng. Bên cạnh đó, nếu Oracle quan tâm rất nhiều đến sự hoàn hảo, họ có thể đã tạo ra JVM JIT autovectorize và nhận được sự tăng cường hiệu suất lớn hơn nhiều, mà không làm phiền các nhà phát triển. Bây giờ đó là song song tự động được thực hiện đúng.
Alexanderr Dubinsky

Bạn nên cập nhật câu trả lời này ngay bây giờ khi Java 9 được phát hành.
Radiodef

4
Không, @Radiodef. Câu hỏi yêu cầu cụ thể cho một giải pháp Java 8.
Renato trở lại

145

Các hoạt động takeWhiledropWhileđã được thêm vào JDK 9. Mã ví dụ của bạn

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

sẽ hoạt động chính xác như bạn mong đợi khi được biên dịch và chạy theo JDK 9.

JDK 9 đã được phát hành. Nó có sẵn để tải về tại đây: http://jdk.java.net/9/


3
Liên kết trực tiếp đến các tài liệu xem trước cho JDK9 Stream, với takeWhile/ dropWhile: download.java.net/jdk9/docs/api/java/util/stream/Stream.html
Miles

1
Có bất kỳ lý do tại sao họ được gọi takeWhiledropWhilethay vì limitWhileskipWhile, để thống nhất với API hiện tại không?
Lukas Eder

10
@LukasEder takeWhiledropWhilekhá phổ biến, xuất hiện trong Scala, Python, Groovy, Ruby, Haskell và Clojure. Sự bất cân xứng với skiplimitkhông may. Có thể skiplimitnên được gọi droptake, nhưng những thứ đó không trực quan trừ khi bạn đã quen thuộc với Haskell.
Stuart Marks

3
@StuartMarks: Tôi hiểu điều đó dropXXXtakeXXXlà những thuật ngữ phổ biến hơn nhưng cá nhân tôi có thể sống với nhiều SQL-esque hơn limitXXXskipXXX. Tôi thấy sự bất cân xứng mới này khó hiểu hơn nhiều so với lựa chọn thuật ngữ cá nhân ... :) (btw: Scala cũng có drop(int)take(int))
Lukas Eder

1
yeah, hãy để tôi nâng cấp lên Jdk 9 trong sản xuất. Nhiều nhà phát triển vẫn còn trên Jdk8, một tính năng như vậy nên được đưa vào Stream ngay từ đầu.
wilmol

50

allMatch()là một chức năng ngắn mạch, vì vậy bạn có thể sử dụng nó để dừng xử lý. Nhược điểm chính là bạn phải làm bài kiểm tra của mình hai lần: một lần để xem bạn có nên xử lý nó không, và một lần nữa để xem có nên tiếp tục không.

IntStream
    .iterate(1, n -> n + 1)
    .peek(n->{if (n<10) System.out.println(n);})
    .allMatch(n->n < 10);

5
Điều này có vẻ không trực quan với tôi lúc đầu (được đặt tên phương thức), nhưng các tài liệu xác nhận rằng đó Stream.allMatch()là một hoạt động ngắn mạch . Vì vậy, điều này sẽ hoàn thành ngay cả trên một dòng vô hạn như thế nào IntStream.iterate(). Tất nhiên, nhìn lại, đây là một tối ưu hóa hợp lý.
Bailey Parker

3
Điều này là gọn gàng, nhưng tôi không nghĩ rằng nó truyền đạt rất tốt rằng ý định của nó là cơ thể của peek. Nếu tôi gặp nó vào tháng tới, tôi sẽ mất một phút để tự hỏi tại sao lập trình viên trước tôi lại kiểm tra xem allMatchvà sau đó bỏ qua câu trả lời.
Joshua Goldberg

10
Nhược điểm của giải pháp này là nó trả về một boolean để bạn không thể thu thập kết quả của luồng như bình thường.
neXus

35

Theo dõi câu trả lời của @StuartMarks . Thư viện StreamEx của tôi có takeWhilehoạt động tương thích với triển khai JDK-9 hiện tại. Khi chạy theo JDK-9, nó sẽ chỉ ủy thác cho việc triển khai JDK (thông qua MethodHandle.invokeExactđó thực sự nhanh). Khi chạy theo JDK-8, việc triển khai "polyfill" sẽ được sử dụng. Vì vậy, sử dụng thư viện của tôi, vấn đề có thể được giải quyết như thế này:

IntStreamEx.iterate(1, n -> n + 1)
           .takeWhile(n -> n < 10)
           .forEach(System.out::println);

Tại sao bạn không triển khai nó cho lớp StreamEx?
Một số

@Someguy Tôi đã thực hiện nó.
Tagir Valeev

14

takeWhilelà một trong những chức năng được cung cấp bởi thư viện protonpack .

Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);

assertThat(finiteInts.collect(Collectors.toList()),
           hasSize(10));

11

Cập nhật: Java 9 Streambây giờ đi kèm với TakeWhile phương thức .

Không có nhu cầu cho hack hoặc các giải pháp khác. Chỉ cần sử dụng nó!


Tôi chắc chắn rằng điều này có thể được cải thiện rất nhiều khi: (ai đó có thể làm cho nó an toàn theo luồng)

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

TakeWhile.stream(stream, n -> n < 10000)
         .forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));

Một hack chắc chắn ... Không thanh lịch - nhưng nó hoạt động ~: D

class TakeWhile<T> implements Iterator<T> {

    private final Iterator<T> iterator;
    private final Predicate<T> predicate;
    private volatile T next;
    private volatile boolean keepGoing = true;

    public TakeWhile(Stream<T> s, Predicate<T> p) {
        this.iterator = s.iterator();
        this.predicate = p;
    }

    @Override
    public boolean hasNext() {
        if (!keepGoing) {
            return false;
        }
        if (next != null) {
            return true;
        }
        if (iterator.hasNext()) {
            next = iterator.next();
            keepGoing = predicate.test(next);
            if (!keepGoing) {
                next = null;
            }
        }
        return next != null;
    }

    @Override
    public T next() {
        if (next == null) {
            if (!hasNext()) {
                throw new NoSuchElementException("Sorry. Nothing for you.");
            }
        }
        T temp = next;
        next = null;
        return temp;
    }

    public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
        TakeWhile tw = new TakeWhile(s, p);
        Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
        return StreamSupport.stream(split, false);
    }

}

8

Bạn có thể sử dụng java8 + rxjava .

import java.util.stream.IntStream;
import rx.Observable;


// Example 1)
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n ->
          {
                System.out.println(n);
                return n < 10;
          }
    ).subscribe() ;


// Example 2
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n -> n < 10)
    .forEach( n -> System.out.println(n));

6

Trên thực tế, có 2 cách để làm điều đó trong Java 8 mà không cần bất kỳ thư viện bổ sung hoặc sử dụng Java 9.

Nếu bạn muốn in số từ 2 đến 20 trên bảng điều khiển, bạn có thể thực hiện việc này:

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);

hoặc là

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);

Đầu ra là trong cả hai trường hợp:

2
4
6
8
10
12
14
16
18
20

Không ai đề cập đến anyMatch nào. Đây là lý do cho bài này.


5

Đây là nguồn được sao chép từ JDK 9 java.util.stream.Stream.takeWhile (Vị ngữ). Một chút khác biệt để làm việc với JDK 8.

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
    class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
        private static final int CANCEL_CHECK_COUNT = 63;
        private final Spliterator<T> s;
        private int count;
        private T t;
        private final AtomicBoolean cancel = new AtomicBoolean();
        private boolean takeOrDrop = true;

        Taking(Spliterator<T> s) {
            super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
            this.s = s;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean test = true;
            if (takeOrDrop &&               // If can take
                    (count != 0 || !cancel.get()) && // and if not cancelled
                    s.tryAdvance(this) &&   // and if advanced one element
                    (test = p.test(t))) {   // and test on element passes
                action.accept(t);           // then accept element
                return true;
            } else {
                // Taking is finished
                takeOrDrop = false;
                // Cancel all further traversal and splitting operations
                // only if test of element failed (short-circuited)
                if (!test)
                    cancel.set(true);
                return false;
            }
        }

        @Override
        public Comparator<? super T> getComparator() {
            return s.getComparator();
        }

        @Override
        public void accept(T t) {
            count = (count + 1) & CANCEL_CHECK_COUNT;
            this.t = t;
        }

        @Override
        public Spliterator<T> trySplit() {
            return null;
        }
    }
    return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
}

4

Đây là một phiên bản được thực hiện trên ints - như được hỏi trong câu hỏi.

Sử dụng:

StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);

Đây là mã cho StreamUtil:

import java.util.PrimitiveIterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

public class StreamUtil
{
    public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
    {
        return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
    }

    private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
    {
        private final PrimitiveIterator.OfInt iterator;
        private final IntPredicate predicate;

        public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
        {
            super(Long.MAX_VALUE, IMMUTABLE);
            this.iterator = stream.iterator();
            this.predicate = predicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (iterator.hasNext()) {
                int value = iterator.nextInt();
                if (predicate.test(value)) {
                    action.accept(value);
                    return true;
                }
            }

            return false;
        }
    }
}

2

Đi để lấy thư viện AbacusUtil . Nó cung cấp API chính xác mà bạn muốn và hơn thế nữa:

IntStream.iterate(1, n -> n + 1).takeWhile(n -> n < 10).forEach(System.out::println);

Tuyên bố tôi là nhà phát triển của AbacusUtil.


0

Bạn không thể hủy bỏ một luồng ngoại trừ bởi một hoạt động đầu cuối ngắn mạch, điều này sẽ khiến một số giá trị luồng không được xử lý bất kể giá trị của chúng. Nhưng nếu bạn chỉ muốn tránh các hoạt động trên một luồng, bạn có thể thêm một biến đổi và bộ lọc vào luồng:

import java.util.Objects;

class ThingProcessor
{
    static Thing returnNullOnCondition(Thing thing)
    {    return( (*** is condition met ***)? null : thing);    }

    void processThings(Collection<Thing> thingsCollection)
    {
        thingsCollection.stream()
        *** regular stream processing ***
        .map(ThingProcessor::returnNullOnCondition)
        .filter(Objects::nonNull)
        *** continue stream processing ***
    }
} // class ThingProcessor

Điều đó biến luồng của sự vật thành null khi mọi thứ đáp ứng một số điều kiện, sau đó lọc ra null. Nếu bạn sẵn sàng thưởng thức các tác dụng phụ, bạn có thể đặt giá trị điều kiện thành đúng một khi gặp phải điều gì đó, vì vậy tất cả những điều tiếp theo được lọc ra bất kể giá trị của chúng. Nhưng ngay cả khi không, bạn có thể tiết kiệm rất nhiều (nếu không hoàn toàn tất cả) xử lý bằng cách lọc các giá trị ra khỏi luồng mà bạn không muốn xử lý.


Thật khập khiễng rằng một số người vô danh đã đánh giá thấp câu trả lời của tôi mà không nói lý do tại sao. Vì vậy, cả tôi và bất kỳ độc giả nào khác đều không biết câu trả lời của tôi là gì. Trong trường hợp không có lời biện minh của họ, tôi sẽ coi những lời chỉ trích của họ không hợp lệ và câu trả lời của tôi là được đăng là chính xác.
Matthew

Bạn trả lời không giải quyết được vấn đề OP, đó là xử lý các luồng vô hạn. Nó dường như cũng không phức tạp hóa mọi thứ khi bạn có thể viết điều kiện trong bộ lọc () gọi chính nó, mà không cần map (). Câu hỏi đã có mã ví dụ, chỉ cần thử áp dụng câu trả lời của bạn cho mã đó và bạn sẽ thấy chương trình sẽ lặp lại mãi mãi.
SenoCtar

0

Ngay cả tôi cũng có một yêu cầu tương tự - gọi dịch vụ web, nếu thất bại, hãy thử lại 3 lần. Nếu thất bại ngay cả sau nhiều thử nghiệm này, hãy gửi thông báo qua email. Sau khi googling rất nhiều, anyMatch()đến như một vị cứu tinh. Mã mẫu của tôi như sau. Trong ví dụ sau, nếu phương thức webServiceCall trả về true trong lần lặp đầu tiên, thì luồng không lặp lại như chúng ta đã gọi anyMatch(). Tôi tin rằng, đây là những gì bạn đang tìm kiếm.

import java.util.stream.IntStream;

import io.netty.util.internal.ThreadLocalRandom;

class TrialStreamMatch {

public static void main(String[] args) {        
    if(!IntStream.range(1,3).anyMatch(integ -> webServiceCall(integ))){
         //Code for sending email notifications
    }
}

public static boolean webServiceCall(int i){
    //For time being, I have written a code for generating boolean randomly
    //This whole piece needs to be replaced by actual web-service client code
    boolean bool = ThreadLocalRandom.current().nextBoolean();
    System.out.println("Iteration index :: "+i+" bool :: "+bool);

    //Return success status -- true or false
    return bool;
}

0

Nếu bạn biết chính xác số lần sửa chữa sẽ được thực hiện, bạn có thể làm

IntStream
          .iterate(1, n -> n + 1)
          .limit(10)
          .forEach(System.out::println);

1
Trong khi điều này có thể trả lời câu hỏi của tác giả, nó thiếu một số giải thích từ và liên kết đến tài liệu. Đoạn mã thô không hữu ích lắm nếu không có một số cụm từ xung quanh nó. Bạn cũng có thể tìm thấy làm thế nào để viết một câu trả lời tốt rất hữu ích. Vui lòng chỉnh sửa câu trả lời của bạn.
hellow

0
    IntStream.iterate(1, n -> n + 1)
    .peek(System.out::println) //it will be executed 9 times
    .filter(n->n>=9)
    .findAny();

thay vì cao điểm, bạn có thể sử dụng mapToObj để trả về đối tượng hoặc tin nhắn cuối cùng

    IntStream.iterate(1, n -> n + 1)
    .mapToObj(n->{   //it will be executed 9 times
            if(n<9)
                return "";
            return "Loop repeats " + n + " times";});
    .filter(message->!message.isEmpty())
    .findAny()
    .ifPresent(System.out::println);

-2

Nếu bạn có vấn đề khác, có thể cần giải pháp khác nhưng đối với vấn đề hiện tại của bạn, tôi chỉ cần đi với:

IntStream
    .iterate(1, n -> n + 1)
    .limit(10)
    .forEach(System.out::println);

-2

Có thể hơi lạc đề nhưng đây là những gì chúng ta có List<T>thay vìStream<T> .

Trước tiên, bạn cần phải có một takephương pháp sử dụng. Phương pháp này có ncác yếu tố đầu tiên :

static <T> List<T> take(List<T> l, int n) {
    if (n <= 0) {
        return newArrayList();
    } else {
        int takeTo = Math.min(Math.max(n, 0), l.size());
        return l.subList(0, takeTo);
    }
}

nó chỉ hoạt động như scala.List.take

    assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3, 4, 5), 3));
    assertEquals(newArrayList(1, 2, 3), take(newArrayList(1, 2, 3), 5));

    assertEquals(newArrayList(), take(newArrayList(1, 2, 3), -1));
    assertEquals(newArrayList(), take(newArrayList(1, 2, 3), 0));

bây giờ sẽ khá đơn giản để viết một takeWhilephương thức dựa trêntake

static <T> List<T> takeWhile(List<T> l, Predicate<T> p) {
    return l.stream().
            filter(p.negate()).findFirst(). // find first element when p is false
            map(l::indexOf).        // find the index of that element
            map(i -> take(l, i)).   // take up to the index
            orElse(l);  // return full list if p is true for all elements
}

nó hoạt động như thế này:

    assertEquals(newArrayList(1, 2, 3), takeWhile(newArrayList(1, 2, 3, 4, 3, 2, 1), i -> i < 4));

việc thực hiện này lặp lại một phần danh sách một vài lần nhưng nó sẽ không thêm các O(n^2)hoạt động. Hy vọng đó là chấp nhận được.


-3

Tôi có một giải pháp nhanh chóng khác bằng cách thực hiện điều này (thực tế là không sạch sẽ, nhưng bạn có ý tưởng):

public static void main(String[] args) {
    System.out.println(StreamUtil.iterate(1, o -> o + 1).terminateOn(15)
            .map(o -> o.toString()).collect(Collectors.joining(", ")));
}

static interface TerminatedStream<T> {
    Stream<T> terminateOn(T e);
}

static class StreamUtil {
    static <T> TerminatedStream<T> iterate(T seed, UnaryOperator<T> op) {
        return new TerminatedStream<T>() {
            public Stream<T> terminateOn(T e) {
                Builder<T> builder = Stream.<T> builder().add(seed);
                T current = seed;
                while (!current.equals(e)) {
                    current = op.apply(current);
                    builder.add(current);
                }
                return builder.build();
            }
        };
    }
}

2
Bạn đang đánh giá toàn bộ luồng trước! Và nếu currentkhông bao giờ .equals(e), bạn sẽ có được một vòng lặp vô tận. Cả hai ngay cả khi bạn sau đó áp dụng, ví dụ .limit(1). Điều đó tồi tệ hơn nhiều so với 'ô uế' .
charlie

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.