我正在学习函数式编程,我在 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]
}
}
可以做类似的东西吗?
通常,当我必须将状态存储在 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)
您在 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 中记忆函数,并附有示例和现成的代码。
是的,
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
您可以按照其他人的说法使用代理,但根据您的用例,代理会很慢。我在一些 2023 AdventOfCode 解决方案上使用了 Agent,与您这里的解决方案相比,它非常慢......
但我想对于几千个斐波那契数,Agent 就可以了。
在我的 AoC 问题中,我的备忘录中有数百万条条目。