paiza Bランク・スキルチェック過去問題「みんなでしりとり C編」を沼りながら解いた

以下の、paiza Bランク スキルチェック過去問「みんなでしりとり」をC言語で解きました。

https://paiza.jp/works/mondai/b_rank_skillcheck_archive/word_chain

解答へコメントを付与して、簡単に覚書しておきます。

解くのにだいぶ難航して、合計20回くらいデバッグ出力と誤答を繰り返しました。

真っ当に順当にしりとりを判定して、シミュレーションをするという戦略をとりました。
そのためか、ソースコードは長いです。

では、最終的なコードは以下の通りです。

```C
#include <stdio.h>
#include <string.h>
int main(void){
    
    // 入力受け取り（人数、キーワード数、発言数）
    char buf[100];
    int people_num, keyword, meme;
    fgets(buf, sizeof(buf), stdin);
    sscanf(buf, "%d %d %d", &people_num, &keyword, &meme);
    
    // 入力受け取り（キーワード一覧）
    char keywords[1000][100];
    for(int i = 0; i < keyword; i++) {
        fgets(keywords[i], sizeof(keywords[i]), stdin);
        keywords[i][strlen(keywords[i])-1] = '\0';
        //printf("%s\n", keywords[i]);
    }
    
    // 参加メンバーリスト作成（1でゲーム残留、0で敗退）
    int people[10];
    for(int i = 0; i < people_num; i++) {
        people[i] = 1;
    }
    
    // ここからゲーム開始
    
    int count = 0; // メンバーリストの現在地インデックス
    char mae = '\0'; // しりとりのための最後尾文字
    
    // 発言数分処理を繰り返す
    for(int i = 0; i < meme; i++) {
        
        int flag = 0; // ゲームへの残留/敗退判定フラグ（最初は敗退状態からはじめる）
        
        char buf[100];
        fgets(buf, sizeof(buf), stdin);
        
        // 文字列の改行が邪魔なので、改行がついていたら、削除する（改行を文字列終端に置き換える）
        if(buf[strlen(buf)-1] == '\n') {
            buf[strlen(buf)-1] = '\0';
        }
        
        // 発言文字列がキーワード一覧に存在するか確認する
        for(int j = 0; j < keyword; j++) {
            //printf(">>%s %s<<\n",buf, keywords[j]);
            if(strcmp(keywords[j], buf) == 0) {
                flag = 1; // キーワードが存在したら、フラグを残留状態へ倒す
                // キーワードの再使用は禁止なので、
                // 1文字目を文字列終端に置き換えて、簡易的に使用キーワードを削除する
                keywords[j][0] = '\0'; 
                break;
            }
        }
        
        // キーワードが存在したら、次に'z'で終わっている文字列かチェックする
        if(flag == 1 && buf[strlen(buf)-1] == 'z') {
            flag = 0; // zで終わっていたら敗退状態へ
        }
        
        //printf("[%c][%c]\n", mae, buf[0]);
        
        // フラグが残留状態だったら、しりとり条件の判定をする
        if(flag == 1) {
            // 最後尾文字が\0でないなら、しりとり条件の判定が必要
            if(mae != '\0') {
                // 最後尾文字と新しい発言の冒頭文字が一致しなかったら、しりとり失敗
                // フラグを敗退状態へ
                if(mae != buf[0]) {
                    flag = 0;
                }
            }
        }
        
        //printf("%s %d %d\n", buf, count, flag);
        
        // フラグが敗退状態だったら、メンバーリストに敗退を記録する
        if(flag == 0) {
            people[count] = 0;
            mae = '\0'; // ゲームのしりとり条件をリセットするため、最後尾文字を\0にしておく
        }
        else {
            mae = buf[strlen(buf)-1];
        }
        
        // 次の順番のメンバーを探索する
        do {
            // countをインクリメントして、人数をはみ出る場合はループで0に戻す
            count++;
            if(count >= people_num) {
                count = 0;
            }
            // 残留メンバーが1人かどうかを判定し、1人だったらゲーム終了
            int sum = 0;
            for(int i = 0; i < people_num; i++) {
                sum += people[i];
            }
            if(sum == 1) {
                break;
            }
            
        } while(people[count] != 1); // 残留状態ではないメンバーだったら、再度ループ処理（次のメンバーのチェックへ）
    }
    
    // ここから結果を出力する
    
    // 残留メンバーをカウントする
    int result = 0;
    for(int i = 0; i < people_num; i++) {
        if(people[i] == 1) {
            result++;
        }
    }
    
    printf("%d\n", result);
    
    // 残留メンバーリスト出力
    for(int i = 0; i < people_num; i++) {
        if(people[i] == 1) {
            printf("%d\n", i+1);
        }
    }
    
    
    return 0;
}
```

# 難航ポイント

難航したポイントは、

- キーワード、発言へ付与される改行が邪魔で、判定が正常にならない
- メンバーのターンの順番がループするのを忘れた
- 残留メンバーが1人になったときにwhileループをbreakするのを忘れて、無限ループを起こす

こんな感じでした。

# 終わりに

難航ポイントを踏まえると、もうちょっと慎重に実装することが必要だなと思いました。

部分正答みたいな回答が何度もあったため、条件の吟味が必要ですね。

特に0 -> 1 -> 2 -> 0 -> 1 -> ...みたいなインデックスのループ処理を忘れやすいです。

ここを今後は意識していきたいです