Rubyで集合(Setクラス)を扱う


Set(集合)クラスとは、その名の通り「要素の集合」を表すクラスです。

その性質としては大まかに2つあります。

  1. 要素間の順序関係がない
  2. 重複した要素は存在しない

Hashはvalueの中に重複した要素が存在しても良いですが、Setでは重複した要素の存在は許可されません。(HashでもKeyに重複は許容されないですが)

PythonやSwift等、最近の言語では文法レベルで集合を扱えるようにしている言語も多いですが、Rubyでは標準ライブラリとして集合を扱えるようになっています。

本記事では、集合(Set)について基本的な性質とその使い方について記述していきます。

irbで試してみる

まずはirbを起動しましょう。

$ irb

SetクラスはRuby標準ライブラリに含まれているので、irbで

require 'set'

を実行すればSetクラスを扱えるようになります。

Setインスタンスを作成

Setを作成するには、Enumerableモジュールの#to_setを呼ぶか、Set.newを使います。

Set.newで渡せる引数はEnumerableです。

set1 = Set.new #空のSetを作成
set2 = Set.new([1,3,5,7]) # 1,3,5,7を要素とするSetを作成
#enumerableモジュールをmixinしたクラスのインスタンスなら、to_setメソッドをコールすることでもSetを作成できる
set3 = ['hoge','fuga','foo','bar'].to_set 

Setの中身は、他のEnumerableなものと同じくeachなどで取り出すことができます

要素を追加する

Setには、要素を追加するaddメソッドとadd?メソッドがあります。

この2つのメソッドは、重複した要素があるときの返り値の挙動が異なります。

メソッド 追加しようとした要素が存在したときの挙動
add 集合を返す
add? nilを返す
Set.new([1,2]).add(3) #=> #<Set: {1, 2, 3}>
Set.new([1,2]).add?(3) #=> #<Set: {1, 2, 3}>
Set.new([1,2,3]).add(3) #=> #<Set: {1, 2, 3}>
Set.new([1,2,3]).add?(3) #=> nil

要素をまとめて追加したい場合には、Set#mergeを使います。

Set.new([1,3,5]).merge([2,4,6]) # => #<Set: {1, 3, 5, 2, 4, 6}>

末尾から追加されているように見えますが、もともとSetの順番は保証されていないことに注意しましょう。

要素を削除する

「要素の追加」と同じ構造で、deleteメソッドとdelete?メソッドがある

メソッド 削除しようとした要素が存在したときの挙動
add 集合を返す
add? nilを返す
Set.new([1,2]).delete(3) #=> #<Set: {1, 2}>
Set.new([1,2]).delete?(3) #=> nil
Set.new([1,2,3]).delete(3) #=> #<Set: {1, 2}>
Set.new([1,2,3]).delete?(3) #=> #<Set: {1, 2}>

まとめて要素を削除するには、Set#substractを使います。

Set.new([1,2,3,4,5,6]).subtract([2,4,6]) => #<Set: {1, 3, 5}>

Enumerableモジュールをincludeしているので、配列やHashみたいに以下のようなこともできます

# 1~10の数値から偶数のみを選択する
Set.new(1..10).reject!{|val| val%2!=0}

集合の中身を調べる

set = Set.new(%w(hoge fuga foo))
set.size
set.include?('bar')
set.each do |val|
  print val
end

集合の演算

2つの集合の和集合、積集合、対称差、差集合を計算することが出来ます。

set1 = Set.new(%w(java ruby))
set2 = Set.new(%w(java swift python))
# 和集合
set1 | set2 # => #<Set: {"java", "ruby", "swift", "python"}>
# 積集合
set1 & set2 # => #<Set: {"java"}>
# 対称差
set1 ^ set2 # => #<Set: {"swift", "python", "ruby"}>
# 差集合
set1 - set2 # => #<Set: {"ruby"}>

集合を分割する場合は、「どのように分割するか」の処理を記述したブロックを渡し、Set#devideを呼びます。

下記の例では、語数によって集合の要素を分割しています。

set = Set.new(%w(java go ruby python c swift R))
set.divide{|v| v.length} 
# => #<Set: {#<Set: {"java", "ruby"}>, #<Set: {"go"}>, #<Set: {"python"}>, #<Set: {"c", "R"}>, #<Set: {"swift"}>}>

本記事では、集合(Set)について基本的な性質とその使い方について記述しました。

より詳しく知りたい方は「パーフェクトRuby」の本で勉強すると良いでしょう。あるいは、公式ドキュメントでも更に詳しい情報を得ることが出来ます。