Providing Limit condition on Stream generation [du

2019-01-28 09:44发布

问题:

This question already has an answer here:

  • Limit a stream by a predicate 18 answers

I am writing a code to calculate Fibonacci numbers. With this code I can generate first n numbers of the Fibonacci sequence.

Stream.generate(new Supplier<Long>() {
    private long n1 = 1;
    private long n2 = 2;

    @Override
    public Long get() {
        long fibonacci = n1;
        long n3 = n2 + n1;
        n1 = n2;
        n2 = n3;
        return fibonacci;
    }
}).limit(50).forEach(System.out::println);

The method limit returns the Stream which holds the number of elements passed to this method. I want to stop the generation of the Stream after the Fibonacci number reached some value.

I mean if I want to list all Fibonacci numbers less than 1000 then I cannot use limit, because I don't know how many Fibonacci numbers there could be.

Is there any way to do this using lambda expressions?

回答1:

If you don't mind using an iterator, you can write it as:

static LongUnaryOperator factorial = x -> x == 0 ? 1
                                      : x * factorial.applyAsLong(x - 1);

public static void main(String[] args) {
    LongStream ls = LongStream.iterate(0, i -> i + 1).map(factorial);
    OfLong it = ls.iterator();
    long next = 0;
    while ((next = it.nextLong()) <= 1000) System.out.println(next);
}


回答2:

The best solution using the Stream’s built-in features I could find is:

LongStream.generate(new LongSupplier() {
  private long n1 = 1, n2 = 2;

  public long getAsLong() {
      long fibonacci = n1;
      long n3 = n2 + n1;
      n1 = n2;
      n2 = n3;
      return fibonacci;
  }
}).peek(System.out::println).filter(x->x>1000).findFirst();

It has the disadvantage of processing the first item being >=1000 though. This can be prevented by making the statement conditional, e.g.

.peek(x->{if(x<=1000) System.out.println(x);}).filter(x->x>1000).findFirst();

but I don’t like to evaluate the same condition (bigger than thousand or not) twice. But maybe one of these two solution might be practical enough for real life tasks where a limit based on the resulting value is needed.

I think, it’s clear that the entire construct is not parallel capable…



回答3:

Yes, there is a lambda way but unfortunately, I don't think it is implemented in the current Java 8 Stream API. Sorry to point you to a different language, but what I think you want is something like

 takeWhile(p: (A) ⇒ Boolean): Stream[A]

from the Scala Stream API.

As this is not implemented in the Java API, you have to do it yourself. How about this one:

public static List<T> takeWhile(Iterable<T> elements, Predicate<T> predicate) {
   Iterator<T> iter = elements.iterator();
   List<T> result = new LinkedList<T>();
   while(iter.hasNext()) {
     T next = iter.next();
     if (predicate.apply(next)) {
       result.add(next);
     } else {
       return result;  // Found first one not matching: abort
     }
   }
   return result;  // Found end of the elements
}

Then you could use it like

List<Long> fibNumbersUnderThousand = takeWhile(allFibNumStream, l -> l < 1000);

(Assuming that Stream is an instance of Iterable - if not, you might need to call the .iterator() method and wrap that up)



回答4:

Dirty first version

        Stream.generate(new Supplier<Long>() {
        private long n1 = 1;
        private long n2 = 2;

        @Override
        public Long get() {
            long fibonacci = n1;
            long n3 = n2 + n1;
            n1 = n2;
            n2 = n3;
            return fibonacci;
        }
    }).limit(50).forEach(x -> {
        if (x < 1000) {
            System.out.println(x);
        }
    });