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
2221iex> 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
3840iex> 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
4749iex> values = [good: 1 , good: 2 , bad: 3 , good: 4 ]
4850iex> 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
6470for 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
6975end
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
80128iex> pixels = << 213 , 45 , 132 , 64 , 76 , 32 , 76 , 0 , 0 , 234 , 32 , 15 >>
81129iex> 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
91142iex> 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
100159iex> stream = IO .stream (:stdio , :line )
101160iex> 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