문제

내가 쓰는 기능을 찾기 삼각형 번호 그리고 자연적인 방법으로 그것을 쓰는 것을 재귀적으로:

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

그러나 시도를 계산하는 첫 번째 100,000 삼각형의 번호와 함께 실패 stack overflow 습니다.이는 최상의 기능을 ,그러나 다른 클라이언트, 지만,저는 솔루션을 원하는 것입니다,그러나 다른 클라이언트는 어떤 기능을 전달합니다.

도움이 되었습니까?

해결책

나는 이와 같은 것이 Lua의 가변 인수 목록과 함께 작동해야한다고 확신합니다.

local function varg_tostring(...)
    local s = select(1, ...)
    for n = 2, select('#', ...) do
        s = s..","..select(n,...)
    end
    return s
end

local function memoize(f)
    local cache = {}
    return function (...)
        local al = varg_tostring(...)
        if cache[al] then
            return cache[al]
        else
            local y = f(...)
            cache[al] = y
            return y
        end
    end
end

아마도 인수 목록이 toString ()로 변환 될 수 있도록 __TOSTRING이있는 메타 타블로 영리한 일을 할 수도 있습니다. 오 가능성.

다른 팁

Mathematica는 해시와 기능 호출이 동일한 구문을 사용한다는 사실에 의존하여 메모 화를하는 특히 매끄러운 방법을 가지고 있습니다.

triangle[0] = 0;
triangle[x_] := triangle[x] = x + triangle[x-1]

그게 다야. 패턴 매칭 기능 호출 규칙은보다 일반적인 정의 전에 항상보다 구체적인 정의를 사용하는 것이기 때문에 작동합니다.

물론 지적한 바와 같이이 예제는 폐쇄 형 솔루션이 있습니다. triangle[x_] := x*(x+1)/2. Fibonacci 번호는 Memoization을 추가하면 급격한 속도를 제공하는 방법의 전형적인 예입니다.

fib[0] = 1;
fib[1] = 1;
fib[n_] := fib[n] = fib[n-1] + fib[n-2]

비록 그것도 폐쇄 형식에 해당하지만, 비록 지저분하지만 : http://mathworld.wolfram.com/fibonaccinumber.html

나는 당신이 "루프 만 사용할 수 있기 때문에 이것을 메모 화에 부적절하다고 제안한 사람에 동의하지 않습니다. 메모 화의 요점은 반복 함수 호출이 O (1) 시간이라는 것입니다. 그것은 O (n)보다 훨씬 낫습니다. 실제로, 메모 화 된 구현이 폐쇄 형식 구현보다 성능이 향상되는 시나리오를 조정할 수도 있습니다!

당신은 또한 당신의 원래 문제에 대해 잘못된 질문을하고 있습니다;)

이것은 해당 경우에 더 좋은 방법입니다.

삼각형 (n) = n * (n -1) / 2

더욱이, 공식에 그러한 깔끔한 솔루션이 없다고 가정하면, 여기서는 여전히 가난한 접근법이 될 것입니다. 이 경우 간단한 루프를 작성하는 것이 좋습니다. 보다 이 답변 완전한 토론을 위해.

C# 3.0- 재귀 함수의 경우 다음과 같은 작업을 수행 할 수 있습니다.

public static class Helpers
{
    public static Func<A, R> Memoize<A, R>(this Func<A, Func<A,R>,  R> f)
    {
        var map = new Dictionary<A, R>();
        Func<A, R> self = null;
        self = (a) =>
        {
            R value;
            if (map.TryGetValue(a, out value))
                return value;
            value = f(a, self);
            map.Add(a, value);
            return value;
        };
        return self;
    }        
}

그런 다음 다음과 같은 메모 화 된 피보나치 기능을 만들 수 있습니다.

var memoized_fib = Helpers.Memoize<int, int>((n,fib) => n > 1 ? fib(n - 1) + fib(n - 2) : n);
Console.WriteLine(memoized_fib(40));

스칼라 (테스트되지 않은) :

def memoize[A, B](f: (A)=>B) = {
  var cache = Map[A, B]()

  { x: A =>
    if (cache contains x) cache(x) else {
      val back = f(x)
      cache += (x -> back)

      back
    }
  }
}

