Da Sie Ihre Frage neu geschrieben haben, hier ist eine neue Antwort .
Zuerst sieht es für mich wie Ihre Implementierung von computeNonRecursivelly
ist immer noch rekursiv, seit getOrEnqueue
nennt es.
Ich glaube nicht, dass Sie eine Cache
direkt verwenden können, weil Sie 2 Schritte in Ihrer Berechnung haben müssen: eine, die die Abhängigkeiten für den gewünschten Wert angibt, und eine, die die Berechnung durchführt, sobald die Abhängigkeiten erfüllt sind. Es wird jedoch nur funktionieren, wenn Sie nie zyklische Abhängigkeiten haben (es ist die gleiche Anforderung wie bei der Rekursion).
Auf diese Weise können Sie Abhängigkeiten, die sich nicht bereits im Cache befinden (und deren Abhängigkeiten usw.) in die Warteschlange stellen und sie dann in der richtigen Reihenfolge berechnen. Etwas entlang der Linien von:
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;
}
}
Jetzt können Sie einen TwoStepCacheLoader
implementieren, die den Cache sicher ruft:
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);
}
}
}
Ich habe einen Komponententest darauf ausgeführt werden, so scheint es, um korrekt zu funktionieren.
Haben Sie Dr. Heinz M. Kabutz 'Artikel über [Fork/Join mit Fibonacci und Karatsuba] (http://www.javaspecialists.eu/archive/Issue201.html) angeschaut. Einige seiner Ideen können zutreffen. – OldCurmudgeon
@OldCurmudgeon: Jetzt habe ich, es ist interessant, aber hat nicht wirklich geholfen. – maaartinus
Ich fand das [Cache-Fibonacci-Beispiel] (http://vladmihalcea.com/2014/03/03/caching-best-practices/), das scheint zu gelten. Siehe den Abschnitt "Spielzeit" nach unten. Es scheint zu funktionieren, indem die vorher berechnete Zahl im Cache gehalten wird und ältere Werte entfernt werden. Die rekursive Last wird durch eine sorgfältige Reihenfolge der Anweisungen in der 'load' Methode vermieden.Ich bin nicht sicher, ob das über das "Spielzeug" -Beispiel hinaus ausgedehnt werden kann, wie Sie es nennen. –