【问题标题】:Fibonacci with memoization elixir斐波那契与记忆灵丹妙药
【发布时间】:2018-10-11 14:12:06
【问题描述】:

我正在学习函数式编程,我在 elixir 中做了一个简单的斐波那契。

我知道在函数式编程中不可能改变值,我编写了一个代码来制作带有记忆的斐波那契,但代码很糟糕。 如何改进此代码?

defmodule Fib do
  def fib_memoized(0, memo) do
    {0, memo}
  end

  def fib_memoized(1, memo) do
    {1, memo}
  end

  def fib_memoized(n, memo \\ %{}) do
    if Map.has_key?(memo, n) do
      { memo[n], memo }
    else
      {n1, memo1} = fib_memoized(n-1, memo)
      {n2, memo2} = fib_memoized(n-2, memo1)

      value = n1+n2

      {value, Map.merge(memo2, %{n => value})}
    end
  end

  def fib(n) do
    { value, _ } = fib_memoized(n)
    value 
  end
end

IO.puts Fib.fib(1000)

在javascript中可以创建一个高阶函数并保存“地图”并更新这个。

例如:

function memoization(fn) {
    let memo = {}
    return function (n) {
      if(!memo[n]) {
        memo[n] = fn(n)
      }
      return memo[n]
    }
  }

有可能做出类似的东西吗?

【问题讨论】:

  • Memoization and Dynamic ProgrammingReal World Ocaml一书中谈到了记忆递归函数。该知识可以翻译成任何需要通用memoization 函数的语言。它甚至在其中一个示例中使用了斐波那契程序。希望这会有所帮助:D

标签: functional-programming elixir fibonacci memoization


【解决方案1】:

您在 JS 中发布的片段到 Elixir 的逐字翻译如下:

memoization = fn fun ->
  fn n, acc ->
    acc =
      if(!acc[n]) do
        Process.sleep(1_000)
        IO.inspect acc, label: "Just put... Need a rest... Sleeping... Zzzz..."
        Map.put(acc, n, fun.(n))
      else
        acc
      end
    {acc, Map.get(acc, n)}
  end
end

fun = memoization.(& &1 * 2)
{acc, _} = IO.inspect fun.(42, %{}), label: "Result for 42"
{acc, _} = IO.inspect fun.(42, acc), label: "Result for 42"
{acc, _} = IO.inspect fun.(3.14, acc), label: "Result for 3.14"
{_, _} = IO.inspect fun.(3.14, acc), label: "Result for 3.14"

导致:

# Just put... Need a rest... Sleeping... Zzzz...: %{}
# Result for 42: {%{42 => 84}, 84}
# Result for 42: {%{42 => 84}, 84}
# Just put... Need a rest... Sleeping... Zzzz...: %{42 => 84}
# Result for 3.14: {%{42 => 84, 3.14 => 6.28}, 6.28}
# Result for 3.14: {%{42 => 84, 3.14 => 6.28}, 6.28}

由于 Elixir 中没有全局状态,因此必须通过累加器。哦,等等,真的有!

我已经写了一个详细的blog post on how to memoize functions in Elixir 带有一个示例和现成的代码。

【讨论】:

    【解决方案2】:

    通常,当我必须在 elixir 中存储状态时,我会启动一个流程来执行此操作。在斐波那契案例中,Agent 非常适合。

    可以这样写:

    defmodule Fib do
      use Agent
    
      def start do
        Agent.start_link(fn -> %{0 => 0, 1 => 1} end, name: __MODULE__)
      end
    
      def fib(n) do
        cached_value = Agent.get(__MODULE__, &(Map.get(&1, n)))
    
        if cached_value do
          cached_value
        else
          v = fib(n - 1) + fib(n - 2)
          Agent.update(__MODULE__, &(Map.put(&1, n, v)))
          v
        end
      end
    end
    
    {:ok, _} = Fib.start
    IO.puts Fib.fib(1000)
    

    【讨论】:

    • 我认为这是一个惯用的解决方案。
    【解决方案3】:

    是的,Agentmemo 一样好用。

    defmodule Fibonacci do
      @memo Module.concat(__MODULE__, Memo)
    
      def fib(n) do
        Agent.start_link(fn -> %{0 => 0, 1 => 1} end, name: @memo)
    
        result = Agent.get(@memo, fn state -> Map.get(state, n) end)
        do_fib(result, n)
      end
    
      defp do_fib(nil = _result, n) do
        value = fib(n - 1) + fib(n - 2)
        Agent.update(@memo, fn state -> Map.put(state, n, value) end)
        value
      end
    
      defp do_fib(result, _n) do
        result
      end
    end
    

    同样可以使用Agent(memo) 计算阶乘。

    defmodule Factorial do
      @memo Module.concat(__MODULE__, Memo)
    
      def fact(n) do
        Agent.start_link(fn -> %{0 => 1, 1 => 1} end, name: @memo)
    
        result = Agent.get(@memo, fn state -> Map.get(state, n) end)
        do_fact(result, n)
      end
    
      defp do_fact(nil, n) do
        value = n * fact(n - 1)
        Agent.update(@memo, fn state -> Map.put(state, n, value) end)
        value
      end
    
      defp do_fact(result, _n) do
        result
      end
    end
    

    【讨论】:

      猜你喜欢
      • 2011-12-14
      • 2019-06-04
      • 2015-10-19
      • 2021-11-17
      • 2018-11-09
      • 2021-05-21
      • 2015-06-16
      • 2021-08-17
      • 1970-01-01
      相关资源
      最近更新 更多