이것은 Arity 1의 기능에만 적용되지만 카레를 사용하면 작동하게 할 수 있습니다. 더 미묘한 문제는 그 것입니다 memoize(f) != memoize(f) 모든 기능에 대해 f. 이것을 고치는 매우 교활한 방법 중 하나는 다음과 같습니다.

val correctMem = memoize(memoize _)

나는 이것이 컴파일 될 것이라고 생각하지 않지만 아이디어를 설명합니다.

업데이트: 주석가들은 메모 화가 재귀를 최적화하는 좋은 방법이라고 지적했다. 틀림없이, 나는 일반화 된 메모 화가 구축하기가 그렇게 사소하지 않은 언어 (c#)에서 일하기 때문에 이전에 이것을 고려하지 않았습니다. 소금 한 알을 염두에두고 아래 게시물을 가져 가십시오.

제 생각에는 누가는 가장 적합한 솔루션을 가지고있을 것입니다 이 문제에 대해서는 추억이 일반적으로 스택 오버플로 문제에 대한 해결책이 아닙니다.

스택 오버 플로우는 일반적으로 플랫폼이 처리 할 수있는 것보다 더 깊은 재귀로 인해 발생합니다. 언어는 때때로 지원 "꼬리 재귀"재귀 호출을위한 새로운 컨텍스트를 만드는 대신 현재 통화의 맥락을 재사용합니다. 그러나 많은 주류 언어/플랫폼은 이것을 지원하지 않습니다. C#은 테일 리퍼션에 대한 고유 한 지원이 없습니다. .NET 지터의 64 비트 버전은 IL 레벨에서 최적화로 적용 할 수 있으며, 32 비트 플랫폼을 지원 해야하는 경우 쓸모가 없습니다.

언어가 꼬리 재귀를 지원하지 않는 경우, 스택 오버 플로우를 피하기위한 최선의 옵션은 명시 적 루프 (훨씬 덜 우아하지만 때로는 필요한 경우) 또는이 문제에 대한 Luke와 같은 비 정기 알고리즘을 찾는 것입니다.

function memoize (f)
   local cache = {}
   return function (x)
             if cache[x] then
                return cache[x]
             else
                local y = f(x)
                cache[x] = y
                return y
             end
          end
end

triangle = memoize(triangle);

참고하는 것을 방지 stack overflow,삼각형은 여전히 할 필요가 시드됩니다.

여기에는 뭔가하지 않고 작동을 변환하는 인수 문자열입니다.주의해야 할 점은 그것을 처리할 수 없는 전무 인수입니다.하지만 받아들인 솔루션을 수 없는 값을 구분 nil 문자열에서 "nil", 그래서 아마 확인.

local function m(f)
  local t = { }
  local function mf(x, ...) -- memoized f
    assert(x ~= nil, 'nil passed to memoized function')
    if select('#', ...) > 0 then
      t[x] = t[x] or m(function(...) return f(x, ...) end)
      return t[x](...)
    else
      t[x] = t[x] or f(x)
      assert(t[x] ~= nil, 'memoized function returns nil')
      return t[x]
    end
  end
  return mf
end

나는이 질문에서 LUA에서 유연한 메모 화 기능을 구현하기 위해 영감을 받았습니다.

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

주요 장점 :

  • 가변 수의 인수를 수락합니다
  • Tostring을 사용하지 않습니다. 대신, 매개 변수를 사용하여 트리 구조에서 캐시를 구성하여 트리를 통과합니다.
  • 반환하는 기능으로 잘 작동합니다 여러 값.

여기에 참조로 코드를 붙여 넣기 :

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

도움이 될 수있는 일반적인 C# 3.0 구현은 다음과 같습니다.

public static class Memoization
{
    public static Func<T, TResult> Memoize<T, TResult>(this Func<T, TResult> function)
    {
        var cache = new Dictionary<T, TResult>();
        var nullCache = default(TResult);
        var isNullCacheSet = false;
        return  parameter =>
                {
                    TResult value;

                    if (parameter == null && isNullCacheSet)
                    {
                        return nullCache;
                    }

                    if (parameter == null)
                    {
                        nullCache = function(parameter);
                        isNullCacheSet = true;
                        return nullCache;
                    }

                    if (cache.TryGetValue(parameter, out value))
                    {
                        return value;
                    }

                    value = function(parameter);
                    cache.Add(parameter, value);
                    return value;
                };
    }
}

(a 프랑스 블로그 기사)

다른 언어로 회고록을 게시하는 정맥에서, 나는 언어를 바꾸는 C ++ 예제로 @onebyone.livejournal.com에 응답하고 싶습니다.

먼저, 단일 arg 함수에 대한 메모리 자 :

template <class Result, class Arg, class ResultStore = std::map<Arg, Result> >
class memoizer1{
public:
    template <class F>
    const Result& operator()(F f, const Arg& a){
        typename ResultStore::const_iterator it = memo_.find(a);
        if(it == memo_.end()) {
            it = memo_.insert(make_pair(a, f(a))).first;
        }
        return it->second;
    }
private:
    ResultStore memo_;
};

Memoizer의 인스턴스를 만들고 기능과 인수를 공급하십시오. 두 가지 다른 기능간에 동일한 메모를 공유하지 않도록하십시오 (그러나 동일한 기능의 다른 구현간에 공유 할 수 있음).

다음으로 드라이버 Functon 및 구현. 드라이버 함수 만 공개 int fib (int)가 필요합니다. // 드라이버 int fib_ (int); // 구현

구현 :

int fib_(int n){
    ++total_ops;
    if(n == 0 || n == 1) 
        return 1;
    else
        return fib(n-1) + fib(n-2);
}

그리고 운전자, 메모 화

int fib(int n) {
    static memoizer1<int,int> memo;
    return memo(fib_, n);
}

출력을 보여주는 Permalink codepad.org에서. 정확성을 확인하기 위해 통화 수가 측정됩니다. (여기에 단위 테스트 삽입 ...)

이것은 하나의 입력 함수 만 기억합니다. 독자의 운동으로 남은 여러 개의 Args에 대한 일반화.

Perl Generic Memoization에서는 쉽게 얻을 수 있습니다. Memoize 모듈은 Perl 코어의 일부이며 신뢰할 수 있고 유연하며 사용하기 쉬운 것입니다.

Manpage의 예 :

# This is the documentation for Memoize 1.01
use Memoize;
memoize('slow_function');
slow_function(arguments);    # Is faster than it was before

기능의 메모 화를 추가, 제거 및 사용자 정의 할 수 있습니다. 런타임에! 사용자 정의 기념품 계산을위한 콜백을 제공 할 수 있습니다.

Memoize.pm은 Memento 캐시를 지속적으로 만들기위한 시설이 있으므로 프로그램의 호출마다 다시 채워질 필요가 없습니다!

문서는 다음과 같습니다. http://perldoc.perl.org/5.8.8/memoize.html

보다 이 블로그 게시물 일반 스칼라 솔루션의 경우 최대 4 개의 인수.

아이디어를 확장하면 두 가지 입력 매개 변수로 기능을 메모 할 수도 있습니다.

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

매개 변수 순서는 캐싱 알고리즘에서 중요하므로 캐시를 확인하기 전에 매개 변수를 정렬하여 캐시 히트를 얻을 확률이 메모 될 함수에서 매개 변수 순서가 중요하지 않은 경우에 주목하십시오.

그러나 일부 기능은 수익성있게 메모 할 수 없다는 점에 유의해야합니다. 나는 썼다 Memoize2 재귀가 있는지 확인합니다 유클리드 알고리즘 가장 큰 일반적인 제수를 찾기 위해서는 속도가 높아질 수 있습니다.

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

결과적으로 GCD 메모 화에 잘 대응하지 않습니다. 계산은 캐싱 알고리즘보다 훨씬 저렴합니다. 많은 수의 경우 상당히 빠르게 종료됩니다. 잠시 후 캐시는 매우 커집니다. 이 알고리즘은 아마도 가능한 한 빠릅니다.

재귀는 필요하지 않습니다. N 번째 삼각형 번호는 N (N-1)/2이므로 ...

public int triangle(final int n){
   return n * (n - 1) / 2;
}

이것을 다시 생각하지 마십시오. x*(x+1)/2 공식을 사용하거나 간단히 값을 반복하고 갈 때 메모 화하십시오.

int[] memo = new int[n+1];
int sum = 0;
for(int i = 0; i <= n; ++i)
{
  sum+=i;
  memo[i] = sum;
}
return memo[n];
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top