問題
あなたは友達たちと N 人でしりとりを行うことにしました。
1 人目、 2 人目、...、 N 人目、 1 人目、2 人目、... という順序で発言をします。ここで、それぞれの人は、次に挙げる 4 つのしりとりのルールを守って発言をする必要があります。
- 発言は、単語リストにある K 個の単語のうちのいずれかの単語でなければならない。
- 最初の人以外の発言の頭文字は、直前の人の発言の最後の文字と一緒でなければならない。
- 今までに発言された単語を発言してはならない。
- z で終わる単語を発言してはならない。
ここで、発言の途中で上のルールを破った場合、ルールを破った人はしりとりから外れます。
そして、その人を抜いて引き続きしりとりを続けていきます。このとき、後続の人は、ルール 2 を守る必要はありません。
N 人がしりとりを行ったログが M 行分与えられます。
このとき、M 回の発言が終わった後、しりとりから脱落せずに残っている人のリストを表示するプログラムを書いてください。
コード
n: 人数
words: 使っていい単語のリスト
seq: 発言のリスト
last: 前に言われた発言(nilの場合はなし)
f: 全員の通し番号(-1の場合は脱落している)
person: いまの発言者
said: 最新の発言
n, k, m = gets.split.map(&:to_i)
words = Array.new(k) { gets.chomp }
seq = Array.new(m) { gets.chomp }
last = nil
#発言が条件を満たしていればtrue、いなければfalseを返すメソッド
define_method(:check) do |said|
return false unless words.include?(said) #単語リストにあるか?
if last
return false if last[-1] != said[0] #「しりとり」になっているか?
end
return false if said[-1] == "z" #最後が「z」で終わっていないか?
true
end
f = (1..n).to_a
person = 0
seq.each do |said|
if check(said)
#発言が条件を満たしている場合
#単語のリストから最新の発言を消す
i = words.find_index(said)
words[i] = ""
last = said #最後に言われた単語を更新する
else
#発言が条件を満たしていない場合
f[person] = -1 #発言者を「脱落」させる
last = nil
end
#次の発言者に移る
loop do
person = (person + 1) % n
break if f[person] > 0
end
end
f.select! { _1 > 0 }
puts f.size #残った人数を出力
puts f #残った人の番号を出力
条件分岐が複雑なので、一部メソッド化することを考えます。
メソッドcheck(said)
は、発言が条件を満たしていればtrue
、いなければfalse
を返します。
メソッドは条件をそのままコードにしているだけですが、ひとつの単語は一度使われたらもう使えないので、単語リストwords
から消すことにします。それで、条件(1)と(3)を同時に処理しています。
なお、メソッド定義にdefine_method
を使っているのは、メソッド内で外部の変数を参照しているからです。