【问题标题】:Java Memoization of Recursive method递归方法的Java记忆
【发布时间】:2014-05-03 11:46:21
【问题描述】:

我正在尝试创建阶乘函数的记忆版本。当我调用 factMemoized(4) 时,它第一次计算 4 的阶乘并将其存储在 Map 中。当我再次调用 factMemoized(4) 时,它现在给出了存储的结果,而不是再次重新计算它。这按预期工作。但是,当我调用 factMemoized(3) 时,它会重新计算值,尽管它已经计算了 fact(3) 作为计算 fact(4) 的一部分。有什么方法可以确保即使是作为递归调用的一部分计算的值也将存储在 map 中,而无需在 fact() 函数中添加 memoization 函数?

import java.util.HashMap;
import java.util.Map;


public class MemoizeBetter {

public static <F, T> Function<F, T> memoize(final Function<F, T> inputFunction) {
    return new Function<F, T>() {
      // Holds previous results
      Map<F, T> memoization = new HashMap<F, T>();

      @Override
      public T apply(final F input) {
        // Check for previous results
        if (!memoization.containsKey(input)) {
          // None exists, so compute and store a new one

          memoization.put(input, inputFunction.apply(input));
        }else{
            System.out.println("Cache hit:"+input);
        }

        // At this point a result is guaranteed in the memoization
        return memoization.get(input);
      }
    };
  }

public static void main(String args[]){


final Function<Integer, Integer> fact = new Function<Integer, Integer>() {
      @Override
      public Integer apply(final Integer input) {
        System.out.println("Fact: " + input);
        if(input == 1)
            return 1;
        else return input * apply(input -1);

      }
    };

    final Function<Integer, Integer> factMemoized = MemoizeBetter.memoize(fact);

    System.out.println("Result:"+ factMemoized.apply(1));
    System.out.println("Result:"+factMemoized.apply(2));
    System.out.println("Result:"+factMemoized.apply(3));
    System.out.println("Result:"+factMemoized.apply(2));
    System.out.println("Result:"+factMemoized.apply(4));
    System.out.println("Result:"+factMemoized.apply(1));    }    
}

interface Function<F,T>{
    T apply(F input);
}

【问题讨论】:

  • memorize map 被覆盖,即 (0,4),它将覆盖到相同的索引 (0,3),确保您的逻辑正确。

标签: java memoization


【解决方案1】:

问题是您的 Factorial 函数不会递归调用到函数的记忆版本。

要解决此问题,有几个选项。

  1. 你可以参数化你的 Factorial 函数并给它引用 Function 它应该递归调用。在未记忆的情况下,这将是函数本身;在 memoized 的情况下,这将是 memoizing 包装器。

  2. 您可以通过扩展 Factorial 函数类来实现记忆化,覆盖而不是委托给未记忆的apply()。这很难做到即席,但有一些实用程序可以动态创建子类(例如,这是实现 AOP 的常用方法)。

  3. 你可以让基础函数充分了解记忆。

这是第一个选项的要点:

interface MemoizableFunction<I, O> extends Function<I, O> {

    //in apply, always recurse to the "recursive Function"
    O apply(I input);

    setRecursiveFunction(Function<? super I, ? extends O>);
}

final MemoizableFunction<Integer, Integer> fact = new MemoizableFunction<Integer, Integer>() {

  private Function<Integer, Integer> recursiveFunction = this;

  @Override
  public Integer apply(final Integer input) {
    System.out.println("Fact: " + input);
    if(input == 1)
        return 1;
    else return input * recursiveFunction.apply(input -1);
  }

  //...
};

【讨论】:

  • 谢谢——在所有这些情况下,事实函数需要注意记忆。我正在研究如何让编写事实函数的人对此透明。
  • @Tim:在第二种情况下,函数不一定需要知道,但它需要允许子类化。这类似于 Guice 等 AOP 提供者对其 AOP 目标的限制。事实上,记忆化是 AOP 的一个非常教科书的用例。我怀疑在 Java 中是否有一种直接的方法来记忆任何给定的函数,因为您需要拦截递归调用以某种方式
  • 谢谢。在 clojure 中,这相当简单。 (defn f [n] (println "f call with" n) (if (== 1 n) 1 (* n (f (- n 1))))) def f (memoize f)
  • @Tim:这不是同样的问题吗?例如,请参阅有关在 Clojure 中记忆递归函数的问题(在本例中为斐波那契):stackoverflow.com/questions/3906831/…。您在 Clojure 中的方法似乎与您在 Java 中遇到的问题相同。接受的答案似乎是在修改函数的环境,以便它调用记忆的fib(基本上是我的第一个建议)。下一个最高答案只是递归调用记忆中的fib(我的第三个建议)。
【解决方案2】:

解决此问题的另一种方法是使用数组来存储已计算的斐波那契值。它的工作方式是,如果第 n 个位置的斐波那契存在于数组的第 n 个索引处,则不会再次计算该值,而只是从数组中选取。

但是,如果数组中第 n 个位置的值不存在,则计算它。下面给出的是这种方法 fibonacci() 的代码 -

public static long fibonacci(long n){
    long fibValue=0;
    if(n==0 ){
        return 0;
    }else if(n==1){
        return 1;
    }else if(fibArray[(int)n]!=0){
        return fibArray[(int)n];    
    }
    else{
        fibValue=fibonacci(n-1)+fibonacci(n-2);
        fibArray[(int) n]=fibValue;
        return fibValue;
    }
}

请注意,此方法使用全局(类级别)静态数组 fibArray[]。要查看带有解释的整个代码,您还可以查看以下内容 - http://www.javabrahman.com/gen-java-programs/recursive-fibonacci-in-java-with-memoization/

【讨论】:

    猜你喜欢
    • 2017-07-07
    • 2018-10-29
    • 2014-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-12
    • 2012-09-27
    • 1970-01-01
    相关资源
    最近更新 更多