Bir akışı yüklemle sınırlama


187

(Potansiyel olarak sonsuz) sınırlayan bir Java 8 akışı işlemi var mı Streamİlk öğe bir yüklemle eşleşene kadar mı?

Java 9'da takeWhile10'dan küçük tüm sayıları yazdırmak için aşağıdaki örnekte olduğu gibi kullanabiliriz .

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

Java 8'de böyle bir işlem olmadığından, genel olarak uygulamanın en iyi yolu nedir?


1
Olası faydalı bilgiler
şurada


Mimarların bu kullanım alanına girmeden " bunu gerçekten ne için kullanabiliriz? " Java 8'den itibaren Akışlar sadece mevcut veri yapıları için gerçekten yararlıdır: - /
Thorbjørn Ravn Andersen


Java 9 ile yazmak daha kolay olurduIntStream.iterate(1, n->n<10, n->n+1).forEach(System.out::print);
Marc Dzaebel

Yanıtlar:


81

Böyle bir işlem bir Java 8 ile mümkün olmalıdır Stream, ancak verimli bir şekilde yapılamaz - örneğin, öğelere sırayla bakmak zorunda olduğunuz için böyle bir işlemi mutlaka paralelleştiremezsiniz.

API bunu yapmak için kolay bir yol sağlamaz, ancak muhtemelen en basit yol, "take-while" uygulamasına sahip olmak için Stream.iterator()sarın ve sonra a Iteratorve sonra a'ya geri Spliteratordönmektir Stream. Veya - belki - sarın Spliterator, ancak bu uygulamada artık bölünemez.

İşte takeWhilea üzerinde denenmemiş bir uygulama 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
Teoride, takeWhile'ı durum bilgisi olmayan bir yüklemle paralel hale getirmek kolaydır. Koşulu paralel gruplar halinde değerlendirin (yüklemin fazladan bir süre gerçekleştirilmezse yan etkisi olmadığını varsayarak). Sorun, bunu Akışların kullandığı özyinelemeli ayrışma (çatal / birleştirme çerçevesi) bağlamında yapmaktır. Gerçekten, korkunç derecede verimsiz olan Akışlar.
Aleksandr Dubinsky

91
Eğer otomajik paralellikle meşgul olmasalardı akışlar çok daha iyi olurdu. Paralellik, Akımların kullanılabileceği yerlerin yalnızca küçük bir kısmında gereklidir. Üstelik, Oracle perfoans konusunda çok fazla önemsiyorsa, JVM JIT'i otomatikleştirebilir ve geliştiricileri rahatsız etmeden çok daha büyük bir performans artışı elde edebilirlerdi. Şimdi bu otomajik paralellik doğru yapılmış.
Aleksandr Dubinsky

Java 9 yayınlandığında bu yanıtı güncellemelisiniz.
Radiodef

4
Hayır, @Radiodef. Soru, özellikle bir Java 8 çözümü istiyor.
Renato Back

145

İşlemler takeWhileve dropWhileJDK 9'a eklenmiştir. Örnek kodunuz

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

JDK 9 altında derlendiğinde ve çalıştırıldığında beklediğiniz gibi davranacaktır.

JDK 9 yayınlandı. Buradan indirebilirsiniz: http://jdk.java.net/9/


3
JDK9 Stream için önizleme belgelerine doğrudan bağlantı : takeWhile/ dropWhile: download.java.net/jdk9/docs/api/java/util/stream/Stream.html
Miles

1
Dedikleri bir nedeni var mı takeWhileve dropWhileyerine limitWhileve skipWhileAPI mevcut olan tutarlılık için,?
Lukas Eder

10
@LukasEder takeWhileve dropWhileScala, Python, Groovy, Ruby, Haskell ve Clojure'da oldukça yaygındır. İle asimetri skipve limittalihsiz. Belki skipve limitdenilen gerektiğini düşündü dropve takeama zaten Haskell aşina değilseniz bu sezgisel olarak değil.
Stuart Marks

