使用Memoized方法进行递归函数

时间:2017-08-14 18:42:37

标签: c# recursion memoization

我有一个像这样的记忆功能:

static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
    var cache = new ConcurrentDictionary<A, R>();
    return argument => cache.GetOrAdd(argument, f);
}

我也有一些递归方法

long TheRecursiveMeth (string inString) {
// recursive function that calls itself    
}

现在,在我的主要功能中,我尝试:

TheRecursiveMeth = TheRecursiveMeth.Memoize();

但编译器抱怨

  

'。' operator不能应用于`method group'

类型的操作数

  

作业的左侧必须是变量,属性或   索引器

如何调用TheRecursiveMeth实际调用TheRecursiveMeth.Memoize(),包括递归调用?

编辑:我试图避免编辑TheRecursiveMeth的定义。显然我可以检查字典。

编辑2:既然你感兴趣,我有一个函数来计算给定字符串的某些回文的数量。这里解释起来有点复杂,但基本上是这样的:

long palCount(string inString) {
    if (inString.Length==1) return 1;
    else {
      count = 0;
      foreach(substring of inString) {
          // more complex logic here 
          count += palCount(subString);
      }
      return count;
    }
}

很明显,这类事情会从记忆中受益。我最初避免添加算法,因为它是无关紧要的,并且更有可能让人们给我建议,这是不重要的。

1 个答案:

答案 0 :(得分:2)

对于完全可推广的解决方案,您必须更改编写函数的方式,以使这一切能够正常工作。

忽略你试图实例化memoized函数的问题,主要问题是由于早期绑定,你无法调用你的memoized函数,编译器将始终绑定到原始函数。您需要为函数实现提供对memoized版本的引用,并让它使用它。你可以使用一个工厂来实现这个目的,该工厂同时具有与memoized函数相同的签名和args。

public long Fib(long n)
{
    if (n < 2L)
        return 1L;
    return Fib(n - 1) + Fib(n - 2);
}

然后更改以下功能:

private Func<long, long> _FibImpl = Memoized<long, long>((fib, n) =>
{
    if (n < 2L)
        return 1L;
    return fib(n - 1) + fib(n - 2); // using `fib`, the parameter passed in
});
public long Fib(long n) => _FibImpl(n);

到此:

class MemoizedAttribute : Attribute { }

class Memoizer<TArg, TResult> : IInterceptor
{
    private readonly ConcurrentDictionary<TArg, TResult> cache;
    public Memoizer(IEqualityComparer<TArg> comparer = null)
    {
        cache = new ConcurrentDictionary<TArg, TResult>(comparer ?? EqualityComparer<TArg>.Default);
    }
    public void Intercept(IInvocation invocation)
    {
        if (!IsApplicable(invocation))
        {
            invocation.Proceed();
        }
        else
        {
            invocation.ReturnValue = cache.GetOrAdd((TArg)invocation.GetArgumentValue(0),
                _ =>
                {
                    invocation.Proceed();
                    return (TResult)invocation.ReturnValue;
                }
            );
        }
    }
    private bool IsApplicable(IInvocation invocation)
    {
        var method = invocation.Method;
        var isMemoized = method.GetCustomAttribute<MemoizedAttribute>() != null;
        var parameters = method.GetParameters();
        var hasCompatibleArgType = parameters.Length == 1 && typeof(TArg).IsAssignableFrom(parameters[0].ParameterType);
        var hasCompatibleReturnType = method.ReturnType.IsAssignableFrom(typeof(TResult));
        return isMemoized && hasCompatibleArgType && hasCompatibleReturnType;
    }
}

对于踢,这是一个可以与Castle DynamicProxy一起使用的memoizer拦截器的实现。

public class FibCalculator
{
    [Memoized]
    public virtual long Fib(long n)
    {
        if (n < 2L)
            return 1L;
        return Fib(n - 1) + Fib(n - 2);
    }
}

var calculator = new Castle.DynamicProxy.ProxyGenerator().CreateClassProxy<FibCalculator>(new Memoizer<long, long>());
calculator.Fib(5); // 5 invocations
calculator.Fib(7); // 2 invocations

然后确保您的方法声明为虚拟并具有适当的属性。

-I"path"