2
3

【Ruby】Setクラスについてざっくり調べてみた

Last updated at Posted at 2024-07-20

なぜこの記事を書いたのか

※追記※@scivola様に内容のご指摘をいただきましたので修正します。ありがとうございます🙇‍♂

業務中に簡単なコードを書こうとして「配列Aと配列Bで共通の要素を抽出できないか」ということを調べたときに、このSetクラスの存在を初めて知りましたが、結構便利そうなので興味本位で調べてみることにしました。

※自分がまじで低レベルなので鬼基本的な内容です(間違っていたらご指摘ください🙇‍♂)

Setクラス。。?

集合を表すクラスです。要素の間に順序関係はありません。

ちょっとこれだけだとピンとはこない。。。

  • ただ、集合順序関係がちょっとしたキーワードになりそう👀
  • 以下でRubyにおける集合に関して、そしてsetクラスに関してをざっくり書いていきます

Rubyにおける集合

本質的な難しい話は置いておいて(できんし)ざっくり集合とは、、、

  • 重複する要素を持たない一意な要素のコレクション(複数の要素を一つにまとめたデータ構造) を指す
    • Rubyでは配列で和集合(|)・差集合(-)・積集合(&)といった集合を扱うことができる
      • ちなみに非破壊的(元の配列を壊さない)
    • 同様に、RubyではSetクラスでもこの集合を扱うことができる
  1. 和集合(|)
  • 2つの配列の要素を1つの配列に集め、かつ重複しないようにしている

配列の場合

irb(main):010> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):011> b = [3, 4, 5]
=> [3, 4, 5]

# 1つの配列に集まり、かつ3が重複していない
irb(main):012> a | b
=> [1, 2, 3, 4, 5]

Setクラスの場合

irb(main):015> a = Set[1, 2, 3]
=> #<Set: {1, 2, 3}>
irb(main):016> b = Set[3, 4, 5]
=> #<Set: {3, 4, 5}>

irb(main):017> a | b
=> #<Set: {1, 2, 3, 4, 5}>

2 差集合(-)

  • 左の配列に含まれる要素と右の配列に含まれる要素が合致する場合、それを取り除く

配列の場合

irb(main):010> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):011> b = [3, 4, 5]
=> [3, 4, 5]

# 3が取り除かれる
irb(main):013> a - b
=> [1, 2]

Setクラスの場合

irb(main):015> a = Set[1, 2, 3]
=> #<Set: {1, 2, 3}>
irb(main):016> b = Set[3, 4, 5]
=> #<Set: {3, 4, 5}>

irb(main):018> a - b
=> #<Set: {1, 2}>

3 積集合(&)

  • 2つの配列に含まれている共通の要素を返す

配列の場合

irb(main):010> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):011> b = [3, 4, 5]
=> [3, 4, 5]

# 共通している3のみ返る
irb(main):014> a & b
=> [3]

Setクラスの場合

irb(main):010> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):011> b = [3, 4, 5]
=> [3, 4, 5]

irb(main):019> a & b
=> #<Set: {3}>

鬼ざっくり、Setクラスでは集合が扱えるのかーーということがわかった

Setクラスをもう少しだけ詳しく

1️⃣ Setクラスに配列を与えることで、新しい集合(setオブジェクト)を作成できる

  • 主に以下2つのやり方で作成できる
    • Set[1, 2] #=> #<Set: {1, 2}>
    • Set.new([1, 2]) #=> #<Set: {1, 2}>
      • Set[]というように少し特殊な書き方だが、これはSet.new([])のシンタックスシュガーのため、結果はどちらも同じ
      • すいませんシンタックスシュガーと書きましたが間違いでした、、、、Set[]Set.newはSetオブジェクトを作るための全く別の手段
      • 以下でいただいたご指摘をもとに内容を修正・加筆
# どちらも結果は同じsetオブジェクト(集合)を作成
irb(main):024> Set[1, 2]
=> #<Set: {1, 2}>

irb(main):026> Set.new([1, 2])
=> #<Set: {1, 2}>

