查看源代码 列表推导

在 Elixir 中,通常需要遍历一个可枚举对象,经常需要过滤掉一些结果并将值映射到另一个列表。列表推导是这种结构的语法糖:它们将这些常见任务分组到 for 特殊形式中。

例如,我们可以将一个整数列表映射到它们的平方值

iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]

列表推导由三个部分组成:生成器、过滤器和收集器。

生成器和过滤器

在上面的表达式中,n <- [1, 2, 3, 4] 是**生成器**。它实际上是生成要用于列表推导的值。任何可枚举对象都可以传递到生成器表达式的右侧

iex> for n <- 1..4, do: n * n
[1, 4, 9, 16]

生成器表达式也支持对其左侧进行模式匹配;所有不匹配的模式都将被忽略。想象一下,不是范围,而是键为原子 :good:bad 的关键字列表,我们只希望计算 :good 值的平方

iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]

除了模式匹配之外,还可以使用过滤器来选择一些特定元素。例如,我们可以选择 3 的倍数,并丢弃所有其他元素

iex> for n <- 0..5, rem(n, 3) == 0, do: n * n
[0, 9]

列表推导会丢弃所有过滤器表达式返回 falsenil 的元素;所有其他值都会被选中。

列表推导通常比使用来自 EnumStream 模块的等效函数提供更简洁的表示。此外,列表推导还允许给出多个生成器和过滤器。这是一个接收目录列表并获取这些目录中每个文件大小的示例

dirs = ["/home/mikey", "/home/james"]

for dir <- dirs,
    file <- File.ls!(dir),
    path = Path.join(dir, file),
    File.regular?(path) do
  File.stat!(path).size
end

也可以使用多个生成器来计算两个列表的笛卡尔积

iex> for i <- [:a, :b, :c], j <- [1, 2], do:  {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]

最后,请记住,列表推导内的变量赋值,无论是在生成器、过滤器还是代码块内,都不会反映在列表推导之外。

比特串生成器

比特串生成器也受支持,当您需要在比特串流上进行推导时非常有用。下面的示例接收来自二进制文件的像素列表,以及它们各自的红色、绿色和蓝色值,并将它们转换为包含三个元素的元组

iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]

比特串生成器可以与“常规”可枚举生成器混合使用,并且也支持过滤器。

:into 选项

在上面的示例中,所有列表推导都返回列表作为结果。但是,可以通过将 :into 选项传递给列表推导,将列表推导的结果插入到不同的数据结构中。

例如,比特串生成器可以与 :into 选项一起使用,以便轻松地从字符串中删除所有空格

iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"

集合、映射和其他字典也可以传递给 :into 选项。通常,:into 接受任何实现 Collectable 协议的结构。

:into 的一个常见用例是转换映射中的值

iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
%{"a" => 1, "b" => 4}

让我们再举一个使用流的例子。由于 IO 模块提供了流(既是 Enumerable 又是 Collectable),因此可以使用列表推导来实现一个回显终端,该终端会回显输入的字符串的大写版本

iex> stream = IO.stream(:stdio, :line)
iex> for line <- stream, into: stream do
...>   String.upcase(line) <> "\n"
...> end

现在在终端中输入任何字符串,您将看到相同的字符串将以大写形式打印出来。不幸的是,这个例子也让您的 IEx shell 卡在了列表推导中,所以您需要按两次 Ctrl+C 才能退出它。:)

其他选项

列表推导支持其他选项,例如 :reduce:uniq。以下是一些关于列表推导的更多资源