🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
18-列表速构(Comprehension) ======== [生成器和过滤器](#182-%E7%94%9F%E6%88%90%E5%99%A8%E5%92%8C%E8%BF%87%E6%BB%A4%E5%99%A8)<br/> [比特串生成器](#182-bitstring%E7%94%9F%E6%88%90%E5%99%A8)<br/> [Into](#183-into)<br/> >Comprehensions翻译成“速构”不知道贴不贴切,这参照了《Erlang/OTP in Action》译者的用辞。 “速构”是函数式语言中常见的概念,它大体上指的是用一套规则(比如从另一个列表,过滤掉一些元素)来生成元素填充新列表。 这个概念我们在中学的数学课上就可能已经接触过,在大学高数中更为常见:如```{ x | x ∈ N }```,指的是这个集合里所有元素是自然数。 该集合是由自然数集合的元素映射生成过来的。相关知识可见[WIKI](http://en.wikipedia.org/wiki/List_comprehension)。 Elixir中,使用枚举类型(如列表)来做循环操作是很常见的。对对象列表进行枚举时,通常要有选择性地过滤掉其中一些元素,还有可能做一些变换。列表速构(comprehensions)就是为此目的诞生的语法糖:把这些常见任务分组,放到特殊的```for```中执行。 例如,我们可以这样计算列表中每个元素的平方: ``` iex> for n <- [1, 2, 3, 4], do: n * n [1, 4, 9, 16] ``` 注意看,```<-```符号就是模拟自```∈ ```的形象。 这个例子用熟悉(当然,如果你高数课没怎么听那就另当别论)的数学符号表示就是: ``` S = { X^2 | X ∈ [1,4], X ∈ N} ``` 这个例子用常见的编程语言去理解,基本上类似于foreach...in...什么的。但是更强大。 一个列表速构由三部分组成:生成器,过滤器和收集器。 ##18.2-生成器和过滤器 在上面的例子中,```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] ``` 过滤器能过滤掉某些产生的值。例如我们可以只对奇数进行平方运算: ``` iex> require Integer iex> for n <- 1..4, Integer.odd?(n), do: n * n [1, 9] ``` 过滤器会保留所有判断结果是非nil或非false的值。 总的来说,速构比直接使用枚举或流模块的函数提供了更精确的表述。 不但如此,速构还接受多个生成器和过滤器。下面就是一个例子,代码接受目录列表,删除这些目录下的所有文件: ``` for dir <- dirs, file <- File.ls!(dir), path = Path.join(dir, file), File.regular?(path) do File.rm!(path) end ``` 需要记住的是,在速构中,变量赋值这种事应在生成器中进行。因为在过滤器或代码块中的赋值操作不会反映到速构外面去。 ## 18.2-比特串生成器 速构也支持比特串作为生成器,而且这种生成器在组织处理比特串的流时非常有用。 下面的例子中,程序从二进制数据(表示为<<像素1的R值,像素1的G值,像素1的B值,像素2的R值,像素2的G...>>)中接收一个像素的列表,把它们转换为元组: ``` 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}] ``` 比特串生成器可以和“普通的”枚举类型生成器混合使用,过滤器也是。 ## 18.3-Into 在上面的例子中,速构返回一个列表作为结果。 但是,通过使用```:into```选项,速构的结果可以插入到一个不同的数据结构中。 例如,你可以使用比特串生成器加上```:into```来轻松地构成无空格字符串: ``` iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>> "helloworld" ``` 集合、图以及其他字典类型都可以传递给```:into```选项。总的来说,```:into```接受任何实现了_Collectable_协议的数据结构。 例如,IO模块提供了流。流既是Enumerable也是Collectable。 你可以使用速构实现一个回声终端,让其返回任何输入的东西的大写形式: ``` iex> stream = IO.stream(:stdio, :line) iex> for line <- stream, into: stream do ...> String.upcase(line) <> "\n" ...> end ``` 现在在终端中输入任意字符串,你会看到同样的内容以大写形式被打印出来。 不幸的是,这个例子会让你的shell陷入到该速构代码中,只能用Ctrl+C两次来退出。