3
@StuartMarks: Bunu anlıyorum dropXXXve takeXXXdaha popüler terimler ama kişisel olarak daha fazla SQL-esque limitXXXve ile yaşayabilirim skipXXX. Bu yeni asimetriyi, terimlerin bireysel seçiminden çok daha kafa karıştırıcı buluyorum ... :) (btw: Scala'da da var drop(int)ve take(int))
Lukas Eder

1
evet sadece üretimde Jdk 9'a geçmeme izin verin. Birçok geliştirici hala Jdk8'de, böyle bir özellik başlangıçtan itibaren Akışlar'a dahil edilmelidir.
wilmol

50

allMatch()kısa devre işlevidir, bu nedenle işlemeyi durdurmak için kullanabilirsiniz. Ana dezavantaj, testinizi iki kez yapmanız gerektiğidir: bir kez test edip etmeyeceğinizi ve tekrar devam edip etmeyeceğinizi görmek için.

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

5
Bu (yöntem adı verilen) ilk başta bana unintuitive görünüyordu, ama dokümanlar onaylamak o Stream.allMatch()bir olan kısa devre işlemi . Böylece bu, sonsuz bir akışta bile tamamlanacaktır IntStream.iterate(). Tabii ki, geçmişe bakıldığında, bu mantıklı bir optimizasyon.
Bailey Parker

3
Bu temiz, ama niyetinin bedeni olduğu çok iyi iletişim kurduğunu sanmıyorum peek. Önümüzdeki ay karşılaşırsam, benden önce programcının neden kontrol edip allMatchsonra cevabı görmezden geldiğini merak etmek için bir dakikanızı ayırırdım.
Joshua Goldberg

10
Bu çözümün dezavantajı, bir boole döndürmesi, böylece akışın sonuçlarını normal şekilde toplayamamanızdır.
neXus

35

@StuartMarks cevabının takibi olarak . Benim StreamEx kütüphanesi vardır takeWhileşimdiki JDK-9 uygulamasıyla uyumlu çalışmasını. JDK-9 altında çalışırken sadece JDK uygulamasına ( MethodHandle.invokeExactki bu gerçekten hızlıdır) delege edecektir . JDK-8 altında çalışırken, "çoklu dolgu" uygulaması kullanılacaktır. Bu yüzden kütüphanemi kullanarak sorun şu şekilde çözülebilir:

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

Neden StreamEx sınıfı için uygulamadınız?
Someguy

@Someguy Ben uyguladım.
Tagir Valeev

14

takeWhileprotonpack kütüphanesi tarafından sağlanan işlevlerden biridir .

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

Güncelleme: Java 9 Streamartık bir takeWhile yöntemiyle geliyor .

Saldırılara veya diğer çözümlere gerek yok. Sadece kullan!


Bu büyük ölçüde geliştirilebilir eminim: (birisi belki iplik güvenli yapabilir)

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

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

Kesinlikle bir kesmek ... Zarif değil - ama işe yarıyor ~: 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

Java8 + rxjava kullanabilirsiniz .

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

Aslında bunu fazladan kitaplık olmadan veya Java 9 kullanarak Java 8'de yapmanın 2 yolu vardır.

Konsolda 2 ila 20 arasındaki sayıları yazdırmak istiyorsanız, bunu yapabilirsiniz:

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

veya

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

Çıktı her iki durumda da:

2
4
6
8
10
12
14
16
18
20

Hiç kimse söz anyMatch henüz. Bu yazının nedeni budur.


5

JDK 9 java.util.stream.Stream.takeWhile (Predicate) kaynağından kopyalanan kaynak budur. JDK 8 ile çalışmak için küçük bir fark.

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

İşte ints üzerinde yapılan bir versiyon - soruda sorulduğu gibi.

Kullanımı:

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

StreamUtil için kod:

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

AbacusUtil kütüphanesine git . Tam olarak istediğiniz API'yı ve daha fazlasını sağlar:

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

Beyan : AbacusUtil'in geliştiricisiyim.


0

Kısa devre terminali işlemi dışında bir akışı iptal edemezsiniz. Bu değer, değerlerine bakılmaksızın bazı akış değerlerinin işlenmemiş olmasını sağlar. Ancak yalnızca bir akıştaki işlemlerden kaçınmak istiyorsanız akışa bir dönüşüm ve filtre ekleyebilirsiniz:

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

Bu, bir koşulla karşılaştığında nesnelerin akışını null değerine dönüştürür, sonra null'ları filtreler. Yan etkileri şımartmak istiyorsanız, bir şeyle karşılaşıldığında koşul değerini true olarak ayarlayabilirsiniz, böylece sonraki tüm şeyler değerlerinden bağımsız olarak filtrelenir. Ancak olmasa bile, değerleri işlemek istemediğiniz akıştan filtreleyerek çok fazla (tamamen olmasa da) işlem kaydedebilirsiniz.


Anonim bir değerlendirmenin cevabımı nedenini söylemeden küçümsemesi çok üzücü. Yani ne ben ne de başka bir okuyucu cevabımın neyin yanlış olduğunu bilmiyor. Gerekçelerinin yokluğunda eleştirilerinin geçersiz olduğunu ve cevabımın doğru olduğu şeklinde yorumlayacağım.
Matthew

Cevap, sonsuz akışlarla uğraşan OP problemini çözmez. Ayrıca, şuna () çağrısında haritayı () gerek kalmadan kendisini çağırmak gibi bir şeyleri gereksiz yere zorlaştırıyor gibi görünüyor. Sorunun zaten bir örnek kodu var, cevabınızı bu koda uygulamaya çalışın ve programın sonsuza kadar döneceğini göreceksiniz.
SenoCtar

0

Benzer bir gereksinimim olsa bile - web hizmetini çağırın, başarısız olursa, 3 kez tekrar deneyin. Bu çok sayıda denemeden sonra bile başarısız olursa, bir e-posta bildirimi gönderin. Çok fazla çalıştıktan sonra anyMatch()bir kurtarıcı olarak geldi. Örnek kodum aşağıdaki gibidir. Aşağıdaki örnekte, webServiceCall yöntemi ilk yinelemenin kendisinde true değerini döndürürse, akış çağrıldığımız gibi daha fazla yinelenmez anyMatch(). İnanıyorum, aradığınız şey bu.

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

Gerçekleştirilecek tekrar sayısını kesin olarak biliyorsanız,

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

1
Bu, yazarların sorusunu cevaplayabilirken, bazı açıklayıcı kelimeler ve belgelere bağlantılar yoktur. Ham kod parçacıkları, çevresinde bazı ifadeler olmadan çok yararlı değildir. Ayrıca bulabilirsiniz iyi bir cevap yazmayı çok yararlı. Lütfen cevabınızı düzenleyin.
hellow

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

tepe yerine son nesneyi veya iletiyi döndürmek için mapToObj kullanabilirsiniz

    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

Farklı bir sorununuz varsa, farklı bir çözüme ihtiyaç duyulabilir, ancak mevcut sorununuz için basitçe:

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

-2

Biraz konu dışı olabilir, ama bu bizim için List<T>değilStream<T> .

İlk önce bir takeutil yöntemine sahip olmanız gerekir . Bu yöntemler ilk nunsurları alır :

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);
    }
}

sadece gibi çalışıyor 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));

şimdi takeWhiledayalı bir yöntem yazmak oldukça basit olacaktake

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
}

şu şekilde çalışır:

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

bu uygulama listeyi birkaç kez tekrarlar, ancak ekleme O(n^2)işlemi eklemez . Umarım bu kabul edilebilir.


-3

Bunu uygulayarak hızlı bir çözüm daha var (aslında kirli değil, ama fikir olsun):

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
Tüm akışı önceden değerlendiriyorsunuz ! Ve currentasla olmazsa .equals(e), sonsuz bir döngü elde edersiniz. Her ikisi de daha sonra başvursanız bile örn .limit(1). Bu 'kirli' olmaktan çok daha kötü .
charlie

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.