2015-11-20 4 views
7

मैं एक पुनरावर्ती बंद बना सकते हैं:क्या जावा 8 में रिकर्सन द्वारा परिभाषित आलसी (बेहतर अनंत) संग्रह बनाना संभव है?

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

लेकिन निश्चित रूप से, यह केवल एक उदाहरण के रूप भावना है। इसके विपरीत, यदि मैंने आलसी/अनंत सूची/स्ट्रीम बनाई है, तो रिकर्सन का उपयोग बहुत अच्छे तरीके से किया जा सकता है: किसी भी सदस्य को एक से अधिक बार गणना नहीं की जानी चाहिए।

मैं निम्नलिखित निर्माण के बारे में सोचा:

IntStream fi; 
fi=IntStream.iterate(0, i -> fi[i-1]+fi[i-2]); 

लेकिन वह जिस तरह से यह काम नहीं करेगा - मैं index.The अन्य समस्या से धारा से कोई आइटम नहीं मिल सकता है, तो मैं बाद में हूँ वह यह है कि धारा के साथ जाओ, यह खाया जाएगा और मैं इसे बार-बार उपयोग नहीं कर सकता। यदि मैं स्ट्रीम को सूची में कॉपी करता हूं, तो अब यह आलसी नहीं है।

नतीजतन, मुझे कुछ निर्माण की आवश्यकता है जिसे मैं इंडेक्स द्वारा संबोधित कर सकता हूं। fibo(i) के रूप में।

संपादित करें। जाहिर है, समाधान एक धारा नहीं हो सकता है, क्योंकि स्ट्रीम को दो बार उपयोग नहीं किया जा सकता है। मैं एफ (i) में प्रत्येक कॉल पर सभी गणना दोहराना नहीं चाहता हूं।

+0

शायद आप 'स्प्लिटरेटर.ऑफिंट' – fge

+0

@fge बनाना चाहते हैं, मुझे अनुक्रमिक पहुंच की आवश्यकता नहीं है। यह एक सरणी की पीढ़ी के द्वारा आसान किया जा सकता है। – Gangnus

+2

एक सरणी परिभाषा से आलसी नहीं है और अनंत भी नहीं हो सकती है; मुझे यकीन नहीं है कि आप परिणाम के रूप में क्या पूछ रहे हैं – fge

उत्तर

0

समाधान एक आंशिक तर्क के साथ लैम्ब्डा फ़ंक्शन द्वारा परिभाषित ऑब्जेक्ट्स के आलसी, अनंत अनुक्रम के प्रतिनिधित्व के लिए FunctionalSequence के रूप में बनाया जाएगा। समारोह पुनरावृत्त हो सकता है या नहीं। पुनरावृत्त मामले के लिए FunctionalSequence कक्षा में प्रारंभ मान सेट करने के लिए initialize विधि होगी।

ऐसे वर्ग की एक वस्तु की घोषणा तो दिखेगा:

FunctionalSequence<BigInteger> fiboSequence = new FunctionalSequence<>(); 
    fiboSequence. 
     initialize(Stream.of(BigInteger.ONE,BigInteger.ONE)). 
     setSequenceFunction(
      (i) -> 
      fiboSequence.get(i-2).add(fiboSequence.get(i-1)) 
     ); 

सूचना, सवाल में पुनरावर्ती लैम्ब्डा उदाहरण के रूप में, हम वस्तु की घोषणा नहीं कर सकते हैं और एक ऑपरेटर में रिकर्सिवली यह परिभाषित करते हैं। घोषणा के लिए एक ऑपरेटर, परिभाषा के लिए दूसरा।

FunctionalSequence वर्ग परिभाषा:

import java.util.Iterator; 
import java.util.LinkedList; 
import java.util.stream.Stream; 

public class FunctionalSequence<T> implements Iterable<T>{ 
    LinkedList<CountedFlighweight<T>> realList = new LinkedList<>(); 
    StackOverflowingFunction<Integer, T> calculate = null; 
    public FunctionalSequence<T> initialize(Stream<T> start){ 
     start.forEachOrdered((T value) -> 
     { 
       realList.add(new CountedFlighweight<>()); 
       realList.getLast().set(value); 
     }); 
     return this; 
    } 
    public FunctionalSequence<T> setSequenceFunction(StackOverflowingFunction<Integer, T> calculate){ 
     this.calculate = calculate; 
     return this; 
    } 

