Since you rewrote your question, here is a new answer.
First, it seems to me that your computeNonRecursivelly
implementation is still recursive, since getOrEnqueue
calls it.
I donβt think you can use Cache
directly, because you need to have 2 steps in your calculation: one that defines the dependencies for the required value, and one that performs the calculations after the dependencies are completed. This will only work if you never have circular dependencies (this is the same requirement as with recursion).
Thus, you can queue dependencies that are not already in the cache (and their dependencies, etc.), and then calculate them in the correct order. Sort of:
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; } }
Now you can implement TwoStepCacheLoader
, which safely calls the cache:
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); } } }
I ran unit test on it, it works correctly.