如何优化Elixir中带Memoization的Fibonacci实现代码?
优化Elixir带记忆化的斐波那契实现
嘿,我看到你正在用Elixir探索函数式编程的斐波那契实现,还想到了用记忆化(Memoization)来优化性能,这思路真的很棒!先帮你梳理下现有代码的几个小问题,再给出几个实用的优化方向~
现有代码的潜在问题
- 对外接口不够友好:需要用户手动传递
memo参数,正常使用时用户只需要输入斐波那契的序号n就够了 - 条件判断冗余:每次递归都用
if Map.has_key?做检查,不如用Elixir原生的模式匹配来得优雅 - memo传递的处理可以更流畅:拆分的两次递归调用后,memo的更新逻辑可以更统一
优化方案一:封装内部函数,隐藏记忆化细节
这种方案最适合单次计算场景,把memo的维护逻辑放在私有函数里,对外暴露简洁的公共接口:
defmodule Fib do # 对外公共接口,用户只需要传n即可 def fib(n) when n >= 0 do fib_memoized(n, %{0 => 0, 1 => 1}) |> elem(0) # 只返回计算结果,不需要暴露memo给用户 end # 私有函数,负责处理带记忆化的递归逻辑 defp fib_memoized(n, memo) do case Map.fetch(memo, n) do # 如果memo里已有结果,直接返回 {:ok, value} -> {value, memo} # 如果没有,递归计算n-1和n-2,然后更新memo :error -> {n1, memo1} = fib_memoized(n-1, memo) {n2, memo2} = fib_memoized(n-2, memo1) result = n1 + n2 {result, Map.put(memo2, n, result)} end end end
优化点说明:
- 用
defp定义私有函数,隐藏内部的记忆化实现,符合封装原则 - 用
Map.fetch配合case的模式匹配代替if判断,更贴合Elixir的函数式风格 - 公共接口直接返回结果,不需要用户处理memo参数,使用起来更直观
优化方案二:用Agent维护全局缓存(适合多次查询场景)
如果需要多次查询不同的斐波那契值,用Agent来维护一个全局的记忆化缓存会更高效,避免重复计算:
defmodule FibAgent do use Agent # 启动Agent,初始化缓存(包含基础情况) def start_link(_opts) do Agent.start_link(fn -> %{0 => 0, 1 => 1} end, name: __MODULE__) end # 对外公共接口,查询第n个斐波那契数 def fib(n) when n >= 0 do Agent.get_and_update(__MODULE__, fn memo -> case Map.fetch(memo, n) do {:ok, value} -> {value, memo} :error -> # 递归查询n-1和n-2(会自动利用缓存) n1 = fib(n-1) n2 = fib(n-2) result = n1 + n2 # 更新缓存并返回结果 {result, Map.put(memo, n, result)} end end) end end
使用方式:
# 先启动Agent FibAgent.start_link([]) # 查询结果 FibAgent.fib(10) # 返回55 FibAgent.fib(15) # 会复用之前缓存的10以内的结果,速度更快
优化点说明:
- 用Agent维护全局缓存,多次查询时自动复用之前的计算结果
- 同样用模式匹配处理缓存的存在性,代码简洁优雅
- 适合需要频繁查询不同斐波那契数的场景
额外的代码质量建议
- 加上参数守卫(比如
when n >= 0),避免传入负数导致无限递归 - 把基础情况(0和1)提前存入初始memo,减少递归的边界判断
- 优先用Elixir的原生函数(比如
Map.fetch、elem)代替自定义的条件判断,代码更易读也更符合社区规范
内容的提问来源于stack exchange,提问作者Renato Cassino
相关产品推荐
相关产品推荐

