How do I write a generic memoize function?

2019-01-17 17:37发布

I'm writing a function to find triangle numbers and the natural way to write it is recursively:

function triangle (x)
   if x == 0 then return 0 end
   return x+triangle(x-1)
end

But attempting to calculate the first 100,000 triangle numbers fails with a stack overflow after a while. This is an ideal function to memoize, but I want a solution that will memoize any function I pass to it.

16条回答
小情绪 Triste *
2楼-- · 2019-01-17 18:14

Please don't recurse this. Either use the x*(x+1)/2 formula or simply iterate the values and memoize as you go.

int[] memo = new int[n+1];
int sum = 0;
for(int i = 0; i <= n; ++i)
{
  sum+=i;
  memo[i] = sum;
}
return memo[n];
查看更多
戒情不戒烟
3楼-- · 2019-01-17 18:15

You're also asking the wrong question for your original problem ;)

This is a better way for that case:

triangle(n) = n * (n - 1) / 2

Furthermore, supposing the formula didn't have such a neat solution, memoisation would still be a poor approach here. You'd be better off just writing a simple loop in this case. See this answer for a fuller discussion.

查看更多
我欲成王,谁敢阻挡
4楼-- · 2019-01-17 18:15

Extending the idea, it's also possible to memoize functions with two input parameters:

function memoize2 (f)
   local cache = {}
   return function (x, y)
             if cache[x..','..y] then
                return cache[x..','..y]
             else
                local z = f(x,y)
                cache[x..','..y] = z
                return z
             end
          end
end

Notice that parameter order matters in the caching algorithm, so if parameter order doesn't matter in the functions to be memoized the odds of getting a cache hit would be increased by sorting the parameters before checking the cache.

But it's important to note that some functions can't be profitably memoized. I wrote memoize2 to see if the recursive Euclidean algorithm for finding the greatest common divisor could be sped up.

function gcd (a, b) 
   if b == 0 then return a end
   return gcd(b, a%b)
end

As it turns out, gcd doesn't respond well to memoization. The calculation it does is far less expensive than the caching algorithm. Ever for large numbers, it terminates fairly quickly. After a while, the cache grows very large. This algorithm is probably as fast as it can be.

查看更多
相关推荐>>
5楼-- · 2019-01-17 18:18

I've been inspired by this question to implement (yet another) flexible memoize function in Lua.

https://github.com/kikito/memoize.lua

Main advantages:

  • Accepts a variable number of arguments
  • Doesn't use tostring; instead, it organizes the cache in a tree structure, using the parameters to traverse it.
  • Works just fine with functions that return multiple values.

Pasting the code here as reference:

local globalCache = {}

local function getFromCache(cache, args)
  local node = cache
  for i=1, #args do
    if not node.children then return {} end
    node = node.children[args[i]]
    if not node then return {} end
  end
  return node.results
end

local function insertInCache(cache, args, results)
  local arg
  local node = cache
  for i=1, #args do
    arg = args[i]
    node.children = node.children or {}
    node.children[arg] = node.children[arg] or {}
    node = node.children[arg]
  end
  node.results = results
end


-- public function

local function memoize(f)
  globalCache[f] = { results = {} }
  return function (...)
    local results = getFromCache( globalCache[f], {...} )

    if #results == 0 then
      results = { f(...) }
      insertInCache(globalCache[f], {...}, results)
    end

    return unpack(results)
  end
end

return memoize
查看更多
登录 后发表回答