    @Override 
    public Iterator<T> iterator() { 
     return new SequenceIterator(); 
    } 
    public T get(int currentIndex) throws StackOverflowError{ 
     if(currentIndex < 0) return null; 
     while (currentIndex >= realList.size()){ 
      realList.add(new CountedFlighweight<T>()); 
     } 
     try { 
      return (T) realList.get(currentIndex).get(calculate, currentIndex); 
     } catch (Exception e) { 
      return null; 
     } 
    } 
    public class SequenceIterator implements Iterator<T>{ 
     int currentIndex; 
     @Override 
     public boolean hasNext() { 
      return true; 
     } 

     @Override 
     public T next() { 
      T result = null; 
      if (currentIndex == realList.size()){ 
       realList.add(new CountedFlighweight<T>()); 
      } 
      // here the StackOverflowError catching is a pure formality, by next() we would never cause StackOverflow 
      try { 
       result = realList.get(currentIndex).get(calculate, currentIndex); 
      } catch (StackOverflowError e) { 
      } 
      currentIndex++; 
      return result; 
     } 

    } 
    /** 
    * if known is false, the value of reference is irrelevant 
    * if known is true, and reference is not null, reference contains the data 
    * if known is true, and reference is null, that means, that the appropriate data are corrupted in any way 
    * calculation on corrupted data should result in corrupted data. 
    * @author Pet 
    * 
    * @param <U> 
    */ 
    public class CountedFlighweight<U>{ 
     private boolean known = false; 
     private U reference; 
     /** 
     * used for initial values setting 
     */ 
     private void set(U value){ 
      reference = value; 
      known = true; 
     } 
     /** 
     * used for data retrieval or function counting and data saving if necessary 
     * @param calculate 
     * @param index 
     * @return 
     * @throws Exception 
     */ 
     public U get(StackOverflowingFunction<Integer, U> calculate, int index) throws StackOverflowError{ 
      if (! known){ 
       if(calculate == null) { 
        reference = null; 
       } else { 
        try { 
         reference = calculate.apply(index); 
        } catch (Exception e) { 
         reference = null; 
        } 
       } 
      } 
      known = true; 
      return reference; 
     } 
    } 

    @FunctionalInterface 
    public interface StackOverflowingFunction <K, U> { 
     public U apply(K index) throws StackOverflowError; 

    } 
} 

के रूप में पुनरावर्ती क्रिया आसानी से StackOverflowError को पूरा कर सकता है, हम इतना है कि उस मामले में पूरे पुनरावर्ती अनुक्रम वास्तव में मुलाकात की किसी भी परिवर्तन और फेंक बिना वापस रोल होगा प्रत्यावर्तन व्यवस्थित करना चाहिए अपवाद।

FunctionalSequence के उपयोग तो दे सकता है:

// by iterator: 
    int index=0; 
    Iterator<BigInteger> iterator = fiboSequence.iterator(); 
    while(index++<10){ 
     System.out.println(iterator.next()); 
    } 

या तो:

static private void tryFibo(FunctionalSequence<BigInteger> fiboSequence, int i){ 
    long startTime = System.nanoTime(); 
    long endTime; 
    try { 
     fiboSequence.get(i); 
     endTime = System.nanoTime(); 
     System.out.println("repeated timing for f("+i+")=" + (endTime-startTime)/1000000.+" ns"); 
    } catch (StackOverflowError e) { 
     endTime = System.nanoTime(); 
     //e.printStackTrace(); 
     System.out.println("failed counting f("+i+"), time=" + (endTime-startTime)/1000000.+" ns"); 
    }  
} 

पिछले समारोह निम्नलिखित तरीके से इस्तेमाल किया जा सकता:

tryFibo(fiboSequence, 1100); 
    tryFibo(fiboSequence, 100); 
    tryFibo(fiboSequence, 100); 
    tryFibo(fiboSequence, 200); 
    tryFibo(fiboSequence, 1100); 
    tryFibo(fiboSequence, 2100); 
    tryFibo(fiboSequence, 2100); 
    tryFibo(fiboSequence, 1100); 
    tryFibo(fiboSequence, 100); 
    tryFibo(fiboSequence, 100); 
    tryFibo(fiboSequence, 200); 
    tryFibo(fiboSequence, 1100); 

