📚
ダブルスのシャッフル機能について考えてみた(Ruby)
はじめに
バドミントンサークルで、自動で試合の組み合わせを決めてくれるアプリを欲しかったので、シャッフル機能を検討しました。
フロントはActionViewを想定しています。
シャッフルの条件
- 各人の試合出場回数をカウントできること
- 完全に同じ試合が出た場合は再度シャッフル
- 入力値はコート数、人数
- 全てダブルスの試合
使用したRubyのメソッド紹介
shuffle
配列の中身をランダムシャッフルしてくれます。
!をつけると破壊的になります。
sample
配列からランダムに一つ値を返します。引数を指定すると複数返すことも可能です。
combination
配列から引数で使用したサイズの組み合わせを返します。
10個の配列で引数2を指定した場合、10C2(=45)通りの配列を返してくれます。
テーブル
シャッフル機能とは関係無いテーブルやカラムは除外しています。
実装
match.rb
class Match < ApplicationRecord
belongs_to :event
# unfixed: 調整中の組み合わせ, fixed: 決定した組み合わせ, past: 終了した組み合わせ
enum state: { unfixed: 0, fixed: 1, past: 2 }
end
event.rb
class Event < ApplicationRecord
has_many :attendances, dependent: :destroy
has_many :matches, dependent: :destroy
# 仮決定の組み合わせ
def match_unfixed_array
match_set = []
matches.unfixed.each do |m|
match_set << [m.user_a, m.user_b, m.user_c, m.user_d]
end
match_set
end
# 決定した組み合わせ
def match_fixed_array
match_set = []
matches.fixed.each do |m|
match_set << [m.user_a, m.user_b, m.user_c, m.user_d]
end
matches.past.each do |m|
match_set << [m.user_a, m.user_b, m.user_c, m.user_d]
end
match_set
end
# 仮組
def match_array
match_set = []
matches.each do |m|
match_set << [m.user_a, m.user_b, m.user_c, m.user_d]
end
match_set
end
# 引数はmatchオブジェクトが入り、過去の試合と重複していないかを確認
def check_duplication_match(match)
match_check = [match[0], match[1], match[2], match[3]]
!match_array.include?(match_check)
end
# 引数はmatchオブジェクトが入り、現在の試合の中でユーザーが重複していないか確認
def check_duplication_member(match)
match_check = match_unfixed_array.flatten
match.each do |m|
match_check -= [m]
end
match_check.count == match_unfixed_array.flatten.count
end
# viewsで使用 ユーザーの試合回数を算出
def match_count_player(user)
match_fixed_array.flatten.count(user)
end
def matches_make
member = attendances.absent.pluck(:user_id)
pairs = member.combination(2).to_a
round_robin = []
pairs.combination(2).to_a.each do |i|
if i.flatten.uniq.count == 4
round_robin << [i[0][0], i[0][1], i[1][0], i[1][1]]
end
end
round_robin
end
end
matches_controller.rb
# 1試合目のみ実行される
def create
event = Event.find(params[:event_id])
event.matches.unfixed.destroy_all
court_num = params[:match][:court_num].to_i
matches = event.matches_make.shuffle
match = matches.sample
if event.matches.empty?
event.matches.create(state: 'unfixed', user_a: match[0], user_b: match[1], user_c: match[2], user_d: match[3])
end
matches.each do |m|
# court_numはコート数で、試合の組み合わせがコート数と同じだけ決まった段階で繰り返しを終了させる
if court_num == event.matches.unfixed.count
break
end
# メンバーの重複および過去の試合と同じ組み合わせになっていないかを確認
if event.check_duplication_match(m) && event.check_duplication_member(m)
event.matches.create(state: 'unfixed', user_a: m[0], user_b: m[1], user_c: m[2], user_d: m[3])
end
end
end
# 既に1試合以上fixed終えている場合はこちらのメソッドを
def match_fixed
event = Event.find(params[:event_id])
# 現在確定している試合を終了させステータスに変更する
event.matches.fixed.update(state: 'past')
# 調整中の組み合わせを全て削除する
event.matches.unfixed.destroy_all
# 新たな組み合わせを作成
params[:match][:matches].each do |i|
user_a = event.attendances.find(i['user_a']).user.id
user_b = event.attendances.find(i['user_b']).user.id
user_c = event.attendances.find(i['user_c']).user.id
user_d = event.attendances.find(i['user_d']).user.id
event.matches.create(state: 'fixed', user_a: user_a, user_b: user_b, user_c: user_c, user_d: user_d)
end
end
最後に
スーパーFat Controllerなのでリファクタリングのしがいがありますね!!
これ以上モデルにメソッドを寄せてもFat Modelになっちゃうので、フォームオブジェクトで全部まとめる方がいいかも?
Discussion