RubyのSet(集合)入門!重複排除・集合演算(和・積・差)を直感的に扱う
生徒
「Rubyでデータのリストを作るときに、同じ名前や数字が混ざらないようにする方法はありますか?」
先生
「それなら『Set(セット)』というクラスを使うのが一番です。日本語では『集合』と呼びます。」
生徒
「集合…?数学の授業で聞いたことがあるような。難しそうですが、初心者でも使いこなせますか?」
先生
「大丈夫ですよ!ダブったデータを自動で消してくれたり、2つのグループを合体させたり、共通点を見つけたりするのが得意な、とっても便利な道具なんです。」
1. Set(集合)とは?配列との違いを理解しよう
RubyのSet(セット)は、同じ値が一つも存在しないことを保証してくれるデータの集まりです。プログラミング未経験の方がよく使う「配列(Array)」は、順番を大切にし、同じデータが何度出てきてもそのまま保存します。一方、Setは「何が入っているか」を重視し、重複を許さないという特徴があります。
例えば、参加者の名簿を作るとき、同じ人が二重に登録されていたら困りますよね。Setを使えば、うっかり同じ名前を追加してしまっても、コンピュータが自動的に一つにまとめてくれます。また、Setは内部的にデータを探すのが非常に速いため、大量のデータの中から特定のものが含まれているかチェックする際にも威力を発揮します。パソコンの操作に慣れていない方でも、「整理整頓が完璧な魔法の箱」だとイメージすれば分かりやすいでしょう。
2. Setを使い始める準備:require 'set'
RubyでSetを使うには、一つだけおまじないが必要です。SetはRubyの基本機能の一部ではありますが、使う前に「これからSetを使いますよ」と宣言しなければなりません。これを専門用語でライブラリの読み込みと呼びます。
プログラムの最初の行に require 'set' と書くだけで準備完了です。これだけで、高度な集合演算が使えるようになります。もしこれを忘れてしまうと、「Setって何ですか?」とコンピュータが困ってしまうので、必ずセットで覚えるようにしましょう。
require 'set'
# 新しいSet(集合)を作成する
users = Set.new(["田中", "佐藤", "田中"])
# 内容を表示(「田中」は一つにまとめられる)
puts users.to_a.inspect
実行結果は以下の通りです。
["田中", "佐藤"]
3. 重複排除のメリット:勝手にダブりを消してくれる
先ほどの例でも見た通り、Setの最大の武器は重複排除です。これは、複雑なシステムを作る際に非常に役立ちます。例えば、ブログのタグ管理や、お買い物サイトの閲覧履歴など、「同じものを二度登録したくない」場面は無数にあります。
配列でこれをやろうとすると、「すでにこのデータは入っているかな?」と毎回if文で確認しなければなりませんが、Setなら add という命令で追加するだけで、裏側で勝手にチェックしてくれます。これにより、プログラムがシンプルになり、読み間違いやミスを減らすことができるのです。初心者こそ、この「自動整理機能」を積極的に活用して、綺麗なコードを目指しましょう。
4. 和集合(|):2つのグループを合体させる
ここからは「集合演算」という、Setが得意とする計算について解説します。まずは和集合(わしゅうごう)です。記号は |(縦棒)を使います。これは、2つの集合を一つにまとめる操作です。もちろん、合体させた後にダブりがあれば、自動的に一つにまとめられます。
例えば、「野球部の人たち」と「サッカー部の人たち」をまとめて「運動部名簿」を作りたいとき、両方の部に所属している人がいても、Setなら一人分としてカウントされます。
require 'set'
baseball = Set.new(["田中", "佐藤", "鈴木"])
soccer = Set.new(["鈴木", "高橋", "伊藤"])
# 2つの部活を合体(和集合)
sports_club = baseball | soccer
puts sports_club.to_a.inspect
実行結果は以下の通りです。
["田中", "佐藤", "鈴木", "高橋", "伊藤"]
「鈴木さん」は両方にいましたが、結果の名簿には一人だけ載っていますね。これが和集合の便利なところです。
5. 積集合(&):共通しているものだけを抜き出す
次に紹介するのは積集合(せきしゅうごう)です。記号は &(アンド)を使います。これは、2つのグループのどちらにも入っている「共通メンバー」だけを取り出す操作です。
アンケートの結果などで、「商品Aも商品Bも両方買ったことがある人」を探したいときなどに非常に便利です。複雑な繰り返し処理を書かなくても、記号一つでパッと答えが出るのがSetの凄さです。
require 'set'
group_a = Set.new([1, 2, 3, 4, 5])
group_b = Set.new([4, 5, 6, 7, 8])
# 両方に共通する数字を取り出す(積集合)
common_numbers = group_a & group_b
puts common_numbers.to_a.inspect
実行結果は以下の通りです。
[4, 5]
6. 差集合(-):特定のものを引き算する
3つ目は差集合(さしゅうごう)です。記号は -(マイナス)を使います。その名の通り、ある集合から別の集合に含まれるデータを引き算します。
「全体のリストから、すでに案内を送った人を引きたい」「全校生徒の中から欠席者を引きたい」といった場面で活躍します。配列でも引き算はできますが、Setの方が直感的で動作も軽快です。
require 'set'
all_students = Set.new(["田中", "佐藤", "鈴木", "高橋"])
absent_students = Set.new(["佐藤", "高橋"])
# 全員から欠席者を引く(差集合)
present_students = all_students - absent_students
puts "出席者: #{present_students.to_a.inspect}"
実行結果は以下の通りです。
出席者: ["田中", "鈴木"]
7. 排他的論理和(^):どちらか片方だけにいる人
少し珍しいですが、便利なのが排他的論理和(はいたてきろんりわ)です。記号は ^(ハット)を使います。これは、「どちらか一方のグループだけに属している人(両方に属している人は除く)」を抜き出す操作です。
例えば、「月曜コースか火曜コースのどちらか一回だけ受講している人」を探したいとき(両方受けている熱心な人は除きたい場合)などに使えます。これもSetを使えば一瞬です。初心者の方は「はみ出している部分だけを集めるイメージ」を持つと良いでしょう。
8. 高速な存在チェック:include?
Setのもう一つの重要な特徴は、「あるデータが含まれているか調べるのがめちゃくちゃ速い」という点です。Rubyには include?(インクルード)という、データがあるか探す命令がありますが、配列(Array)の場合はデータの数が増えれば増えるほど、探す時間がどんどん伸びていきます。配列は端から順番に見ていくからです。
対してSetは、内部で「ハッシュ」という仕組みを使っており、データの場所をすぐに見つけることができます。何万件ものデータから「このIDは登録済みかな?」と確認するような処理では、配列ではなくSetを使うのがプログラマの常識です。動作がサクサク動くアプリを作るための、隠れたテクニックといえますね。
9. セット同士の比較:subset? と superset?
最後に、集合同士の「親子関係」を調べるメソッドを紹介します。
subset?(サブセット)は「ある集合が、別の集合に丸ごと含まれているか(部分集合か)」を判定します。
逆に superset?(スーパーセット)は「丸ごと含んでいるか(上位集合か)」を判定します。
例えば、「必要な資格リスト」という集合に対して、「自分が持っている資格リスト」という集合がすべてカバーしているか、といったチェックが可能です。結果は true(はい)か false(いいえ)で返ってきます。このように、Setは個別のデータだけでなく、「グループ同士のパワーバランス」を測るのにも適しているのです。
require 'set'
required_skills = Set.new(["Ruby", "HTML", "CSS"])
my_skills = Set.new(["Ruby", "HTML", "CSS", "JavaScript"])
# 自分のスキルは、必要スキルをすべてカバーしているか?
puts my_skills.superset?(required_skills)
実行結果は以下の通りです。
true
これで、集合の基本操作はバッチリです!重複を気にせず、賢くデータを管理できるようになりますよ。