用过 Python 的人几乎都用过列表推导式,从早期版本开始,它一直是 Python 语法的核心特性。
如此方便的东西会逐渐进入其他语言,Julia 也不例外。
对 Julia 程序员来说,推导式是一个选项,而不是必需品,因为通常还有别的做法(广播、高阶函数等)。
不过,推导式往往能提供一种简单、易读且性能不错的构造数组的方式。 用不用它,最终取决于个人口味,以及你更喜欢 Python 还是函数式语言。
语法基本是直接照搬 Python,但为高维数组做了扩展。
非常有 Python 风格,包括可选的 if 子句。
julia> [x^2 for x in 1:3]
3-element Vector{Int64}:
1
4
9
julia> [x^2 for x in 1:3 if isodd(x^2)]
2-element Vector{Int64}:
1
9
输出形状取决于推导式的细节:在简单情况下通常与输入集合相同,但 if 子句会把它压平成一个 Vector。
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> [x^2 for x in m]
2×3 Matrix{Int64}:
1 4 9
16 25 36
julia> [x^2 for x in m if isodd(x^2)]
3-element Vector{Int64}:
1
25
9
和 Python 一样,我们可以写多个 for 子句,用不同的变量,输入集合可以相同,也可以不同。
julia> [x * y for x in 1:3 for y in 4:6]
9-element Vector{Int64}:
4
5
6
8
10
12
12
15
18
julia> [x * y for x in 1:3 for y in 4:6 if isodd(x * y)]
2-element Vector{Int64}:
5
15
这等价于嵌套循环。 即使输入是矩阵,输出也是一维的。
julia> m
2×3 Matrix{Int64}:
1 2 3
4 5 6
julia> [x*y for x in m for y in m]
36-element Vector{Int64}:
1
4
2
5
(truncated output...)
上一节讲的是只用空格分隔的多个 for 子句。
这一节里只有一个 for,变量之间用逗号分隔。
julia> [(x, y) for x in 1:3, y in 4:6]
3×3 Matrix{Tuple{Int64, Int64}}:
(1, 4) (1, 5) (1, 6)
(2, 4) (2, 5) (2, 6)
(3, 4) (3, 5) (3, 6)
推导式中的每个变量都会在输出中创建一个新维度,变量的每一种可能组合对应一个条目。
在上面的例子里,x 沿行方向递增,y 沿列方向递增。
还可以有更高的维度,不过照例要提醒一句:输出的可读性会成问题。
前几节讲的重点是数组输出,推导式放在方括号 [ ... ] 里。
当方括号换成圆括号 ( ... ) 时,情况就不一样了:我们得到的是一种generator expression,一个惰性求值的迭代器,可以按需产出下一个值。
julia> g = ((x, y) for x in 1:3, y in 4:6)
Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}, var"#9#10"}(var"#9#10"(), Base.Iterators.ProductIterator{Tuple{UnitRange{Int64}, UnitRange{Int64}}}((1:3, 4:6)))
# Indexing fails with a generator, the entries don't exist yet
julia> g[1, 2]
ERROR: MethodError: no method matching getindex(::Base.Generator{Base.Iterators.ProductIterator{Tuple{UnitRange{…}, UnitRange{…}}}, var"#9#10"}, ::Int64, ::Int64)
# conversion to array
julia> collect(g)
3×3 Matrix{Tuple{Int64, Int64}}:
(1, 4) (1, 5) (1, 6)
(2, 4) (2, 5) (2, 6)
(3, 4) (3, 5) (3, 6)
# generators are mainly designed for iteration
julia> [i * j for (i, j) in g]
3×3 Matrix{Int64}:
4 5 6
8 10 12
12 15 18
当推导式的结果马上要用于后续处理时,生成器可以节省内存,不必存储一个很大的中间数组。
作为函数参数使用的生成器不需要额外的括号。
# inefficient
julia> v = [x^2 for x in 1:1e6];
julia> sum(v)
3.333338333335e17
# better - use a generator
julia> sum(x^2 for x in 1:1e6)
3.3333383333312755e17
生成器的语法和推导式完全相同,只是外面的括号不同。
生成器在字典构造中也很方便。
julia> Dict(x => x^2 for x in 1:5)
Dict{Int64, Int64} with 5 entries:
5 => 25
4 => 16
2 => 4
3 => 9
1 => 1
你在一家在线时尚精品店工作。 你想出了一个网站功能,可以向用户推荐穿搭。 你关注的是各种可能的穿搭、它们的价格,以及排除掉撞色的选项。
解决下面这些问题可能有多种方法,但都可以用推导式来解决。
实现clothingitem(),它接收两个Tuple:一个包含categories,另一个包含各自对应的qualities。
该函数应返回一个Dict,以categories为键,以qualities为值。
julia> categories = ("item_name", "price", "color", "base_color");
julia> qualities = ("Descriptive Name", 99.00, "Ochre Red", "red");
julia> clothingitem(categories, qualities)
Dict{String, Any} with 4 entries:
"price" => 99.0
"item_name" => "Descriptive Name"
"base_color" => "red"
"color" => "Ochre Red"
你的函数应能处理任意数量的categories。
实现get_combinations(),它接收一个上衣的Tuple和一个下装的Tuple。
该函数应返回这两个Tuple的cartesian product,形式为二维数组(即Matrix)。
每个元素都是一个(top, bottom)组合,类型为Tuple。
上衣沿行方向变化,下装沿列方向变化。
julia> tops = (
Dict("item_name" => "Dress shirt"),
Dict("item_name" => "Casual shirt")
);
julia> bottoms = (
Dict("item_name" => "Jeans"),
Dict("item_name" => "Dress trousers")
);
julia> get_combinations(tops, bottoms)
2×2 Matrix{Tuple{Dict{String, String}, Dict{String, String}}}:
(Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Jeans")) (Dict("item_name"=>"Dress shirt"), Dict("item_name"=>"Dress trousers"))
(Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Jeans")) (Dict("item_name"=>"Casual shirt"), Dict("item_name"=>"Dress trousers"))
每件衣服都带有一个price字段,因此能够比较整套穿搭的总价会很有帮助。
实现get_prices(),它接收一个服装组合数组。
该函数应返回一个形状相同的数组,各套穿搭的价格位于各自对应的位置。
julia> tops = (
Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
);
julia> bottoms = (
Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
);
julia> combomatrix = get_combinations(tops, bottoms);
julia> get_prices(combomatrix)
2×2 Matrix{Int}::
65 110
50 95
julia> filteredmatrix = filter_clashing(combomatrix)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
(Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))
julia> get_prices(filteredmatrix)
1-element Vector{Int}:
50
每件衣服都有一个base_color字段。
用这个字段移除上衣与下装底色相同的所有组合。
实现filter_clashing(),它接收一个服装组合数组。
返回一个一维数组(即Vector),包含所有符合条件的组合。
julia> tops = (
Dict("item_name" => "Dress shirt", "base_color" => "blue", "price" => 35),
Dict("item_name" => "Casual shirt", "base_color" => "black", "price" => 20)
);
julia> bottoms = (
Dict("item_name" => "Jeans", "base_color" => "blue", "price" => 30),
Dict("item_name" => "Dress trousers", "base_color" => "black", "price" => 75)
);
julia> combos = get_combinations(tops, bottoms);
julia> filter_clashing(combos)
1-element Vector{Tuple{Dict{String, Any}, Dict{String, Any}}}:
(Dict("price" => 20, "item_name" => "Casual shirt", "base_color" => "black"), Dict("price" => 30, "item_name" => "Jeans", "base_color" => "blue"))