1
- 18-列表速构 (Comprehension)
1
+ 18-速构 (Comprehension)
2
2
========
3
3
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
+ > “速构”是函数式语言中常见的概念,指的是定义规则来生成一系列元素填充新的数据集合。
10
9
这个概念我们在中学的数学课上其实就已经接触过,在大学高数中更为常见:
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 ` 指令中表达出来。
14
17
15
- Elixir中,使用枚举类型(如列表)来做循环操作是很常见的。对对象列表进行枚举时,
16
- 通常要有选择性地过滤掉其中一些元素,还有可能做一些变换。
17
- 列表速构(comprehensions)就是为此目的诞生的语法糖:把这些常见任务分组,
18
- 放到特殊的``` for ``` 中执行。
18
+ 例如,我们可以这样,生成原列表中每个元素的平方:
19
19
20
- 例如,我们可以这样计算列表中每个元素的平方:
21
20
``` elixir
22
21
iex> for n <- [1 , 2 , 3 , 4 ], do: n * n
23
22
[1 , 4 , 9 , 16 ]
24
23
```
25
- 注意看,``` <- ``` 符号就是模拟自``` ∈ ``` 的形象。
24
+
25
+ > 注意看,` <- ` 符号其实是模拟符号` ∈ ` 的形象。
26
26
这个例子用熟悉(当然,如果你高数课没怎么听那就另当别论)的数学符号表示就是:
27
+
27
28
```
28
- S = { X^2 | X ∈ [1,4], X ∈ N}
29
+ S = { X^2 | X ∈ [1,4], X ∈ N }
29
30
```
30
- 这个例子用常见的编程语言去理解,基本上类似于foreach...in...什么的。但是更强大。
31
31
32
- 一个列表速构由三部分组成:生成器,过滤器和收集器。
32
+ 速构由三部分组成:生成器,过滤器和收集式。
33
+
34
+ ## 生成器和过滤器
35
+
36
+ 在上面的例子中,` n <- [1, 2, 3, 4] ` 就是生成器。
37
+ 它字面意思上生成了即将要在速构中使用的数值。任何枚举类型(Enumerable)都可以传递给生成器表达式的右端:
33
38
34
- ##18 .2-生成器和过滤器
35
- 在上面的例子中,``` n <- [1, 2, 3, 4] ``` 就是生成器。
36
- 它字面意思上生成了即将要在速构中使用的数值。任何枚举类型都可以传递给生成器表达式的右端:
37
39
``` elixir
38
40
iex> for n <- 1 .. 4 , do: n * n
39
41
[1 , 4 , 9 , 16 ]
40
42
```
41
- 这个例子中的生成器是一个__ 范围__ 。
42
43
43
- 生成器表达式支持模式匹配,它会忽略所有不匹配的模式。
44
- 想象一下如果不用范围而是用一个键值列表,键只有``` :good ``` 和``` :bad ``` 两种,
45
- 来计算中间被标记成‘good’的元素的平方:
44
+ 生成器表达式左操作数支持模式匹配,它会** 忽略** 所有不匹配的模式。
45
+ 想象一下如果不用范围而是用一个键值列表作为生成器的数据源,它的键只有` :good ` 和` :bad ` 两种,
46
+ 我们仅要计算键为‘: good ’的元素值的平方:
47
+
46
48
``` elixir
47
49
iex> values = [good: 1 , good: 2 , bad: 3 , good: 4 ]
48
50
iex> for {:good , n} <- values, do: n * n
49
51
[1 , 4 , 16 ]
50
52
```
51
53
52
- 过滤器能过滤掉某些产生的值。例如我们可以只对奇数进行平方运算:
54
+ 除了使用模式匹配,过滤器也可以用来选择某些特定数值。
55
+ 例如我们可以只选择3的倍数,而丢弃其它数值:
56
+
53
57
``` 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 ]
57
61
```
58
- 过滤器会保留所有判断结果是非nil或非false的值。
59
62
60
- 总的来说,速构比直接使用枚举或流模块的函数提供了更精确的表述。
61
- 不但如此,速构还接受多个生成器和过滤器。下面就是一个例子,代码接受目录列表,
63
+ 速构过程会丢弃过滤器表达式结果为` false ` 或` nil ` 的值;其它值都会被保留。
64
+
65
+ 总的来说,速构提供了比直接使用` Enum ` 或` Stream ` 模块的函数更精确的表达。
66
+ 不但如此,速构还可以接受多个生成器和过滤器。下面就是一个例子,代码接受目录列表,
62
67
删除这些目录下的所有文件:
68
+
63
69
``` elixir
64
70
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
69
75
end
70
76
```
71
77
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...>>),把它转换为三元元组的列表:
74
126
75
- ## 18.2-比特串生成器
76
- 速构也支持比特串作为生成器,而且这种生成器在组织处理比特串的流时非常有用。
77
- 下面的例子中,程序从二进制数据(表示为<<像素1的R值,像素1的G值,像素1的B值,
78
- 像素2的R值,像素2的G...>>)中接收一个像素的列表,把它们转换为元组:
79
127
``` elixir
80
128
iex> pixels = << 213 , 45 , 132 , 64 , 76 , 32 , 76 , 0 , 0 , 234 , 32 , 15 >>
81
129
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 }]
83
131
```
132
+
84
133
比特串生成器可以和“普通的”枚举类型生成器混合使用,过滤器也是。
85
134
86
- ## 18.3-Into
87
- 在上面的例子中,速构返回一个列表作为结果。
88
- 但是,通过使用``` :into ``` 选项,速构的结果可以插入到一个不同的数据结构中。
89
- 例如,你可以使用比特串生成器加上``` :into ``` 来轻松地构成无空格字符串:
135
+ ## ` :into ` 选项
136
+
137
+ 在上面的例子中,速构返回列表作为结果。
138
+ 但是,通过使用``` :into ``` 选项,速构的结果可以插入到不同的数据结构中。
139
+ 例如,你可以使用比特串生成器加上``` :into ``` 来轻松地移除字符串中的空格:
140
+
90
141
``` elixir
91
142
iex> for << c <- " hello world " >> , c != ?\s , into: " " , do: << c>>
92
143
" helloworld"
93
144
```
94
145
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
+ 你可以使用速构实现一个回声终端,让其返回任何输入的字母的大写形式:
96
157
97
- 例如,IO模块提供了流。流既是Enumerable也是Collectable。
98
- 你可以使用速构实现一个回声终端,让其返回任何输入的东西的大写形式:
99
158
``` elixir
100
159
iex> stream = IO .stream (:stdio , :line )
101
160
iex> for line <- stream, into: stream do
@@ -104,4 +163,4 @@ iex> for line <- stream, into: stream do
104
163
```
105
164
106
165
现在在终端中输入任意字符串,你会看到同样的内容以大写形式被打印出来。
107
- 不幸的是,这个例子会让你的shell陷入到该速构代码中,只能用Ctrl+C两次来退出。
166
+ 不幸的是,这个例子会让你的shell陷入到该速构代码中,只能用Ctrl+C两次来退出:-) 。
0 commit comments