Swift 5.0 日本語化計画 : Swift 5.0
Swift 4.0 での Dictionary と Set の改善
2017年10月4日   Nate Cook
Swift の最新リリースでは、辞書 (dictionary) やセット (set) には、共通の作業をこれまで以上に簡単にするための新しいメソッドとイニシャライザが多数用意されました。グループ化、フィルタリング、値の変換などの操作を 1 つのステップで実行できるようになり、より表現力豊かで効率的なコードを書くことができます。
この記事では、マーケットのためのいくつかの食料品データを例として、これらの新しい変革を探っています。名前 (name) と部門 (department) で構成されたこのカスタムの GroceryItem 構造体は、データ型として機能します。
struct GroceryItem: Hashable { var name: String var department: Department enum Department { case bakery, produce, seafood } static func ==(lhs: GroceryItem, rhs: GroceryItem) -> Bool { return (lhs.name, lhs.department) == (rhs.name, rhs.department) } var hashValue: Int { // Combine the hash values for the name and department return name.hashValue << 2 | department.hashValue } } // Create some groceries for our store: let 🍎 = GroceryItem(name: "Apples", department: .produce) let 🍌 = GroceryItem(name: "Bananas", department: .produce) let 🥐 = GroceryItem(name: "Croissants", department: .bakery) let 🐟 = GroceryItem(name: "Salmon", department: .seafood) let 🍇 = GroceryItem(name: "Grapes", department: .produce) let 🍞 = GroceryItem(name: "Bread", department: .bakery) let 🍤 = GroceryItem(name: "Shrimp", department: .seafood) let groceries = [🍎, 🍌, 🥐, 🐟, 🍇, 🍞, 🍤]
以下の例では、groceries 配列を使用して、これらの新しいツールを使用した辞書をビルドし、変換しています。
キーで値をグループ化
新しいグループ化イニシャライザを使用すると、値のシーケンスから辞書を作成し、それらの値から計算されたキーでグループ化することがすぐにできます。この新しいイニシャライザを使用して、部門 (department) ごとにグループ分けされた食料品 (grocery) の辞書をビルドします。
以前のバージョンの Swift でこれを行うには、最初から辞書をビルドするために繰り返しを使用しました。この型の注釈、手動での反復、および各キーが辞書にすでに存在するかどうかを確認するためのチェックが必要です。
// Swift <= 3.1
var grouped: [GroceryItem.Department: [GroceryItem]] = [:]
for item in groceries {
if grouped[item.department] != nil {
grouped[item.department]!.append(item)
} else {
grouped[item.department] = [item]
}
}
この Swift の更新では、Dictionary(grouping:by) イニシャライザを使用して、1 行のコードで同じ辞書を作成することができます。配列内の各要素のキーを返すクロージャを渡して下さい。以下のコードでは、クロージャは各食料雑貨品目の部門を返します。
// Swift 4.0 let groceriesByDepartment = Dictionary(grouping: groceries, by: { item in item.department }) // groceriesByDepartment[.bakery] == [🥐, 🍞]
結果として得られる groceriesByDepartment 辞書には、食料品リストの各部門のエントリがあります。各キーの値は、元のリストと同じ順序で、その部門内の食料品の配列です。groceriesByDepartment のキーとして .bakery を使用すると、配列[🥐,🍞]が得られます。
辞書の値を変換
新しい mapValues(_:) メソッドを使用すると、同じキーを保持しながら辞書の値を変換できます。このコードは、 groceriesByDepartment 内の項目の配列をカウントに変換し、各部門の項目数の参照表を作成します。
let departmentCounts = groceriesByDepartment.mapValues { items in items.count }
// departmentCounts[.bakery] == 2
辞書にはすべて同じキーがあり、値が異なるだけなので、元の辞書と同じ内部レイアウトを使用でき、ハッシュ値を再計算する必要はありません。これにより、辞書を最初からビルドし直すよりも mapValues(_:) の呼び出しが速くなります。
キー値のペアから辞書をビルド
固有のキーを持つときと、繰り返すキーを持つときの 2 つの異なるイニシャライザを使用して、キー値のペアのシーケンスから辞書を作成できるようになりました。
キーのシーケンスと値のシーケンスで始める場合は、zip(_:_:) 関数を使用してそれらを 1 つのペアのシーケンスに結合できます。たとえば、このコードは、食料品項目の名前と項目自体のタプルのシーケンスを作成します。
let zippedNames = zip(groceries.map { $0.name }, groceries)
zippedNames の各要素は、(String、GroceryItem) タプルで、最初の項目は ("Apples",🍎) です。 すべての食料品項目に固有の名前があるため、以下のコードは食料品項目のキーとして名前を使用する辞書をうまく作成します。
var groceriesByName = Dictionary(uniqueKeysWithValues: zippedNames)
// groceriesByName["Apples"] == 🍎
// groceriesByName["Kumquats"] == nil
あなたのデータに固有のキーがあることが確かな場合にのみ、Dictionary(uniqueKeysWithValues:) イニシャライザを使用してください。シーケンス内に重複したキーがあれば、実行時エラーが発生します。
データに繰り返しキーがある (かもしれない) 場合は、新しい統合イニシャライザ Dictionary(_:uniquingKeysWith:) を使用して下さい。このイニシャライザは、キー値のペアのシーケンスと、キーが繰り返されるたびに呼び出されるクロージャを使用します。固有の クロージャは、引数と同じキーを共有する最初の値と 2 番目の値をとり、既存の値、新しい値を返したり、それらを結合することができます。
たとえば、以下のコードは、Dictionary(_:uniquingKeysWith:) を使用して (String,String) タプルの配列を辞書に変換します。"dog" は、2 つのキー値のペアのキーである事に注意して下さい。
let pairs = [("dog", "🐕"), ("cat", "🐱"), ("dog", "🐶"), ("bunny", "🐰")]
let petmoji = Dictionary(pairs,
uniquingKeysWith: { (old, new) in new })
// petmoji["cat"] == "🐱"
// petmoji["dog"] == "🐶"
キー "dog" を持つ第 2 のキー値のペアに達すると、古い値と新しい値 ( "🐕"と "🐶") で固有のクロージャが呼び出されます。クロージャーは常に 2 番目のパラメーターを返すので、結果は "dog" キーの値として "🐶" を返します。
特定のエントリの選択
Dictionarty には、以前のバージョンの Swift のように、キー値のペアの配列ではなく、辞書を返す filter(_:) メソッドが追加されました。キー値のペアをその引数とするクロージャを渡し、そのペアが結果内になければ true を返します。
func isOutOfStock(_ item: GroceryItem) -> Bool { // Looks up `item` in inventory } let outOfStock = groceriesByName.filter { (_, item) in isOutOfStock(item) } // outOfStock["Croissants"] == 🥐 // outOfStock["Apples"] == nil
このコードは、各項目に対して isOutOfStock(_:) 関数を呼び出し、在庫がない食料品項目のみを保持します。
デフォルト値の使用
辞書には、値を取得して更新するのを容易にする 2 番目のキーを基本としたサブスクリプトが追加されました。以下のコードは、項目の辞書とそのカウントとして実装された簡単なショッピングカートを定義しています。
// Begin with a single banana
var cart = [🍌: 1]
一部のキーでは対応する値が辞書にないため、キーを使用して値を検索すると結果は optional になります。
// One banana: cart[🍌] // Optional(1) // But no shrimp: cart[🍤] // nil
optional の値を実際に必要な数に変換するために nil coalescing (nil 合体) 演算子 (??) を使用する代わりに、キーと default パラメータで辞書をサブスクリプトできるようになりました。キーが見つかった場合は、その値が返され、デフォルトは無視されます。キーが見つからない場合、サブスクリプトは指定したデフォルト値を返します。
// Still one banana: cart[🍌, default: 0] // 1 // And zero shrimp: cart[🍤, default: 0] // 0
新しいサブスクリプトを使って値を変更することもでき、新しい項目をカートに追加するのに必要なコードが簡単になります。
for item in [🍌, 🍌, 🍞] { cart[item, default: 0] += 1 }
このループが各々のバナナ (🍌) を処理すると、現在の値が検索され、増分され、辞書に戻されて保管されます。パン (🍞) を追加すると、辞書はキーを見つけられず、その代わりに デフォルト値 (0) を返します。その値が増分された後、辞書は新しいキー値のペアを追加します。
ループの終わりでは、cart は[🍌:3、🍞:1] です。
2 つの辞書を 1 つに統合
増分の変更の容易さに加えて、辞書は、ある辞書を別の辞書に統合するメソッドを使用して、一括して変更する事を簡単にしました。
cart と別の辞書の内容を統合するには、変更する merge(_:uniquingKeysWith:) メソッドを使用できます。あなたが渡す固有のクロージャは、Dictionary(_:uniquingKeysWith:) のイニシャライザと同じ方法で動作します。同じキーの 2 つの値がある場合それはいつでも呼ばれ、値の一方、他方、または組み合わせを返します。
この例では、加算演算子を uniquingKeysWith パラメータとして渡すと、一致するキーの数を加算するため、更新されたカートには各項目の正しい合計が入ります。
let otherCart = [🍌: 2, 🍇: 3]
cart.merge(otherCart, uniquingKeysWith: +)
// cart == [🍌: 5, 🍇: 3, 🍞: 1]
適切に統合する代わりに、統合された内容で新しい辞書を作成するには、変更しない merging(_:uniquingKeysWith:) メソッドを使用して下さい。
そしてそれだけではない...
私たちが言っていない追加がいくつかあります。辞書には新しい機能を備えたカスタムの keys と values のコレクションが追加されました。keys コレクションは高速キー検索を維持しますが、可変 values コレクションでは値を適切に修正できます。
辞書のように、セットには、以前のバージョンの Swift のような配列の代わりに、同じ型のセットを返す新しい filter(_:) メソッドを追加しました。最後に、セットと辞書両方が現在の容量を公開できるようになり、 reserveCapacity(_:) メソッドを追加しました。これらの追加機能を使用すると、内部記憶のサイズを表示および制御できます。
カスタムの keys と values のコレクション以外に、これらの変更はすべて Swift 3.2 で利用できます。Swift 4.0 をまだ使用していない場合でも、今日これらの改良を利用し始めることができます!
これらの新しい機能についての詳細は、Dictionary と Set のドキュメントを参照するか、カスタムキーと値のコレクション および その他の辞書とセットの機能強化 の Swift 改革の提案の追加の理由についての詳細を参照してください。
<-ブログ:Swift 4.0 リリース! 致命的なエラーの表示を改善した Xcode 9.1->