トラック
/
Julia
Julia
/
演習
/
ブティックのおすすめ
ブティックのおすすめ

ブティックのおすすめ

学習演習

はじめに

Pythonを使ったことがある人は、ほぼ間違いなくリスト内包表記を使ったことがあるでしょう。リスト内包表記は、初期のバージョンからPythonの構文の中心的な存在です。

これほど便利な仕組みは、やがてJuliaをはじめとする他の言語にも取り入れられていきます。

Juliaのプログラマーにとって、内包表記は必須ではなく選択肢のひとつです。たいていは代わりの書き方(ブロードキャスト、高階関数など)があります。

しかし、配列を組み立てるのに、内包表記がシンプルで読みやすく、しかも高速な方法になることもよくあります。 使うかどうかは、結局のところ好みの問題であり、Pythonと関数型言語のどちらが好みかにもよります。

構文はほぼPythonからの直輸入ですが、多次元配列のための拡張が加えられています。

変数が1つの場合

省略可能なif節も含め、とてもPythonらしい書き方です。

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節を付けると1次元の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

これは入れ子のループと同じです。 入力が行列でも、出力は1次元になります。

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は1つだけで、変数はカンマで区切ります。

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を引数に取ります。1つはcategories、もう1つはそれぞれに対応するqualitiesです。 この関数は、categoriesをキー、qualitiesを値とするDictを返します。

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を引数に取ります。 この関数は、2つのタプルのcartesian productを2次元配列(すなわち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"))

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()を実装しましょう。コーディネートの組み合わせの配列を引数に取ります。 該当する組み合わせを1次元配列(すなわち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を学んでマスターできます。すべて無料です。