みんなでしりとり (paizaランク B 相当) でやったこと(Python3)


対象

これは paizaランク B 相当の練習問題としてここにあった問題の解答の解説となる。
Bランク問題のため、かなり初歩的な内容が含まれるが、同じようにこの問題で苦戦した人の一助となれば幸いである。

問題文

あなたは友達たちと N 人でしりとりを行うことにしました。
1 人目、 2 人目、...、 N 人目、 1 人目、2 人目、... という順序で発言をします。

ここで、それぞれの人は、次に挙げる 4 つのしりとりのルールを守って発言をする必要があります。

  1. 発言は、単語リストにある K 個の単語のうちのいずれかの単語でなければならない。
  2. 最初の人以外の発言の頭文字は、直前の人の発言の最後の文字と一緒でなければならない。
  3. 今までに発言された単語を発言してはならない。
  4. z で終わる単語を発言してはならない。

ここで、発言の途中で上のルールを破った場合、ルールを破った人はしりとりから外れます。
そして、その人を抜いて引き続きしりとりを続けていきます。このとき、後続の人は、ルール 2 を守る必要はありません。

N 人がしりとりを行ったログが M 行分与えられます。
このとき、M 回の発言が終わった後、しりとりから脱落せずに残っている人のリストを表示するプログラムを書いてください。

・苦戦した点
1.しりとりが成功するかどうかの判定
2.生き残っているメンバーの管理(また、メンバーが一周すると参照箇所をはじめに戻す処理)

コードの解説

変数定義

n,k,m = list(map(int,input().split()))
d = []
for i in range(k):
        d.append(input())
s = []
for i in range(m):
    s.append(input())

member_list = list(range(1,n+1))  #生き残っているメンバーリストの初期化
now = member_list[0] #現在のターンで発言するプレイヤー名
said = [] #発言の重複を避けるため、言った単語はストックしておく

しりとりが成功するかどうかの判定にcheck関数を定義した。
使える単語リスト(d)、用済みの単語リスト(said)、前のプレイヤーの単語(word_b)、今のプレイヤーの単語(word_a)を引数にもつ。
1人目やプレイヤー脱落直後の人はword_bに依存しないため、word_bの値をNoneとしておく。
check関数によりしりとり成功の可否をチェックし、
成功の可否にかかわらずsaidリストにword_aを記録させる。
next_member関数によりプレイヤー名を更新し、ループが1周する

for j in range(m):  #処理開始
    if j == 0:
        word_a = s[j]
        word_b = None
    elif booli == 0:
        word_a = s[j]
        word_b = None
    else:
        word_a = s[j]
        word_b = s[j-1]
    booli = check(d,said,word_b,word_a) #しりとり判定
    said.append(word_a)
    now = next_member(now,member_list,booli)
def check(goi,said,word_b,word_a):
    if ((len(said) == 0) or ((word_b == None)and(word_a in goi))): #一言目or脱落後ならtureを返す
        return bool(1)
    else:
        if (word_a in goi)and(not(word_a in said)and(not(word_a[-1]=='z'))):
            if word_b[-1] == word_a[0]:
                return bool(1)
            else:
                return bool(0)
        else:
            return bool(0)
def next_member(now,remain,booli):
    if booli == 0:
        i = remain.index(now) #今のターンの人のindexを取得し、iに保存
        remain.remove(now)    #今のターンの人をremainから削除
        if i >= len(remain):
            i -= len(remain)
        tugi = remain[i]
        return tugi
    elif booli == 1:
        i = remain.index(now)+1
        if i >= len(remain):
            i -= len(remain)
        tugi = remain[i]
        return tugi

最後に

ndash = len(member_list)
print(ndash)
for i in range(ndash):
    print(int(member_list[i]))

で出力を行う

反省

saidリストをわざわざ作らなくても、dから使用済みの単語は削除していく方が処理が楽だったかもしれない

課題

実はこのプログラム、意図していない動作があり、(して欲しい動きはしたので放置している)
next_memberはmember_listを引数にとりメンバーの削除を行うが、その結果を関数の外に戻していないため、
関数の外ではmember_listの値はデフォルト(全員分)のままだと思ったが
member_listは最新の状況を反映している。
def内の動きについてもう少し勉強して、理解できれば追記しようと思う。

コメントより

関数の呼び出し元と呼び出し先でオブジェクトを共有します。(変数代入でもオブジェクトを共有します)
リストはミュータブル(可変)オブジェクトです。呼出し先でオブジェクトの内容を変更すると、呼び出し元にも影響します。

とのことで、関数で使用した引数(オブジェクト)は共有されるため、通常の動作でした。

便利な反面、誤動作が怖いですね。勉強になりました。ありがとうございます。