Puesto que usted ha reescrito su pregunta, he aquí una nueva respuesta .
Primero, me parece que su implementación de computeNonRecursivelly
es aún recursiva, ya que getOrEnqueue
lo llama.
No creo que pueda usar un Cache
directamente, porque necesita tener 2 pasos en su computación: uno que establece las dependencias para el valor deseado, y uno que hace el cálculo una vez que se cumplen las dependencias. Sin embargo, solo funcionará si nunca tiene dependencias cíclicas (es el mismo requisito que en la recursión).
De esta manera, puede poner en cola las dependencias que todavía no están en la memoria caché (y sus dependencias, etc.) y luego calcularlas en el orden correcto. Algo a lo largo de las líneas de:
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;
}
}
Ahora se puede implementar un TwoStepCacheLoader
que llama a la caché de forma segura:
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);
}
}
}
He corrido una prueba de unidad en él, parece funcionar correctamente.
¿Has mirado el artículo del Dr. Heinz M. Kabutz sobre [Tenedor/Únete con Fibonacci y Karatsuba] (http://www.javaspecialists.eu/archive/Issue201.html). Algunas de sus ideas pueden aplicarse. – OldCurmudgeon
@ OldCurmudgeon: Ahora sí, es interesante pero realmente no me ayudó. – maaartinus
Encontré este [ejemplo de caché Fibonacci] (http://vladmihalcea.com/2014/03/03/caching-best-practices/) que parece aplicarse. Vea la sección llamada "Tiempo de reproducción" hacia la parte inferior. Parece funcionar manteniendo el número computado anterior en la memoria caché y desalojando valores anteriores. La carga recursiva se evita mediante un orden cuidadoso de las instrucciones en el método 'load'.No estoy seguro de si eso se puede extender más allá del ejemplo de "juguete", como lo pones. –