學習軌道
/
Julia
Julia
/
練習
/
精品店推薦
精品店推薦

精品店推薦

學習練習

簡介

用過 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

多變數、Vector 輸出

和 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

說明

你在一間線上時尚精品店工作。你想到一個網站功能,可以向使用者推薦穿搭。你關注的是各種可能的穿搭、它們的價格,以及如何排除互相衝突的搭配。

Note

雖然解決下列問題可能有不同的方式,但全都可以用推導式來解決。

1. 建立衣物項目

實作clothingitem(),讓它接受 2 個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。

2. 推薦組合

實作get_combinations(),讓它接受一個裝著上衣的Tuple和一個裝著下身衣物的Tuple。這個函式應該以二維陣列(也就是Matrix)的形式回傳這些Tuple的cartesian product。每個元素都是一個(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"))

3. 加總穿搭價格

每件衣物都有對應的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

4. 篩選掉衝突的穿搭

每件衣物都有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"))
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Julia Exercism

準備好開始 精品店推薦 了嗎?

註冊 Exercism,透過 35 個概念128 個練習 和真人引導來學習並精通 Julia,全部免費。