Метод Java мемоизация

я столкнулся с интересной проблемой и задавался вопросом, Можно ли и как это сделать на Java: Создайте метод, который может запоминать любую функцию / метод . Этот метод имеет следующие аргументы : метод/функция и аргумент(ы) для этого.

например, скажем, у меня есть этот метод :

int addOne(int a) { return a + 1;}

и я вызываю свой метод memoization два раза с теми же аргументами: addOne и 5 например, первый вызов должен фактически вызвать метод addOne и вернуть результат, а также сохранить этот результат для данного аргумента. Во второй раз, когда я звоню, он должен знать, что это было вызвано раньше, и просто посмотрите предыдущий ответ.

моя идея состояла бы в том, чтобы иметь что-то вроде HashMap<Callable,HashMap<List<Objects>,Object>> где вы будете хранить предыдущие ответы и искать их позже.Я думаю, что это можно как-то сделать с лямбда-выражениями, но я не так хорошо с ними знаком.Я не совсем уверен, как написать этот метод и был бы признателен за некоторую помощь.

может это можно сделать с помощью такого подхода?

2 ответов


в Java 8, вы можете сделать это так:

Map<Integer, Integer> cache = new ConcurrentHashMap<>();

Integer addOne(Integer x) {
    return cache.computeIfAbsent(x -> x + 1);
}

это хороший учебник. Там он сделан для любого метода.

из учебника:

класс Memoizer:

public class Memoizer<T, U> {
    private final Map<T, U> cache = new ConcurrentHashMap<>();

    private Memoizer() {}
    private Function<T, U> doMemoize(final Function<T, U> function) {
        return input -> cache.computeIfAbsent(input, function::apply);
    }

    public static <T, U> Function<T, U> memoize(final Function<T, U> function) {
        return new Memoizer<T, U>().doMemoize(function);
    }
}

как использовать класс:

Integer longCalculation(Integer x) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ignored) {
    }
    return x * 2;
}
Function<Integer, Integer> f = this::longCalculation;
Function<Integer, Integer> g = Memoizer.memoize(f);

public void automaticMemoizationExample() {
    long startTime = System.currentTimeMillis();
    Integer result1 = g.apply(1);
    long time1 = System.currentTimeMillis() - startTime;
    startTime = System.currentTimeMillis();
    Integer result2 = g.apply(1);
    long time2 = System.currentTimeMillis() - startTime;
    System.out.println(result1);
    System.out.println(result2);
    System.out.println(time1);
    System.out.println(time2);
}

выход:

2
2
1000
0

вы можете запомнить любую функцию с помощью Java 8 MethodHandles и lambdas, если вы готовы отказаться от безопасности типа по параметрам:

public interface MemoizedFunction<V> {
    V call(Object... args);
}

private static class ArgList {
    public Object[] args;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof ArgList)) {
            return false;
        }

        ArgList argList = (ArgList) o;

        // Probably incorrect - comparing Object[] arrays with Arrays.equals
        return Arrays.equals(args, argList.args);
    }

    @Override
    public int hashCode() {
        return args != null ? Arrays.hashCode(args) : 0;
    }
}

public static <V> MemoizedFunction<V> memoizeFunction(Class<? super V> returnType, Method method) throws
                                                                                                  IllegalAccessException {
    final Map<ArgList, V> memoizedCalls = new HashMap<>();
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle methodHandle = lookup.unreflect(method)
                                      .asSpreader(Object[].class, method.getParameterCount());
    return args -> {
        ArgList argList = new ArgList();
        argList.args = args;
        return memoizedCalls.computeIfAbsent(argList, argList2 -> {
            try {
                //noinspection unchecked
                return (V) methodHandle.invoke(args);
            } catch (Throwable throwable) {
                throw new RuntimeException(throwable);
            }
        });
    };
}

Пример Работающего

это создает переменную-arity лямбда, которая заключает функцию и почти так же быстро, как вызов функции напрямую (т. е. внутри call(Object...args)) после того, как лямбда построена, так как мы используем MethodHandle.invoke() вместо Method.invoke().

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