Java:自动记忆化

21

我在我的代码中有几个函数,使用记忆化(memoization)非常合适(甚至似乎是必要的)。

我不想为每个函数单独手动实现它。是否有一种方法(例如Python 中的),我可以只是使用一个注释或做其他事情,以便在那些我想要的函数上自动获得这个效果?


经过长时间的搜索,我终于刚刚创建了自己的:https://gist.github.com/chaotic3quilibrium/7535c231bb11bd9abc5712c982c46690 - undefined
5个回答

19

Spring 3.1 现在提供了一个 @Cacheable 注解,它可以标识可缓存的方法 - 也就是说,对于这些方法,它们的结果会被存储到缓存中,因此在后续调用(使用相同的参数)时,缓存中的值会被返回,而无需实际执行该方法。

正如名称所示,@Cacheable 用于标注可缓存的方法 - 也就是说,对于这些方法,它们的结果会被存储到缓存中,因此在后续调用(使用相同的参数)时,缓存中的值会被返回,而无需实际执行该方法。


7

我认为没有原生的记忆化语言实现。

但是,您可以将其作为方法的装饰器轻松实现。您需要维护一个映射:映射的键是参数,值是结果。

以下是一个简单的实现,适用于一个参数的方法:

Map<Integer, Integer> memoizator = new HashMap<Integer, Integer>();

public Integer memoizedMethod(Integer param) {

    if (!memoizator.containsKey(param)) {
        memoizator.put(param, method(param));
    } 

    return memoizator.get(param);
}

我该如何以一般的方式将其实现为我的方法的装饰器? - Albert
@Albert:正如Benoit所述,没有本地实现(即在没有Java黑客攻击的情况下无法以一般方式完成此操作),因为Python装饰器使用有关函数的“元信息”。也就是说,在Python中可以让装饰器更改原始函数。据我所知,这在Java中是不可能的。 - phimuemue
你可以将其作为方法的装饰器,轻松实现。 <- 如何将其作为装饰器?或者你是什么意思? - Albert
@Albert,他所说的装饰器是指在另一个函数周围包装的更通用的术语,而不是Pythonic语法方式,使其看起来干净简单。你可能可以通过AspectJ以某种方式获得你想要的结果,但我个人对它不太熟悉,而且我仍然认为这不会像Python那样容易。 - Karl Bielefeldt

7

我发现了一个叫做Tek271的记忆化库,它似乎使用注释来像你描述的那样对函数进行记忆化。


啊,我明白了。这个库似乎提供了一种方法来为你的对象创建一个包装器对象,并通过注释自动地进行函数记忆化。 - Albert
1
该页面已经移动,现在可以在http://www.tek271.com/software/java/memoizer找到。 - Christian Semrau

4
你可以使用Google的guava库中的Function接口来轻松实现你想要的功能:

您可以使用Function 接口在Google的guava库中轻松地实现您所需的功能:

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

import com.google.common.base.Function;

public class MemoizerTest {
  /**
   * Memoizer takes a function as input, and returns a memoized version of the same function.
   * 
   * @param <F>
   *          the input type of the function
   * @param <T>
   *          the output type of the function
   * @param inputFunction
   *          the input function to be memoized
   * @return the new memoized function
   */
  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));
        }

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

  public static void main(final String[] args) {
    // Define a function (i.e. inplement apply)
    final Function<Integer, Integer> add2 = new Function<Integer, Integer>() {
      @Override
      public Integer apply(final Integer input) {
        System.out.println("Adding 2 to: " + input);
        return input + 2;
      }
    };

    // Memoize the function
    final Function<Integer, Integer> memoizedAdd2 = MemoizerTest.memoize(add2);

    // Exercise the memoized function
    System.out.println(memoizedAdd2.apply(1));
    System.out.println(memoizedAdd2.apply(2));
    System.out.println(memoizedAdd2.apply(3));
    System.out.println(memoizedAdd2.apply(2));
    System.out.println(memoizedAdd2.apply(4));
    System.out.println(memoizedAdd2.apply(1));
  }
}

应该打印:

将2添加到1:

3

将2添加到2:

4

将2添加到3:

5

4

将2添加到4:

6

3

您可以看到,第二次对参数2和1应用memoizedAdd2时,并没有实际运行apply中的计算,它只是获取存储的结果。


更接近我想要的,但仍然太具体了。是否可能进一步概括,以便它可以接受任意数量的参数(而不仅仅是一个)? - Albert
Guava的Function类将所有输入压缩成单个参数。现在,该参数的类型可以是Object[],有效地允许任何内容,但降低了类型检查的效率。或者,创建一个新的Function2接口,通过<F1,F2,T>泛型化为2个参数,一个Function3 <F1,F2,F3,T>等都是相当简单的。 - Tom Tresansky
在Guava中,Suppliers类具有内置的memoize和memoizeWithExpiration方法。 - lbalazscs

1

Cyclops 提供了函数、Suppliers、Callables、Predicates以及通过方法引用(via Method References)实现的扩展方法的Memoisation功能(请参见javadoc)。

例如:

给定一个名为“that”的变量,它记录我们的方法被调用的次数,我们可以看到Memoised函数只执行了一次该方法。

int called = 0;

cached = Memoise.memoiseQuadFunction(this::addAll);

assertThat(cached.apply(1,2,3,4),equalTo(10));
assertThat(cached.apply(1,2,3,4),equalTo(10));
assertThat(called,equalTo(1));

private int addAll(int a,int b,int c, int d){
    called++;
    return a+b+c+d;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接