Java: автоматическая мемоизация

В моем коде есть несколько функций, для которых имеет смысл (кажется даже обязательным) использовать мемоизацию.

Я не хочу реализовывать это вручную для каждой функции отдельно. Есть ли способ (например, как в Python), я могу просто использовать аннотацию или сделать что-то еще, чтобы я получил это автоматически в тех функциях, где я этого хочу?


person Albert    schedule 14.10.2010    source источник


Ответы (5)


Spring 3.1 теперь предоставляет @Cacheable аннотацию, что и делает именно это.

Как следует из названия, @Cacheable используется для разграничения методов, которые можно кэшировать, то есть методов, результат которых сохраняется в кеше, поэтому при последующих вызовах (с теми же аргументами) значение в кеше возвращается без необходимости фактически выполнить метод.

person oksayt    schedule 18.03.2012

Я наткнулся на библиотеку мемоизации под названием Tek271, которая, похоже, использует аннотации для запоминания функций, когда вы описывать.

person Jacob Mattison    schedule 14.10.2010
comment
Ах я вижу. Кажется, что библиотека предоставляет способ создать объект-оболочку для вашего объекта и автоматически запоминает те функции, которые были помечены для запоминания с помощью аннотации. - person Albert; 14.10.2010
comment
Страница перемещена, и теперь ее можно найти по адресу tek271.com/software/java/memoizer - person Christian Semrau; 03.05.2011

Я не думаю, что существует языковая реализация мемоизации.

Но вы можете легко реализовать это как декоратор вашего метода. Вы должны поддерживать карту: ключ вашей карты - это параметр, значение - результат.

Вот простая реализация метода с одним аргументом:

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);
}
person Benoit Courtine    schedule 14.10.2010
comment
Как я могу реализовать его в общем виде как декоратор моего метода? - person Albert; 14.10.2010
comment
@Albert: Как заявил Бенуа, для этого нет собственной реализации (т.е. вы не можете сделать это в общем случае без взлома Java), поскольку материал декоратора python использует некоторую метаинформацию о функции. Т.е. в python можно позволить декоратору изменять исходную функцию. Насколько мне известно, это невозможно в Java. - person phimuemue; 14.10.2010
comment
вы можете легко реализовать это как декоратор вашего метода. ‹- как я могу это сделать как декоратор? Или что вы имеете в виду? - person Albert; 14.10.2010
comment
@Albert, он имеет в виду декоратор в более общем термине обертки вокруг другой функции, а не в синтаксисе Python, который делает его чистым и простым. Возможно, вам удастся каким-то образом получить то, что вы хотите, с помощью AspectJ, но я лично недостаточно знаком с ним, и я все же думаю, что это будет не так просто, как с python. - person Karl Bielefeldt; 14.10.2010

Вы можете использовать функцию. интерфейс в библиотеке guava от Google, чтобы легко достичь того, что вам нужно:

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

Вы можете видеть, что второй раз memoizedAdd2 вызывается (применяется) к аргументам 2 и 1, вычисление в приложении фактически не выполняется, оно просто извлекает сохраненные результаты.

person Tom Tresansky    schedule 14.10.2010
comment
Это ближе к тому, что я хочу, но все же слишком конкретное. Можно ли еще больше обобщить это, чтобы оно могло принимать любое количество параметров (а не только один)? - person Albert; 15.10.2010
comment
Класс функции Guava объединяет все входные данные в один аргумент. Теперь типом этого аргумента может быть Object [], позволяющий эффективно выполнять любые действия, но снижающий эффективность проверки типов. Или было бы довольно просто создать новый интерфейс Function2, обобщенный с помощью ‹F1, F2, T› для 2 аргументов, Function3 ‹F1, F2, F3, T› и т. Д. - person Tom Tresansky; 15.10.2010
comment
В Guava класс Suppliers имеет встроенные методы memoize и memoizeWithExpiration. - person lbalazscs; 20.03.2015

Cyclops предлагает мемоизацию для функций, поставщиков, вызываемых объектов, предикатов и методов расширения (через ссылки на методы) (см. javadoc)

E.g.

Учитывая вызываемую переменную, которая подсчитывает количество фактических вызовов нашего метода, мы можем видеть, что мемоизированная функция фактически выполняет метод только один раз.

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;
}
person John McClean    schedule 11.06.2015