यहाँ परिणाम (परीक्षण की जरूरतों के लिए ढेर 256K तक सीमित था):

1 
1 
2 
3 
5 
8 
13 
21 
34 
55 
failed counting f(1100), time=3.555689 ns 
repeated timing for f(100)=0.213156 ns 
repeated timing for f(100)=0.002444 ns 
repeated timing for f(200)=0.266933 ns 
repeated timing for f(1100)=5.457956 ns 
repeated timing for f(2100)=3.016445 ns 
repeated timing for f(2100)=0.001467 ns 
repeated timing for f(1100)=0.005378 ns 
repeated timing for f(100)=0.002934 ns 
repeated timing for f(100)=0.002445 ns 
repeated timing for f(200)=0.002445 ns 
repeated timing for f(1100)=0.003911 ns 

देखो, उसी सूचकांक के लिए f (i) की दोहराने योग्य कॉल व्यावहारिक रूप से कोई समय नहीं लेती - कोई पुनरावृत्ति नहीं किया गया था। हम StackOverflowError की वजह से एक बार में (1100) तक नहीं पहुंच सकते हैं। लेकिन हम एक बार एफ (200) तक पहुंचने के बाद, एफ (1100) पहुंच योग्य हो जाते हैं। हमने इसे बनाया!

3

मैं एक अच्छा सामान्य समाधान अप में सोच भी नहीं सकते हैं, लेकिन आप विशेष रूप से पिछले दो तत्वों का उपयोग करना चाहते हैं, यह इस तरह कस्टम Spliterator को परिभाषित करने के लिए काफी आसान तरीके से किया जा सकता है:

public static IntStream iterate(int first, int second, IntBinaryOperator generator) { 
    Spliterator.OfInt spliterator = new AbstractIntSpliterator(Long.MAX_VALUE, 
              Spliterator.ORDERED) { 
     int prev1 = first, prev2 = second; 
     int pos = 0; 

     @Override 
     public boolean tryAdvance(IntConsumer action) { 
      if(pos < 2) { 
       action.accept(++pos == 1 ? prev1 : prev2); 
      } else { 
       int next = generator.applyAsInt(prev1, prev2); 
       prev1 = prev2; 
       prev2 = next; 
       action.accept(next); 
      } 
      return true; 
     } 
    }; 
    return StreamSupport.intStream(spliterator, false); 
} 

उपयोग:

iterate(1, 1, Integer::sum).limit(20).forEach(System.out::println); 
+0

1. यह उपयोग से खपत है। 2. उपयोग बंद करने के लिए पूरे कोड की तुलना में उपयोग केवल थोड़ा छोटा है। – Gangnus

+0

और अगर मुझे fibo (i) की आवश्यकता है तो मैं इसका उपयोग कैसे करूं? – Gangnus

+1

@ गैंगनस, 'इटरेट (1, 1, इंटीजर :: योग) .skip (i-1) .findFirst()।() 'प्राप्त करें। –

5

ऐसा लगता है आप कुछ इस तरह के लिए पूछ रहे हैं:

public class Fibonacci extends AbstractList<BigInteger> { 
    @Override 
    public Stream<BigInteger> 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<BigInteger> iterator() { 
     return stream().iterator(); 
    } 
    @Override 
    public int size() { 
     return Integer.MAX_VALUE; 
    } 
    @Override 
    public BigInteger get(int index) { 
     return stream().skip(index).findFirst().get(); 
    } 
} 

यह List इंटरफ़ेस के माध्यम से सुलभ है (यह RandomAccess को किसी अच्छे कारण से लागू नहीं करता है), इस प्रकार, आप get(n) के माध्यम से n'th मान मांग सकते हैं। ध्यान दें कि get का कार्यान्वयन संकेत देता है कि Integer.MAX_VALUE के बाद आप पदों पर मूल्य कैसे प्राप्त कर सकते हैं। बस stream().skip(position).findFirst().get() का उपयोग करें।

