Is it possible to create in Java 8 a unlimitedly growing in lazy way collection, defined by recursion?

前端 未结 3 701
心在旅途
心在旅途 2021-01-05 03:14

I can create a recursive closure:

static IntUnaryOperator fibo;
fibo = 
    (i) -> 
    i<2 ? 1 : fibo.applyAsInt(i-1)+ fibo.applyAsInt(i-2);
         


        
3条回答
  •  轻奢々
    轻奢々 (楼主)
    2021-01-05 03:33

    It seems you are asking for something like this:

    public class Fibonacci extends AbstractList {
        @Override
        public Stream stream() {
            return Stream.iterate(new BigInteger[]{ BigInteger.ONE, BigInteger.ONE },
               p->new BigInteger[]{ p[1], p[0].add(p[1]) }).map(p -> p[0]);
        }
        @Override
        public Iterator iterator() {
            return stream().iterator();
        }
        @Override
        public int size() {
            return Integer.MAX_VALUE;
        }
        @Override
        public BigInteger get(int index) {
            return stream().skip(index).findFirst().get();
        }
    }
    

    It’s accessible via the List interface (it doesn’t implement RandomAccess for a good reason), thus, you may ask for the n’th value via get(n). Note that the implementation of get hints how you can get values at positions after Integer.MAX_VALUE. Just use stream().skip(position).findFirst().get().

    Beware! This list is infinite, as you asked for. Don’t ask it for things that operate on all elements, e.g. not even toString(). But things like the following will work smoothly:

    System.out.println(new Fibonacci().subList(100, 120));
    

    or

    for(BigInteger value: new Fibonacci()) {
        System.out.println(value);
        if(someCondition()) break;
    }   
    

    However, when you have to process large sequences of elements and want to do it efficiently, you should ensure to work on the iterator or stream to avoid O(n²) complexity of repeated get calls.

    Note that I changed the element type to BigInteger as it would be pointless to think about infinite streams when it comes to the Fibonacci sequence and the int or long value type. Even with the long value type, the sequence is over after only 92 values as then, overflow occurs.


    Update: now that you made clear that you are looking for a lazy storage, you may change the class above as follows:

    public class Fibonacci extends AbstractList {
        final Map values=new HashMap<>();
    
        public Fibonacci() {
            values.put(BigInteger.ONE, BigInteger.ONE);
            values.put(BigInteger.ZERO, BigInteger.ONE);
        }
    
        @Override
        public BigInteger get(int index) {
            return get(BigInteger.valueOf(index));
        }
        public BigInteger get(BigInteger index) {
            return values.computeIfAbsent(index, ix ->
                get(ix=ix.subtract(BigInteger.ONE)).add(get(ix.subtract(BigInteger.ONE))));
        }
    
        @Override
        public Stream stream() {
            return Stream.iterate(BigInteger.ZERO, i->i.add(BigInteger.ONE)).map(this::get);
        }
        @Override
        public Iterator iterator() {
            return stream().iterator();
        }
        @Override
        public int size() {
            return Integer.MAX_VALUE;
        }
    }
    

    I used BigInteger as key/index here to fulfill the requirement to be (theoretically) infinite, though we can use a long key as well for all practical uses. The key point is the initially empty storage: (now exemplary using long):

    final Map values=new HashMap<>();
    

    which is pre-initialized with the values that should end each recursion (unless it ends earlier due to already computed values):

    values.put(1L, BigInteger.ONE);
    values.put(0L, BigInteger.ONE);
    

    Then, we can ask for a lazily computed value via:

    public BigInteger get(long index) {
        return values.computeIfAbsent(index, ix -> get(ix-1).add(get(ix-2)));
    }
    

    or a stream delegating to the get method described above:

    LongStream.range(0, Long.MAX_VALUE).mapToObj(this::get);
    

    This creates a stream that is only “practically infinite” whereas the complete example class above, using BigInteger is theoretically infinite…

    The Map will remember every computed value of the sequence.

提交回复
热议问题