2012-06-22 13 views
6

मैं पूरी तरह से इस प्रश्न को फिर से लिखता हूं क्योंकि मूल एक असफल था। इसे सरल रखने के लिए मैं फिबोनाची संख्याओं का खिलौना उदाहरण उपयोग कर रहा हूं।लोडिंग कैश के साथ पुनरावृत्ति में पुनरावृत्ति को कैसे परिवर्तित करें?

trivial recursive cached computation एक बहुत लंबे स्टैक्स्ट्रेस के साथ समाप्त होता है, जैसा कि अपेक्षित था। यही कारण है कि मैं IterativeLoadingCache, की तरह एक अमूर्त वर्ग जो मैं की तरह

@Override 
protected Integer computeNonRecursivelly(Integer key) { 
    final Integer x1 = getOrEnqueue(key-1); 
    final Integer x2 = getOrEnqueue(key-2); 
    if (x1==null) return null; 
    if (x2==null) return null; 
    return x1+x2; 
} 

कुछ द्वारा here तरह का विस्तार कर सकते हैं और जो प्रत्यावर्तन का उपयोग किए बिना सभी कैशिंग और गणना के बारे में ध्यान रखना होगा करना चाहते हैं है।

मैं वास्तव में फिबोनाची संख्याओं की कुशल गणना की तलाश नहीं कर रहा हूं। मुझे रिकर्सिव फ़ंक्शंस के साथ कैशिंग का उपयोग करने की अनुमति देने की आवश्यकता है, जहां रिकर्सन गहराई मनमाने ढंग से उच्च हो सकती है।

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

+0

आप पर डॉ हाइन्ज़ एम Kabutz 'लेख पर ध्यान दिया है (http://www.javaspecialists.eu/archive/Issue201.html) [कांटा/फाइबोनैचि और Karatsuba के साथ जुड़ें]। उनके कुछ विचार लागू हो सकते हैं। – OldCurmudgeon

+0

@ ओल्डकुरम्यूजियन: अब मेरे पास है, यह दिलचस्प है लेकिन वास्तव में मदद नहीं करता है। – maaartinus

+0

