Skip to content

Commit afe6804

Browse files
committed
update comprehens
1 parent 11c7f1b commit afe6804

File tree

1 file changed

+111
-52
lines changed

1 file changed

+111
-52
lines changed

18-comprehensions.md

+111-52
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,160 @@
1-
18-列表速构(Comprehension)
1+
18-速构(Comprehension)
22
========
33

4-
> *Comprehensions* 翻译成“速构”不知道贴不贴切,
5-
这参照了《Erlang/OTP in Action》译者的用辞。国内一些Python书(是的,Python也有这个概念)
6-
中翻译为“推导式”、“递推式”。不过这里请不要纠结它翻译成啥,主要是弄明白它是啥。本章
7-
(或者本文)中出现的话,一般翻译成“速构”或是保留英文。
8-
“速构”是函数式语言中常见的概念,它大体上指的是用一套规则(比如从另一个列表,过滤掉一些元素)
9-
来生成元素填充新列表。
4+
> *Comprehensions* 翻译成“速构”是参照了《Erlang/OTP in Action》译者的用辞。
5+
国内一些Python书(是的,Python也有这个概念)中翻译为“推导式”、“递推式”。
6+
这里不用纠结它的翻译,更重要的是需要弄明白它是什么。
7+
8+
>“速构”是函数式语言中常见的概念,指的是定义规则来生成一系列元素填充新的数据集合。
109
这个概念我们在中学的数学课上其实就已经接触过,在大学高数中更为常见:
11-
```{ x | x ∈ N }```,字面上意思是其表示了一个集合,这个集合里所有元素属于自然数N范围
12-
(也就是自然数集合)。
13-
相关知识可见[WIKI](http://en.wikipedia.org/wiki/List_comprehension)
10+
`{ x | x ∈ N }`这个表达式,字面上意思是:这是一个集合,
11+
这个集合里每个元素x符合“x属于自然数N”这个条件。即,用自然数集合的所有元素来构成这个集合。
12+
相关知识可参考[WIKI](http://en.wikipedia.org/wiki/List_comprehension)
13+
14+
Elixir中,使用枚举类型(Enumerable,如列表)来做循环操作是很常见的,
15+
通常还搭配过滤(filtering)和映射(mapping)行为。
16+
速构(comprehensions)就是为此目的诞生的语法糖:把这些常见任务分组,放到特殊的`for`指令中表达出来。
1417

15-
Elixir中,使用枚举类型(如列表)来做循环操作是很常见的。对对象列表进行枚举时,
16-
通常要有选择性地过滤掉其中一些元素,还有可能做一些变换。
17-
列表速构(comprehensions)就是为此目的诞生的语法糖:把这些常见任务分组,
18-
放到特殊的```for```中执行。
18+
例如,我们可以这样,生成原列表中每个元素的平方:
1919

20-
例如,我们可以这样计算列表中每个元素的平方:
2120
```elixir
2221
iex> for n <- [1, 2, 3, 4], do: n * n
2322
[1, 4, 9, 16]
2423
```
25-
注意看,```<-```符号就是模拟自``````的形象。
24+
25+
>注意看,`<-`符号其实是模拟符号``的形象。
2626
这个例子用熟悉(当然,如果你高数课没怎么听那就另当别论)的数学符号表示就是:
27+
2728
```
28-
S = { X^2 | X ∈ [1,4], X ∈ N}
29+
S = { X^2 | X ∈ [1,4], X ∈ N }
2930
```
30-
这个例子用常见的编程语言去理解,基本上类似于foreach...in...什么的。但是更强大。
3131

32-
一个列表速构由三部分组成:生成器,过滤器和收集器。
32+
速构由三部分组成:生成器,过滤器和收集式。
33+
34+
## 生成器和过滤器
35+
36+
在上面的例子中,`n <- [1, 2, 3, 4]`就是生成器。
37+
它字面意思上生成了即将要在速构中使用的数值。任何枚举类型(Enumerable)都可以传递给生成器表达式的右端:
3338

34-
##18.2-生成器和过滤器
35-
在上面的例子中,```n <- [1, 2, 3, 4]```就是生成器。
36-
它字面意思上生成了即将要在速构中使用的数值。任何枚举类型都可以传递给生成器表达式的右端:
3739
```elixir
3840
iex> for n <- 1..4, do: n * n
3941
[1, 4, 9, 16]
4042
```
41-
这个例子中的生成器是一个__范围__
4243

43-
生成器表达式支持模式匹配,它会忽略所有不匹配的模式。
44-
想象一下如果不用范围而是用一个键值列表,键只有```:good``````:bad```两种,
45-
来计算中间被标记成‘good’的元素的平方:
44+
生成器表达式左操作数支持模式匹配,它会**忽略**所有不匹配的模式。
45+
想象一下如果不用范围而是用一个键值列表作为生成器的数据源,它的键只有`:good``:bad`两种,
46+
我们仅要计算键为‘:good’的元素值的平方:
47+
4648
```elixir
4749
iex> values = [good: 1, good: 2, bad: 3, good: 4]
4850
iex> for {:good, n} <- values, do: n * n
4951
[1, 4, 16]
5052
```
5153

52-
过滤器能过滤掉某些产生的值。例如我们可以只对奇数进行平方运算:
54+
除了使用模式匹配,过滤器也可以用来选择某些特定数值。
55+
例如我们可以只选择3的倍数,而丢弃其它数值:
56+
5357
```elixir
54-
iex> require Integer
55-
iex> for n <- 1..4, Integer.odd?(n), do: n * n
56-
[1, 9]
58+
iex> multiple_of_3? = fn(n) -> rem(n, 3) == 0 end
59+
iex> for n <- 0..5, multiple_of_3?.(n), do: n * n
60+
[0, 9]
5761
```
58-
过滤器会保留所有判断结果是非nil或非false的值。
5962

60-
总的来说,速构比直接使用枚举或流模块的函数提供了更精确的表述。
61-
不但如此,速构还接受多个生成器和过滤器。下面就是一个例子,代码接受目录列表,
63+
速构过程会丢弃过滤器表达式结果为`false``nil`的值;其它值都会被保留。
64+
65+
总的来说,速构提供了比直接使用`Enum``Stream`模块的函数更精确的表达。
66+
不但如此,速构还可以接受多个生成器和过滤器。下面就是一个例子,代码接受目录列表,
6267
删除这些目录下的所有文件:
68+
6369
```elixir
6470
for dir <- dirs,
65-
file <- File.ls!(dir),
66-
path = Path.join(dir, file),
67-
File.regular?(path) do
68-
File.rm!(path)
71+
file <- File.ls!(dir),
72+
path = Path.join(dir, file),
73+
File.regular?(path) do
74+
File.stat!(path).size
6975
end
7076
```
7177

72-
需要记住的是,在速构中,变量赋值这种事应在生成器中进行。
73-
因为在过滤器或代码块中的赋值操作不会反映到速构外面去。
78+
多生成器还可以用来生成两个列表的笛卡尔积:
79+
80+
```elixir
81+
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
82+
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
83+
```
84+
85+
关于多生成器、过滤器的更高级些的例子:计算毕达哥拉斯三元数(Pythagorean triples)。
86+
毕氏三元数一组正整数满足`a * a + b * b = c * c`,让我们在文件`triples.exs`里写这个速构:
87+
88+
```elixir
89+
defmodule Triple do
90+
def pythagorean(n) when n > 0 do
91+
for a <- 1..n,
92+
b <- 1..n,
93+
c <- 1..n,
94+
a + b + c <= n,
95+
a*a + b*b == c*c,
96+
do: {a, b, c}
97+
end
98+
end
99+
```
100+
101+
然后,在终端里:
102+
103+
```
104+
iex triple.exs
105+
```
106+
107+
```elixir
108+
iex> Triple.pythagorean(5)
109+
[]
110+
iex> Triple.pythagorean(12)
111+
[{3, 4, 5}, {4, 3, 5}]
112+
iex> Triple.pythagorean(48)
113+
[{3, 4, 5}, {4, 3, 5}, {5, 12, 13}, {6, 8, 10}, {8, 6, 10}, {8, 15, 17},
114+
{9, 12, 15}, {12, 5, 13}, {12, 9, 15}, {12, 16, 20}, {15, 8, 17}, {16, 12, 20}]
115+
```
116+
117+
Finally, keep in mind that variable assignments inside the comprehension, be it in generators, filters or inside the block, are not reflected outside of the comprehension.
118+
119+
需要记住的是,在生成器、过滤器或者代码块中赋值的变量,不会暴露到速构外面去。
120+
121+
## 比特串生成器
122+
123+
速构也支持比特串作为生成器,这种生成器在处理比特流时非常有用。
124+
下面的例子中,程序接收一个表示像素颜色的二进制串(格式为<<像素1的R值,像素1的G值,像素1的B值,
125+
像素2的R值,像素2的G...>>),把它转换为三元元组的列表:
74126

75-
## 18.2-比特串生成器
76-
速构也支持比特串作为生成器,而且这种生成器在组织处理比特串的流时非常有用。
77-
下面的例子中,程序从二进制数据(表示为<<像素1的R值,像素1的G值,像素1的B值,
78-
像素2的R值,像素2的G...>>)中接收一个像素的列表,把它们转换为元组:
79127
```elixir
80128
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
81129
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
82-
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
130+
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
83131
```
132+
84133
比特串生成器可以和“普通的”枚举类型生成器混合使用,过滤器也是。
85134

86-
## 18.3-Into
87-
在上面的例子中,速构返回一个列表作为结果。
88-
但是,通过使用```:into```选项,速构的结果可以插入到一个不同的数据结构中。
89-
例如,你可以使用比特串生成器加上```:into```来轻松地构成无空格字符串:
135+
## `:into`选项
136+
137+
在上面的例子中,速构返回列表作为结果。
138+
但是,通过使用```:into```选项,速构的结果可以插入到不同的数据结构中。
139+
例如,你可以使用比特串生成器加上```:into```来轻松地移除字符串中的空格:
140+
90141
```elixir
91142
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
92143
"helloworld"
93144
```
94145

95-
集合、图以及其他字典类型都可以传递给```:into```选项。总的来说,```:into```接受任何实现了_Collectable_协议的数据结构。
146+
集合、图、其他字典类型都可以传递给`:into`选项。总的来说,`:into`接受任何实现了_Collectable_协议的数据结构。
147+
148+
`:into`选项一个常见的作用是,不用操作键,而改变图中元素的值:
149+
150+
```elixir
151+
iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
152+
%{"a" => 1, "b" => 4}
153+
```
154+
155+
再来一个使用流的例子。因为`IO`模块提供了流(既是Enumerable也是Collectable)。
156+
你可以使用速构实现一个回声终端,让其返回任何输入的字母的大写形式:
96157

97-
例如,IO模块提供了流。流既是Enumerable也是Collectable。
98-
你可以使用速构实现一个回声终端,让其返回任何输入的东西的大写形式:
99158
```elixir
100159
iex> stream = IO.stream(:stdio, :line)
101160
iex> for line <- stream, into: stream do
@@ -104,4 +163,4 @@ iex> for line <- stream, into: stream do
104163
```
105164

106165
现在在终端中输入任意字符串,你会看到同样的内容以大写形式被打印出来。
107-
不幸的是,这个例子会让你的shell陷入到该速构代码中,只能用Ctrl+C两次来退出。
166+
不幸的是,这个例子会让你的shell陷入到该速构代码中,只能用Ctrl+C两次来退出:-)

0 commit comments

Comments
 (0)