Swiftの集合(Sets)

集合(Sets)

Setsというコレクション型です。こいつは重複を許さないので、そういう値をグループ化する時には使えそうです(例えば、国の名前とか)。Setsは「集合」という和訳が当てられていますが、集合の意味を考えると、その機能が理解しやすいかもしれません。ただし、アクセスするのにSetsのメソッドしか使えないので、同じコレクション型であるArraysとDictionariesに比べると、ちょっと敷居が高いかもしれません。

集合(Sets)とは?

集合(Sets)というコレクション型は配列(Arrays)と何が違うのか?というのは素朴な疑問です。一言でいえば、

Setsは要素の重複を許さない

ということです。

例えば、

// Array vs Set
var someArray: Array<Int> = [1, 2, 2]
var someSet: Set<Int> = [1, 2, 2]
for item in someArray {
    print("Array: \(item)")
}
for item in someSet {
    print("Set: \(item)")
}
// Array: 1
// Array: 2
// Array: 2
// Set: 2
// Set: 1
// と表示

のように、Arrayでは入れた値がそのまま要素になりますが、Setでは2という重複した値は一つしか格納されてません。また、Setでは要素の順番は重要ではないので、Arrayのように入れた順番(この場合1,2,2)で保存されません。

Setの初期化

SetもArrayと同様に配列リテラル(Arrayリテラル)で初期化可能です。また、空のSetで初期化したり、代入することもできます。

Arrayリテラルで初期化

Arrayリテラルで初期化する際には、Array型を初期化する場合と同様の構文が使えます。

// Setの初期化
var kyushu: Set<String> = ["福岡","佐賀","長崎","熊本","大分","宮崎","鹿児島"]
var collectionTypes: Set = ["arrays", "sets"]

ただし、Setの場合、Arrayリテラルを使用するので配列と同じ省エネ宣言は使えません。つまり、上記の例でSetという部分を省略することはできません。省略するとArrayになります。

空のSetで初期化、代入

空のSet(空の配列リテラル)で初期化することもできます。

// 空のSetで初期化
var setDoubleEx1 = Set<Double>()   // 公式マニュアル記載
var setDoubleEx2: Set<Double> = [] // このような初期化も可能

また、空の配列リテラルを代入することで、Setの中身を空にすることができます。

// 空の配列リテラルを代入
collectionTypes = []
print("要素数:\(collectionTypes.count)")
// "要素数:0"と表示

Set要素へのアクセス、変更

Setの要素へアクセスする場合、Arrayで使えたsubscript syntaxは使えません。

// subscript syntaxは使えない
print("\(kyushu[2])")
// コンパイルエラー

したがって、Set型に用意されているプロパティやメソッドを使用してアクセスすることになります。公式マニュアルに紹介してある方法に関して、ここでも簡単に紹介します。

// 要素数を数える
print("九州は\(kyushu.count)県あります。")

// isEmpty(count==0)で空かどうかチェック
// insert(...)で新しい値を代入 (Setではappendは使えない)
// for-in loopで中身を確認
if collectionTypes.isEmpty {
  print("辞書を追加")
  collectionTypes.insert("dictionaries")
} else {
  for collection in collectionTypes {
    print("コレクション型:\(collection)")
  }
}

collectionTypes.insert("vector")

// 要素を削除
if let name = collectionTypes.remove("vector") {
  // nameは"vector"
  print("\(name)をコレクション型から削除")
} else {
  // nameはnil
  print("コレクション型にvectorはありません")
}

// 指定要素を含むかどうか(完全一致)
if collectionTypes.contains("dictionary") {
  print("dictionaryを含む")
} else {
  print("dictionaryを含まない")
}
2点補足です。

  1. removeは指定した要素が存在すれば、それを返しますが、ない場合はnilを返します。
  2. containsはブール値を返すメソッド(method)で、指定した要素がSet内の要素と「完全一致」する場合trueを返します。

2に関してですが、大文字と小文字の違いも判定されます。例えば、上記の例で"dictionaries"要素に対して、contains("Dictionaries")と指定してもダメです。

Setの操作

集合であるSetコレクション型に対しては、Setのメソッドを通して、様々な独自の演算が適用可能です。公式マニュアルに載っている絵が非常に分かりやすいので、同じものを載せます。

[図解]Setsの演算

使用例は以下のようになります。

// Setの演算
let fibonacciNumber: Set = [0, 1, 2, 3, 5, 8] // フィボナッチ数
let triangularNumber: Set = [0, 1, 3, 6] // 三角数

// Swift 3以降
fibonacciNumber.intersection(triangularNumber).sorted() // [0,1,3]
fibonacciNumber.symmetricDifference(triangularNumber).sorted() // [2,5,6,8]
fibonacciNumber.union(triangularNumber).sorted() // [0,1,2,3,5,6,8]
fibonacciNumber.subtracting(triangularNumber).sorted() // [2,5,8]

// Swift 2.xでの仕様
//fibonacciNumber.intersect(triangularNumber).sort() // [0,1,3]
//fibonacciNumber.exclusiveOr(triangularNumber).sort() // [2,5,6,8]
//fibonacciNumber.union(triangularNumber).sort() // [0,1,2,3,5,6,8]
//fibonacciNumber.subtract(triangularNumber).sort() // [2,5,8]

sorted()は、値が数字の場合、数字を昇順(小さいものから大きいもの)に並び替えるメソッドです。図の塗りつぶされた部分と上記例の結果を見比べると、それぞれのメソッドの使い方が分かるのではないかと思います。

Swift 2.xとSwift 3以降ではメソッド名が変わっているモノがあるので少し注意が必要です。

また、上記の4つのメソッドは、演算の結果を新しいSetとして返します。

// 結果を"intersectFibonacciTriangular"に格納
let intersectFibonacciTriangular = fibonacciNumber.intersection(triangularNumber)
intersectFibonacciTriangular.sort() // [0,1,3]

これは1つ前の例でやっていることと全く一緒ですが、一度定数に戻り値を渡してからsorted()を実行しています。

また、比較演算子==, !=による比較も可能です。

// Sets同士の比較
fibonacciNumber == triangularNumber // false
fibonacciNumber != triangularNumber // true

さらに、包含関係を調べるメソッドisSubSetOf(_:), isSuperSetOf(_:)や、共通要素があるかどうか調べるメソッドisDisjointWidth(_:)などがあります。公式マニュアルの絵文字を使った例は分かりやすいですね。

まとめ

  • Setsは各要素がユニークでなければならない(重複を許さないコレクション型)
  • 宣言の際には必ずSetを明示的に書く
  • 配列リテラルで初期化できる
  • 要素へのアクセス・変更はSetsのメソッド経由で(subscipt syntaxは使えない)