सावधान रहें! जैसा कि आपने पूछा था, यह सूची अनंत है। उन सभी चीजों के लिए मत पूछें जो सभी तत्वों पर काम करते हैं, उदा। toString() भी नहीं। लेकिन निम्नलिखित की तरह चीजें आसानी से काम करेगा:

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

या

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

हालांकि, अगर आप तत्वों की बड़ी दृश्यों कार्रवाई करने के लिए है और यह कुशलता से करना चाहते हैं, तो आप पुनरावर्तक पर काम करने के लिए यह सुनिश्चित करना चाहिए या O(n²) बार-बार get कॉल की जटिलता से बचने के लिए स्ट्रीम करें।

ध्यान दें कि मैं BigInteger के तत्व प्रकार बदल के रूप में यह अनंत धाराओं के बारे में सोचना जब यह फाइबोनैचि अनुक्रम और int या long मान प्रकार की बात आती है व्यर्थ होगा। long मान प्रकार के साथ भी, अनुक्रम केवल 92 मानों के बाद खत्म हो गया है, ओवरफ़्लो होता है।


अपडेट: अब है कि आप स्पष्ट है कि आप एक आलसी भंडारण लिए देख रहे हैं बनाया है, आप श्रेणी से ऊपर के रूप में निम्नानुसार बदल सकती है:

public class Fibonacci extends AbstractList<BigInteger> { 
    final Map<BigInteger,BigInteger> 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<BigInteger> stream() { 
     return Stream.iterate(BigInteger.ZERO, i->i.add(BigInteger.ONE)).map(this::get); 
    } 
    @Override 
    public Iterator<BigInteger> iterator() { 
     return stream().iterator(); 
    } 
    @Override 
    public int size() { 
     return Integer.MAX_VALUE; 
    } 
} 

मैं करने के लिए यहाँ कुंजी/सूचकांक के रूप में इस्तेमाल किया BigInteger (सैद्धांतिक रूप से) अनंत होने की आवश्यकता को पूरा करें, हालांकि हम सभी व्यावहारिक उपयोगों के लिए long कुंजी का उपयोग कर सकते हैं। (अब अनुकरणीय का उपयोग कर long):

final Map<Long,BigInteger> values=new HashMap<>(); 

जो मूल्यों है कि प्रत्येक प्रत्यावर्तन खत्म होना चाहिए (जब तक यह पहले की वजह से पहले से ही गणना की मूल्यों समाप्त होता है) के साथ पहले से प्रारंभ है: प्रमुख मुद्दा शुरू में खाली भंडारण है

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

फिर, हम के माध्यम से एक lazily अभिकलन मूल्य के लिए पूछ सकते हैं:

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

या एक धारा get विधि को सौंपने वर्णित अब ove:

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

यह एक धारा है कि केवल "व्यावहारिक रूप से अनंत" जबकि इसके बाद के संस्करण पूर्ण उदाहरण वर्ग है बनाता है, का उपयोग कर BigInteger सैद्धांतिक रूप से अनंत है ...

Map अनुक्रम के हर परिकलित मान याद रखेंगे।

+0

मुझे बहुत खेद है, यह कोड का एक बहुत ही दिलचस्प टुकड़ा है, लेकिन मैं इसके बारे में बात नहीं कर रहा हूं। अगर हम वास्तव में एक आलसी संग्रह था, तो रिकर्सन केवल एक बार इसकी किसी भी वस्तु की गणना करेगा। तो, जटिलता ओ (एन) पर सबसे खराब होगी, अगर हमने एफ (एन) के लिए पहली बार पूछा था। – Gangnus

+0

क्योंकि धारा का उपभोग होता है, इस कोड में जटिलता ओ (एन) हर समय हम एफ (एन) के लिए पूछते हैं। यह बहुत दूर है इष्टतम रूप है। – Gangnus

+0

@ गंगनस: एक धारा भंडारण नहीं है। यहां तक ​​कि यदि आप कई बार स्ट्रीम का उपयोग कर सकते हैं, तो यह एक स्टोरेज में नहीं बदलेगा, प्रत्येक क्वेरी में अभी भी 'ओ (एन) 'था। यदि आप गणना मूल्यों * स्टोर करना चाहते हैं, तो आपको इसे अपने प्रश्न में शामिल करना चाहिए। – Holger

संबंधित मुद्दे