Enumはとても便利なモジュールで、列挙可能なデータを扱うためのアルゴリズムを一式提供してくれます。並べ替え、絞り込み、グループ化、数え上げ、検索、最小値や最大値の取得など、さまざまな機能を備えています。
一般に、_列挙可能_とは、繰り返し処理できるデータ、つまりコレクションのことです。Elixirでは、Enumerableプロトコルを実装したデータ型が列挙可能です。その中でもよく使われるのは、配列とマップです。
Enumの関数の多くは、引数として関数を受け取ります。
Enum.all?([1, 2, 3, 4, 5], fn x -> x > 3 end)
# => false
Enumでもっともよく使われる関数は、mapとreduceです。
map/2Enum.map/2を使うと、列挙可能なデータの各要素を別の要素に置き換えられます。Enum.map/2の第2引数は、元の要素を受け取って、その置き換え後の要素を返す関数です。
reduce/3Enum.reduce/3を使うと、列挙可能なデータ全体を1つの値に_畳み込む_ことができます。そのために、_アキュムレーター_と呼ばれる特別な変数を使います。アキュムレーターは、繰り返しの間で畳み込みの途中状態を保持します。
Enum.reduce/3の第2引数は、アキュムレーターの初期値です。第3引数は、要素とアキュムレーターを受け取り、アキュムレーターの新しい値を返す関数です。
Enumの関数でマップを扱うと、そのマップは自動的に2つの{key, value}タプルからなる配列に変換されます。
マップに戻すには、Enum.into/2を使います。Enum.into/2は、列挙可能なデータをコレクタブルに変換する関数です。コレクタブルとは、Collectableプロトコルを実装したデータ構造のことです。Enum.reduce/3の反対のものと考えるとよいでしょう。
EnumにはEnum.into/3もあります。Enum.into/3はEnum.into/2の派生版で、列挙可能なデータをコレクタブルに変換するときに適用する変換関数を受け取ります。
マップに変換(マッピング)を適用するのに、Enum.into/3やEnum.map/2にEnum.into/2を組み合わせる代わりに、専用のMap.new/2関数を使うこともできます。Map.new/2は、列挙可能なデータと変換関数を受け取るという点ではEnum.into/3とまったく同じように動作しますが、コレクタブルを選べるのではなく、常に新しいマップを返します。
オンラインのファッションブティックを経営しています。毎年恒例の大きなセールが近づいているので、準備ができているか確認するために在庫を確認する必要があります。
在庫の1つの商品はマップで表され、在庫全体はそうしたマップの配列です。
%{
name: "White Shirt",
price: 40,
quantity_by_size: %{s: 3, m: 7, l: 8, xl: 4}
}
sort_by_price/1関数を実装しましょう。在庫を受け取り、商品の価格の昇順で並べ替えたものを返すようにします。
BoutiqueInventory.sort_by_price([
%{price: 65, name: "Maxi Brown Dress", quantity_by_size: %{}},
%{price: 50, name: "Red Short Skirt", quantity_by_size: %{}},
%{price: 50, name: "Black Short Skirt", quantity_by_size: %{}},
%{price: 20, name: "Bamboo Socks Cats", quantity_by_size: %{}}
])
# => [
# %{price: 20, name: "Bamboo Socks Cats", quantity_by_size: %{}},
# %{price: 50, name: "Red Short Skirt", quantity_by_size: %{}},
# %{price: 50, name: "Black Short Skirt", quantity_by_size: %{}},
# %{price: 65, name: "Maxi Brown Dress", price: 65, quantity_by_size: %{}}
# ]
在庫を価格で並べ替えたあと、在庫確認のときに間違えて、いくつかの商品の価格を入力し忘れていたことに気づきました。
with_missing_price/1関数を実装しましょう。在庫を受け取り、価格がない商品の配列を返すようにします。
BoutiqueInventory.with_missing_price([
%{price: 40, name: "Black T-shirt", quantity_by_size: %{}},
%{price: nil, name: "Denim Pants", quantity_by_size: %{}},
%{price: nil, name: "Denim Skirt", quantity_by_size: %{}},
%{price: 40, name: "Orange T-shirt", quantity_by_size: %{}}
])
# => [
# %{price: nil, name: "Denim Pants", quantity_by_size: %{}},
# %{price: nil, name: "Denim Skirt", quantity_by_size: %{}}
# ]
いくつかの商品名に、もう使いたくない言葉が含まれていることに気づきました。その言葉を含むすべての商品名を更新する必要があります。
update_names/3関数を実装しましょう。在庫、削除したい古い言葉、代わりに使いたい新しい言葉を受け取り、名前を更新した商品の配列を返すようにします。
BoutiqueInventory.update_names(
[
%{price: 40, name: "Black T-shirt", quantity_by_size: %{}},
%{price: 70, name: "Denim Pants", quantity_by_size: %{}},
%{price: 65, name: "Denim Skirt", quantity_by_size: %{}},
%{price: 40, name: "Orange T-shirt", quantity_by_size: %{}}
],
"T-shirt",
"Tee"
)
# => [
# %{price: 40, name: "Black Tee", quantity_by_size: %{}},
# %{price: 70, name: "Denim Pants", quantity_by_size: %{}},
# %{price: 65, name: "Denim Skirt", quantity_by_size: %{}},
# %{price: 40, name: "Orange Tee", quantity_by_size: %{}}
# ]
特に売れている商品があったので、すべてのサイズを追加で発注しました。
increase_quantity/2関数を実装しましょう。1つの商品と数値nを受け取り、各サイズの数量をnだけ増やした商品を返すようにします。
BoutiqueInventory.increase_quantity(
%{
name: "Polka Dot Skirt",
price: 68,
quantity_by_size: %{s: 3, m: 5, l: 3, xl: 4}
},
6
)
# => %{
# name: "Polka Dot Skirt",
# price: 68,
# quantity_by_size: %{l: 9, m: 11, s: 9, xl: 10}
# }
倉庫にどれだけのスペースが必要かを知るには、各商品を合計でいくつ持っているかを知る必要があります。
total_quantity/1関数を実装しましょう。1つの商品を受け取り、サイズを問わず合計で何枚あるかを返すようにします。
BoutiqueInventory.total_quantity(%{
name: "Red Shirt",
price: 62,
quantity_by_size: %{s: 3, m: 6, l: 5, xl: 2}
})
# => 16