Elixir 从零 · 第 14 课(展开目录)
01 · 先让代码说话02 · 先认六种值03 · 把值装起来04 · 从旧 list 生成新 list05 · 让形状对上06 · 用 case 做选择07 · 把文字安全变成整数08 · 拆开 &1 和管道09 · 把代码放进项目10 · 真假不只靠 true11 · 走进嵌套数据12 · 一字不一定一字节13 · 让函数按形状接活14 · 把一列数收成一个值15 · 给不同问题选不同路16 · 给 map 一张名片17 · 让项目自己验答案18 · 让成功和失败长得一样19 · 读写文件先管好路20 · 只算眼前需要的21 · 同一句话,不同做法22 · 先约好模块会做什么23 · 把数据形状写在门口24 · 测试不只看一条好路25 · 把项目磨成一个命令26 · 定下小项目边界27 · 把真实文字洗干净28 · 只开三扇门29 · 把约定钉在代码旁30 · 打包,也留下脚印31 · 照任务书交卷
ELIXIR · 基础 · LESSON 1435 分钟

把一列数收成一个值

先读递归累加器,再用 `Enum.reduce/3` 写同一件事。

01 · 先看完整例子

两种写法得到同一个总和

先跑模块,再跑 reduce;两个结果应相同。

Elixir
defmodule Totals do
  def sum(numbers), do: sum(numbers, 0)

  # 空 list 返回累加器,递归在这里停
  defp sum([], total), do: total
  defp sum([number | rest], total), do: sum(rest, total + number)
end

Totals.sum([3, 5, 7])

# reduce 把同一套累加过程写成通用形式
Enum.reduce([3, 5, 7], 0, fn number, total -> total + number end)
先对照结果
  • 两种写法都得到 15。
02 · 回头拆代码

从第一行往下读

  1. 公开的 sum/1 给内部 sum/2 一个初始总和 0。
  2. 每一步拿走一个数字,并把它加进累加器。
  3. reduce 的匿名函数接收当前元素和当前累加结果。
03 · 认清新符号

符号不是暗号

[head | tail]

取出 list 第一项与剩余部分。

Enum.reduce(list, initial, fun)

从初始值开始逐项累积。

sum/2

两个参数版本可作为内部递归函数。

04 · 代码里的概念

把名字和意思对上

01

累加器

递归途中保存阶段结果的参数,如当前总和。

02

fold

把一组值从左到右收拢成一个结果;reduce 就是一种 fold。

05 · 再说清楚

这些写法为什么有用

递归每次取走一个元素,把更小的 list 交给自己。空 list 是停下来的地方。

累加器保存走到当前为止的结果。输入越来越短,累加器越来越接近答案。

Enum.reduce/3 把这套递归收进通用工具。先懂一次手写过程,再放心使用工具。

06 · 自己改一次

合上答案,动手

用 Enum.reduce/3 算 [2, 3, 4] 的乘积。

练习起点
Enum.reduce([2, 3, 4], ____, fn number, product ->
  ____
end)

目标结果: 得到 24。

卡住了,再看提示

乘法的初始值是 1;每步计算 number * product。

运行过以后,再看一种答案
一种答案
Enum.reduce([2, 3, 4], 1, fn number, product ->
  # 把当前数字乘进已有结果
  number * product
end)
想一想:求和时为什么用 0 作为初始累加器?

因为任何数加 0 仍是它自己;空 list 的总和也自然是 0。

带走

记住三句

  1. 1

    递归要让输入不断变小。

  2. 2

    累加器带着阶段结果前进。

  3. 3

    Enum.reduce/3 是常用收拢工具。

本课结束代码也跑过一次,就把这一课收好。