Set[]

  • Set.[] (Ruby 3.3 リファレンスマニュアル)
    • 与えられたオブジェクトを要素とする新しい集合を作る
    • []には集合の要素としたいものを引数として指定する
    • []に引数を渡さない場合、空のsetオブジェクトが作成される
irb(main):003> Set[]
=> #<Set: {}>
  • Set[]はSetクラスをレシーバーとして[]というメソッドを呼び出すことの特殊記法

Set[1, 2]

というのは

  • レシーバー:Set
  • メソッド:[]
  • 引数:1 および 2
    • という意味のメソッド呼び出しになる
  • 全く同じ意味のコードとして以下のようになる
Set.[](1, 2)
irb(main):001> Set[1, 2]
=> #<Set: {1, 2}>
irb(main):002> Set.[](1, 2)
=> #<Set: {1, 2}>

Set.new

irb(main):005> Set.new
=> #<Set: {}>
  • Set.newは引数を与える場合 eachメソッドを持つオブジェクトを一つだけ与えるという点が、Set[]との大きな違い
    • eachを使ってそのオブジェクトから要素を一つずつ取り出し、それらを要素とするSetオブジェクトを生成

例(リファレンス例そのままだが。。。)

irb(main):006> Set.new([1, 2]) {|o| o * 2}
=> #<Set: {2, 4}>

2️⃣ 要素の重複の存在が許容されない

  • 集合は重複する要素を持たない特性があるため、setオブジェクトを作成する際に重複があると排除される
irb(main):031> set = Set['aaa', 'aaa']
=> #<Set: {"aaa"}>
irb(main):032> set = Set[1, 1]
=> #<Set: {1}>

# 要素が別属性の場合は削除されない
irb(main):033> set = Set[1, '1']
=> #<Set: {1, "1"}>

3️⃣要素間の順序関係がない

  • Setは内部的にハッシュを使って実装されているらしく 要素の順序は保証されない という特性を持つ
    • ハッシュ自体もキーの順序を保証しないため、セットも同様に順序関係を持たない
      • 十何年か前のRuby1.8の頃まではハッシュも順序を持たなかったらしいが、現在のRubyのハッシュは要素の順序を持つ
    • 内部的にハッシュが云々という話は関係なく、数学の「集合」という概念が順序を持たないものになっているため
    • 配列は順序が保証されるため、ここが大きな違い
  • 順序関係がないことを証明するために、2つの同要素を持つsetオブジェクトを順不同で作成し、同一かどうかを検証する
irb(main):034> set = Set[3, 1, 2]
=> #<Set: {3, 1, 2}>
irb(main):035> set2 = Set[2, 3, 1]
=> #<Set: {2, 3, 1}>

# 等しいオブジェクト
irb(main):036> puts set == set2
true

配列は順序が保証される

irb(main):037> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):038> b = [2, 3, 1]
=> [2, 3, 1]
irb(main):039> puts a == b
false

4️⃣ setオブジェクトを扱うためのインスタンスメソッドが用意されている

image.png

例) map!を使ってみる(ブロックの中の処理に応じた新しいsetオブジェクトに作り変える)

irb(main):029> set = Set['hello', 'world']
=> #<Set: {"hello", "world"}>

# 先頭を大文字にしたsetオブジェクトを返す
irb(main):030> set.map! {|str| str.capitalize}
=> #<Set: {"Hello", "World"}>

5️⃣ 継承関係は Set < Enumerable < Object < Kernel < BasicObject となっているため、Enumerableクラスのメソッドが使用できる

image.png

例) enumerableのfindを使ってみる

irb(main):020> a = Set[1, 2, 3]
=> #<Set: {1, 2, 3}>

irb(main):023> a.find {|i| i == 3}
=> 3
  • ちなみにEnumerableのメソッドにはsetオブジェクトを作成するためのto_setというメソッドも用意されている
irb(main):041> [2, 3, 1].to_set
=> #<Set: {2, 3, 1}>

Setを使うメリット

ちょっと調べきれていないので、他人様の記事を拝借(また自分で調べて書き足します)

重複を許可せず、かつ並び順を気にしなくて良い という場合にはsetオブジェクトを使ったほうがパフォーマンス的にもよいのかもしれない

参考資料

2
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3