斐波那契与记忆灵丹妙药

问题描述 投票:0回答:4

我正在学习函数式编程,我在 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]
    }
  }

可以做类似的东西吗?

functional-programming elixir fibonacci memoization
4个回答
3
投票

通常,当我必须将状态存储在 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)

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 中没有全局状态,因此需要传递累加器。哦,等等,真的有!

我写了一篇详细的 博客文章,介绍了如何在 Elixir 中记忆函数,并附有示例和现成的代码。


0
投票

是的,

Agent
memo
一样出色。

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

0
投票

您可以按照其他人的说法使用代理,但根据您的用例,代理会很慢。我在一些 2023 AdventOfCode 解决方案上使用了 Agent,与您这里的解决方案相比,它非常慢......

但我想对于几千个斐波那契数,Agent 就可以了。

在我的 AoC 问题中,我的备忘录中有数百万条条目。

© www.soinside.com 2019 - 2024. All rights reserved.