मुझे यह [कैश फिबोनासी उदाहरण] मिला है (http://vladmihalcea.com/2014/03/03/caching-best-practices/) जो लागू होने लगता है। नीचे की ओर "बजाना समय" नामक अनुभाग देखें। ऐसा लगता है कि पिछली गणना संख्या को कैश में रखकर और पुराने मानों को बेदखल कर काम करता है। रिकर्सिव लोड 'लोड' विधि में बयानों के सावधानीपूर्वक आदेश से बचा जाता है।मुझे यकीन नहीं है कि अगर इसे 'खिलौना' उदाहरण से परे बढ़ाया जा सकता है, जैसा कि आपने इसे रखा है। –

उत्तर

3

संपादित करें: ने एक ही गणना के लिए कार्यान्वयन को बदलने के लिए उसी Expression को कई धागे में पैरामीटर के रूप में पारित किया है।

एक LoadingCache का उपयोग न करें, बस eval में परिणाम कैश (एक बार यह यात्रा प्रत्यावर्तन के बजाय का उपयोग करने के संशोधित किया गया है):

public Node eval(final Expression e) { 
    if (e==null) return null; 
    return cache.get(e, new Callable<Node>() { 
     @Override 
     public Node call() { 
      final Node n0 = eval(leftExpression(e)); 
      final Node n1 = eval(rightExpression(e)); 
      return new Node(n0, n1); 
     } 
    }); 
} 

private final Cache<Expression, Node> cache 
= CacheBuilder.newBuilder().build(); 
+0

यह काम करना चाहिए, लेकिन मैं निम्नलिखित सुविधा याद कर रहा हूँ: * "एक और कॉल प्राप्त या getUnchecked वर्तमान में कुंजी के लिए मूल्य लोड कर रहा है करने के लिए है, तो बस उस सूत्र को पूर्ण करने और अपने लोड मान देता है के लिए इंतजार कर रहा है।" * मैं कर सकता जबकि कुछ खुद को लॉक कर रहे हैं, यह शायद ओवरहेड में काफी वृद्धि होगी। – maaartinus

+0

यह सुविधा 'Cache.get (के, कॉल करने योग्य ) 'के लिए प्रलेखित नहीं है, क्योंकि यह' लोडिंग कैश.जेट (के) 'के लिए है, लेकिन चूंकि दोनों विधियां अंततः' LocalCache.get (K, CacheLoader) 'कहती हैं, इसे चाहिए काम। सुनिश्चित नहीं है कि यह वास्तविक सुविधा या कार्यान्वयन विवरण है जो परिवर्तन के अधीन है। –

+0

यहां गुवा योगदानकर्ता: हां, यह गारंटी 'कैश.जेट (के, कॉल करने योग्य)' के लिए है। मैंने यह सुनिश्चित करने के लिए [दस्तावेज दायर किया है] (https://code.google.com/p/guava-libraries/issues/detail?id=1040) यह सुनिश्चित करने के लिए कि दस्तावेज़ में स्पष्ट किया गया है। –

4

आप अपने प्रश्न फिर से लिख दिया के बाद से, यहाँ एक नया जवाब ।

सबसे पहले, यह मुझे लगता है कि computeNonRecursivelly के कार्यान्वयन की तरह मुझे अभी भी रिकर्सिव है, क्योंकि getOrEnqueue इसे कॉल करता है।

मुझे नहीं लगता कि आप सीधे Cache का उपयोग कर सकते हैं, क्योंकि आपको अपनी गणना में 2 कदम रखने की आवश्यकता है: जो वांछित मूल्य के लिए निर्भरता बताता है, और एक निर्भरता मिलने के बाद गणना करता है। यह केवल तभी काम करेगा जब आपके पास चक्रीय निर्भरता न हो, हालांकि (रिकर्सन में यह वही आवश्यकता है)।

इस तरह, आप निर्भरता जो कैश (और उनके निर्भरता, आदि) में पहले से ही नहीं कर रहे हैं कतार फिर उन्हें सही क्रम में गणना कर सकता है। की तर्ज पर कुछ:

public abstract class TwoStepCacheLoader<K, V> extends CacheLoader<K, V> { 
    public abstract Set<K> getDependencies(K key); 
} 

public class TwoStepCache<K, V> extends ForwardingLoadingCache<K, V> { 
    private final TwoStepCacheLoader<K, V> loader; 
    private LoadingCache<K, V> cache; 

    public TwoStepCache(TwoStepCacheLoader<K, V> loader) { 
     this.loader = loader; 
     cache = CacheBuilder.newBuilder().build(loader); 
    } 

    @Override 
    public V get(K key) 
      throws ExecutionException { 
     V value = cache.getIfPresent(key); 
     if (value != null) { 
      return value; 
     } 

     Deque<K> toCompute = getDependenciesToCompute(key); 
     return computeDependencies(toCompute); 
    } 

    private Deque<K> getDependenciesToCompute(K key) { 
     Set<K> seen = Sets.newHashSet(key); 
     Deque<K> dependencies = new ArrayDeque<K>(seen), toCompute = new ArrayDeque<K>(seen); 
     do { 
      for (K dependency : loader.getDependencies(dependencies.remove())) { 
       if (seen.add(dependency) && // Deduplication in the dependencies 
        cache.getIfPresent(dependency) == null) { 
        // We need to compute it. 
        toCompute.push(dependency); 
        // We also need its dependencies. 
        dependencies.add(dependency); 
       } 
      } 
     } while (!dependencies.isEmpty()); 
     return toCompute; 
    } 

    private V computeDependencies(Deque<K> toCompute) 
      throws ExecutionException { 
     V value; 
     do { 
      value = cache.get(toCompute.pop()); 
     } while (!toCompute.isEmpty()); 
     // The last computed value is for our key. 
     return value; 
    } 

    @Override 
    public V getUnchecked(K key) { 
     try { 
      return get(key); 
     } catch (ExecutionException e) { 
      throw new UncheckedExecutionException(e.getCause()); 
     } 
    } 

    @Override 
    protected LoadingCache<K, V> delegate() { 
     return cache; 
    } 
} 

अब आप एक TwoStepCacheLoader कि कैश सुरक्षित रूप से कॉल को लागू कर सकते हैं:

public class Fibonacci { 
    private LoadingCache<Integer, Integer> cache = new TwoStepCache<Integer, Integer>(new FibonacciCacheLoader()); 


    public int fibonacci(int n) { 
     return cache.getUnchecked(n); 
    } 


    private class FibonacciCacheLoader extends TwoStepCacheLoader<Integer, Integer> { 
     @Override 
     public Set<Integer> getDependencies(Integer key) { 
      if (key <= 1) { 
       return ImmutableSet.of(); 
      } 
      return ImmutableSet.of(key - 2, key - 1); 
     } 


     @Override 
     public Integer load(Integer key) 
       throws Exception { 
      if (key <= 1) { 
       return 1; 
      } 
      return cache.get(key - 2) + cache.get(key - 1); 
     } 
    } 
} 

मैं इस पर एक इकाई परीक्षण चलाने के बाद, इसे सही ढंग से चलाने के लिए लगता है।

+0

अन्य तरीकों का विस्तार करने के '' 'ForwardingLoadingCache''' सही ढंग से (और लगातार) ओवरराइड करने के लिए कर रहे हैं, लेकिन वे उदाहरण के लिए कोई फर्क नहीं पड़ता (' '' getAll() '' '' '' लागू() '' ', आदि)। –

+0

"computeNonRecursivelly अभी भी रिकर्सिव" के बारे में, आप सही हैं, लेकिन यह मेरे बदसूरत समाधान से निकालने पर केवल एक त्रुटि हुई थी। कॉल हटाया जा सकता है और हटा दिया जाना चाहिए। – maaartinus

+0

** 1 ** मुझे यकीन है कि वहाँ एक रास्ता है, एक समस्या के बारे में अधिक श्रमिकों डाल करने के लिए और न ही मैं आम निर्भरता किसी भी तरह से सहयोग कर सकता है की आवश्यकता होती है मूल्यों के लिए अगर दो समवर्ती अनुरोधों देख सकते हैं नहीं कर रहा हूँ। 'getDependenciesToCompute' की एक सरल पुनर्लेखन के साथ संभव हो सकता है या नहीं, मैं नहीं बता सकता। मुझे पता है कि यह ऐसा कुछ है जिसे मैंने कभी नहीं लिखा था। ** 2 ** 'प्राप्त करें निर्भरता' विचार वास्तव में अच्छा है। मुझे डर है कि यह पागल मामलों जैसे 'एफ (एन) = एफ (एन -1) + एफ (एफ (लॉग (एन)) पर लागू नहीं होता है)', लेकिन मुझे लगता है कि मुझे इसकी आवश्यकता नहीं होगी। ** 3 ** मैंने कहा कि मैं अच्छे समाधान के लिए धन्यवाद। मुझे इसका मूल्यांकन करने के लिए कुछ और समय चाहिए। – maaartinus

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