Pairs and Dictsのコンセプトでは、Juliaの辞書型の基本を扱いました。
Dictは重要でよく使われるコレクション型なので、それを操作する関数も数多く用意されています。
項目を取り出すもっとも簡単な方法は、キーを使うことです(多くの言語でも同じです)。
ただし、存在しないキーを指定するとKeyErrorになります。
julia> d = Dict(i => ('a':'c')[i] for i in 1:3)
Dict{Int64, Char} with 3 entries:
2 => 'b'
3 => 'c'
1 => 'a'
julia> d[2]
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
# missing key
julia> d[4]
ERROR: KeyError: key 4 not found
取り出す前にキーの存在を確認することもできます(haskey(d, 4)のような関数を使います)が、存在しないキーに対して既定値を返すようにしておくと便利なことがあります。
そのために、get()関数と、辞書を書き換える版であるget!()があります。
この2つは、存在しないキーをDictに追加するかどうかが異なります。
julia> get(d, 4, '!')
'!': ASCII/Unicode U+0021 (category Po: Punctuation, other)
# get() leaves Dict unchanged
julia> d
Dict{Int64, Char} with 3 entries:
2 => 'b'
3 => 'c'
1 => 'a'
julia> get!(d, 4, '!')
'!': ASCII/Unicode U+0021 (category Po: Punctuation, other)
# get!() adds new key to Dict
julia> d
Dict{Int64, Char} with 4 entries:
4 => '!'
2 => 'b'
3 => 'c'
1 => 'a'
以前のコンセプトで見たように、delete!()はキーを指定して項目を削除します。
これは、pop!()関数を使うと、値を取り出す処理と組み合わせられます。
julia> d
Dict{Int64, Char} with 4 entries:
4 => '!'
2 => 'b'
3 => 'c'
1 => 'a'
# popped value is returned
julia> pop!(d, 3)
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
# Dict is shrunk
julia> d
Dict{Int64, Char} with 3 entries:
4 => '!'
2 => 'b'
1 => 'a'
Dictを繰り返し処理すると、既定ではkey => valueのペアが返されます。
それぞれの要素には、おなじみの.firstと.secondでアクセスできます。
# d is from the previous example
julia> [(kv.first, kv.second) for kv in d]
3-element Vector{Tuple{Int64, Char}}:
(4, '!')
(2, 'b')
(1, 'a')
キーだけ、あるいは値だけを使いたいこともよくありますが、それは簡単です。
julia> [k for k in keys(d)]
3-element Vector{Int64}:
4
2
1
julia> [v for v in values(d)]
3-element Vector{Char}:
'!': ASCII/Unicode U+0021 (category Po: Punctuation, other)
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
2つのDictを組み合わせると便利なことがありますが、複数のDictに同じキーが存在する場合にどう扱うかをはっきりさせておく必要があります。
merge()とmerge!()関数は、渡された任意の数のDictを左から右へと組み合わせます。
すでに存在するキーの値はそのまま上書きされるので、もっとも右にあるDictが「勝ち」ます。
julia> d3 = Dict("color" => "red", "shape" => "circle")
Dict{String, String} with 2 entries:
"shape" => "circle"
"color" => "red"
julia> d4 = Dict("color" => "blue", "coords" => (3, 2))
Dict{String, Any} with 2 entries:
"color" => "blue"
"coords" => (3, 2)
# color from d3 is lost
julia> merge(d3, d4)
Dict{String, Any} with 3 entries:
"shape" => "circle"
"color" => "blue"
"coords" => (3, 2)
mergewith()とmergewith!()関数では、第1引数に関数を渡して、キーが重複したときのマージ方法を定義できます。
足し算はわかりやすい例です。
julia> d5 = Dict("color" => "red", "area" => 3)
Dict{String, Any} with 2 entries:
"area" => 3
"color" => "red"
julia> d6 = Dict("shape" => "circle", "area" => 5)
Dict{String, Any} with 2 entries:
"shape" => "circle"
"area" => 5
# area values are added, not overwritten
julia> mergewith(+, d5, d6)
Dict{String, Any} with 3 entries:
"shape" => "circle"
"area" => 8
"color" => "red"
mergewith系の関数は、高階関数が活きる良い例であることに注目してください。
食料品の買い物を自動化する会社Mecha Munch™に、注文アプリの開発メンバーとして加わることになりました。 チームの任務は、ショッピングカートの基本的な操作をすべて管理するMVP(実用最小限の製品)を作ることです。ユーザーが食料品の注文を追加・削除・並べ替えできるようにします。 ありがたいことに、お金と会計の処理は別のチームが担当してくれています!
このMVPでは、ユーザーが自分のショッピングカートに商品を追加できるようにします。 追加するのは1つの商品でも、一度に複数の商品でもかまいません。 MVPなので、商品の数量は_繰り返し_で表します。 ユーザーがオレンジを2つ追加したい場合、入力のイテラブルに'オレンジ'が2回現れます。 その商品がすでにカートに入っている場合は、カートの数量を1増やします。 その商品がカートにとって_新しい_ものであれば、数量1で追加します。
引数にカートの辞書と、追加する商品のイテラブルを取る関数add_items!(<cart>, <items>)を作成します。
更新後のユーザーのショッピングカートの辞書を返します。
julia> additems!(Dict("Banana" => 3, "Apple" => 2, "Orange" => 1),
("Apple", "Apple", "Orange", "Apple", "Banana"))
Dict{String, Int64} with 3 entries:
"Apple" => 5
"Orange" => 2
"Banana" => 4
julia> additems!(Dict("Banana" => 3, "Apple" => 2, "Orange" => 1),
["Banana", "Orange", "Blueberries", "Banana"])
Dict{String, Int64} with 4 entries:
"Blueberries" => 1
"Apple" => 2
"Orange" => 2
"Banana" => 5
アプリには、さまざまな料理の完成したレシピが並ぶ「アイデア」セクションがあります。 ユーザーはこれらのレシピから好きなものを1つ選ぶと、その材料がすべて自動的にショッピングカートに追加されます。 プロジェクトマネージャーから、この「アイデア」レシピを編集する方法を作るよう頼まれました。コンテンツチームが材料や数量を頻繁に変更しているためです。
引数に「アイデア」の辞書と、レシピ更新の辞書を取る関数update_recipes!(<ideas>, <updates>)を作成します。
この関数は、更新後の「アイデア」の辞書を返します。
julia>update_recipes!(
Dict("Banana Bread" => Dict("Banana" => 1, "Apple" => 1, "Walnuts" => 1, "Flour" => 1, "Eggs" => 2, "Butter" => 1),
"Raspberry Pie" => Dict("Raspberry" => 1, "Orange" => 1, "Pie Crust" => 1, "Cream Custard" => 1)),
Dict("Banana Bread" => Dict("Banana" => 4, "Walnuts" => 2, "Flour" => 1, "Butter" => 1, "Milk" => 2, "Eggs" => 3))
)
Dict{String, Dict{String, Int64}} with 2 entries:
"Raspberry Pie" => Dict("Cream Custard"=>1, "Raspberry"=>1, "Orange"=>1, "Pie Crust"=>1)
"Banana Bread" => Dict("Eggs"=>3, "Flour"=>1, "Milk"=>2, "Butter"=>1, "Walnuts"=>2, "Banana"=>4)
julia> update_recipes!(
Dict("Banana Bread" => Dict("Banana" => 1, "Apple" => 1, "Walnuts" => 1, "Flour" => 1, "Eggs" => 2, "Butter" => 1),
"Raspberry Pie" => Dict("Raspberry" => 1, "Orange" => 1, "Pie Crust" => 1, "Cream Custard" => 1),
"Pasta Primavera" => Dict("Eggs" => 1, "Carrots" => 1, "Spinach" => 2, "Tomatoes" => 3, "Parmesan" => 2, "Milk" => 1, "Onion" => 1)),
Dict("Raspberry Pie" => Dict("Raspberry" => 3, "Orange" => 1, "Pie Crust" => 1, "Cream Custard" => 1, "Whipped Cream" => 2),
"Pasta Primavera" => Dict("Eggs" => 1, "Mixed Veggies" => 2, "Parmesan" => 2, "Milk" => 1, "Spinach" => 1, "Bread Crumbs" => 1),
"Blueberry Crumble" => Dict("Blueberries" => 2, "Whipped Creme" => 2, "Granola Topping" => 2, "Yogurt" => 3))
)
Dict{String, Dict{String, Int64}} with 4 entries:
"Raspberry Pie" => Dict("Raspberry"=>3, "Orange"=>1, "Pie Crust"=>1, "Cream Custard"=>1, "Whipped Cream"=>2)
"Pasta Primavera" => Dict("Eggs"=>1, "Mixed Veggies"=>2, "Parmesan"=>2, "Milk"=>1, "Spinach"=>1, "Bread Crumbs"=>1)
"Blueberry Crumble" => Dict("Blueberries"=>2, "Whipped Creme"=>2, "Granola Topping"=>2, "Yogurt"=>3)
"Banana Bread" => Dict("Banana"=>1, "Apple"=>1, "Walnuts"=>1, "Flour"=>1, "Eggs"=>2, "Butter"=>1)
アプリは、ユーザーのカートを店舗に送って発送準備を依頼する必要があります。 ただし、店舗のスタッフは、商品がどこにあるかを一意の通路コードで知る必要があります。 「発送用カート」は、スタッフが効率よく作業できるよう、商品の通路コード順に並べ替える必要があります。
引数にユーザーのショッピングカートと、店舗の通路番号を持つ辞書を取る関数send_to_store(<cart>, <aislecodes>)を作成します。
この関数は、お客様が注文する各商品についてaislecode => quantityのペアを持つ、統合された「発送用カート」を返します。
商品は通路コードの順に並びます。
julia> cart = Dict("Orange" => 1, "Milk" => 2, "Banana" => 3, "Apple" => 2, "Chocolate" => 2);
julia> aislecodes = Dict("Orange" => 2.4, "Milk" => 3.3, "Banana" => 2.6, "Apple" => 2.2, "Chocolate" => 5.7);
julia> send_to_store(cart, aislecodes)
5-element Vector{Pair{Float64, Int64}}:
2.2 => 2
2.4 => 1
2.6 => 3
3.3 => 2
5.7 => 2
アプリがお客様の注文をいつまでも受け付けるわけにはいきません。 いずれ店舗のさまざまな商品は在庫切れになります。 そのため、MVPのアプリは、ユーザーが注文を店舗に送るたびに店舗の在庫を更新する必要があります。 そうしないと、お客様が実際には在庫のない商品を注文してしまいます。
引数にcartとinventoryを取る関数update_store_inventory!(<inventory>, <cart>)を作成します。
この関数は、店舗の在庫数から「カート」で「注文された」数だけ減らし、在庫切れ(つまり0になった)商品の辞書を返します。
julia> inventory = Dict("Banana" => 15, "Apple" => 12, "Orange" => 1, "Milk" => 4);
julia> cart = Dict("Orange" => 1, "Milk" => 2, "Banana" => 3, "Apple" => 2);
julia> update_store_inventory!(inventory, cart)
Dict{String, Int64} with 1 entry:
"Orange" => 0
julia> inventory
Dict{String, Int64} with 4 entries:
"Milk" => 2
"Apple" => 10
"Orange" => 0
"Banana" => 12
在庫の更新だけでなく、アプリは在庫の補充も手伝えます。 MVPは、お客様の注文後に在庫切れになった商品のミニ発注を準備できるようにします。 こうすることで、大きな発注を出すのが簡単になります。 さらに、在庫リストにない期間限定のプロモーション商品があることもあります。 こうした商品がプロモーション期間の終了前に売り切れたなら、人気が証明されたわけなので、在庫リストに追加できます。
引数にoutofstockとstockを取るreorder!(<outofstock>, <stock>)を作成します。
stockの辞書には、標準的な在庫商品とその再発注数量が入っています。
outofstockの辞書はupdate_store_inventory!の結果、つまり数量が0になった商品の辞書です。
outofstockの数量は、stockの数量を反映するように更新して返します。
また、outofstockにあってstockにない商品は、デフォルトの数量100でstockに追加します。
julia> inventory = Dict("Banana" => 15, "Apple" => 12, "Orange" => 1, "Milk" => 4, "Chocolate" => 2);
julia> cart = Dict("Orange" => 1, "Milk" => 2, "Banana" => 3, "Apple" => 2, "Chocolate" => 2);
julia> stock = Dict("Banana" => 150, "Apple" => 120, "Orange" => 120, "Milk" => 45);
julia> outofstock = update_store_inventory!(inventory, cart)
Dict{String, Int64} with 1 entry:
"Orange" => 0
"Chocolate" => 0
julia> reorder!(outofstock, stock)
Dict{String, Int64} with 1 entry:
"Orange" => 120
"Chocolate" => 100
julia> stock
Dict{String, Int64} with 5 entries:
"Milk" => 45
"Apple" => 120
"Chocolate" => 100
"Orange" => 120
"Banana" => 150