成長観察日記

競プロとかPythonとかに関しての成長記録

茶コーダーが競プロ知識だけでやってみた!シリーズ〜バチャを半自動で立てられるPythonを改変し、Twitter投稿用テキストも生成してみた話〜

1日2本のバチャを手動で立てているのはアホすぎたので、心がぶっ壊れたのを機にやってみた(人生 いろいろ〜)




はじめに…大感謝 KowerKoint さん

AtCoder Problems Contest Builder という、バチャを立てている人間にとって、喉から手が出るほど欲しかったツール…。
こちらのステキツール、ツールの素敵さだけでなく、なんとまあコードの読みやすいこと読みやすいこと。
そして「動いたあやったあ!」とツイートした瞬間「わからないことあったら聞いてくださいね」とリプをくださる菩薩的人間性

All perfect…足を向けて眠れません。本当に、だいだいだいかんしゃ!です!

こちらのステキツールを、ABCなにか仕様に変更する過程が、今回の記事内容です。



私の事前知識

  • Pythonで競プロをやっている(底を擦ってる茶)
  • 競プロ以外だと、テキストファイルに出力するやり方は知っている
  • Web知識は人並みくらい

以上!!!!!



ABCなにかとは

元々、私が個人的に精進するために立て始めた、35分のバチャ(バーチャルコンテスト)です。

  • 平日14:00〜14:35、21:00〜21:35で開催
  • コンセプトは「早解き力を鍛える!35分でA〜D問題まで辿り着けるようになろう(できたら解けるようになろう)」
  • 毎晩21時の頭を冴えさせるために、平日に立てている

短い時間でしっかり集中できること、何が出てくるかわからないガチャ感、懐かしい回に出会った時の望郷感(?)、〜緑レートまでは本来の目的で、青以上は1st RTA狙いで…など、いろいろな方にお楽しみいただけています。うれしい



やりたいこと

  • diff範囲指定など、使わない機能を横に置く
  • ARC / AGCなにかを追加にあたり、ABC, ARC, AGCそれぞれのコンテスト指定範囲から乱数を生成
  • ABCはA〜D, ARCはA〜C, AGCはA〜Bの問題をセット
  • 生成したURLを、告知botツイート用にテキストファイルへ出力



やったこと

トークンってどうすりゃ手に入れられるんだ?を調べる

AtCoder Problems Contest Builder の README.mdに記載のある

トークンはAtCoder Problemsにログインしたブラウザに保存されているCookieから確認してコピペします。

の意味や、やり方が、永遠にわからなくて挫けそうになったんだぜ?
が、すでに崩れ落ちているむりおでんことわたくし(2023/05/27(土)現在)は、一味違うんだぜ?なので、得意のググり力でたどり着いた答えが chrome拡張機能のEditThisCookie でした。

拡張機能を起動して出てくるこいつをゲット!コピペだあ!!

無事にトークンとやらをゲットしました(まだふんわりとしかわかってない)



コードを読み、わからない部分を整理してママに聞く

業プロ的Pythonを読むのはまだ2回めなので、目が枯れるまで読みながら、コメントアウトをつけていく。

その後、いつもお世話になっているママことimaimaiさん、開発ができすぎてもう何がすごいのかよくわからないすごさ。あ、生み出しているからママなのか…?!?!
前回の大宴会スクレイピングに引き続き、助けを乞いました…いつもありがとう

ここからはぜひ、AtCoder Problems Contest Builder の main.py も開きながら読んでいただけると、よさそうです。

質問したことは以下です。

  • やりたいこと

    • ランダムに数字を生成して、abc{}a,〜dを追加した問題名で、問題セットを作りたい
  • わかった

    • problem_jsonで持ってきてる問題セットは、keyが問題名になっている
  • わからない

    • 54行目〜、きっと問題を設定している部分のfor文がまだ読解できてない…そこをどうにかすればできる…?
    • 83行目のtokenに直接書き込んでも良いのか…?
    • 全体的に、実験の仕方がわからない



いやあ。なんて素晴らしい質問の仕方なんだ。自画自賛するしかない。しない選択肢がない。

返答していただいた内容は

  • 54行目からは、config.pyで設定した内容から、問題の候補を絞っている
  • config.pyのproblem_infosの各オブジェクトに、問題名を設定するkeyを追加し、それをもとに問題を絞れそう
  • tokenは、他者に公開しない(githubなどに上げない)のであれば、直接書き込んでOK
  • 実験は、89行目以降のrequests.post以降をコメントアウトし、所々でprintデバックでOK

とのことで、満を持し、いじいじしはじめる

変更した箇所

config.py

ABC, ARC or AGCを1日2回開催するデータをセットするために、配列自体を増殖
diff設定などは適当にし、残しておきました(結果的に、のちほどmain.pyのfor文の中で設定しちゃったため)
いつか綺麗にしてあげたい…


# コンテストセット一覧
# abc用
contest_sets_abc = [
    {
        'name': '☀️🍰ABCなにか(Dまで)!',
        'title': '☀️🍰ABCなにか(Dまで)!',
        'memo': 'だれでも参加してください!早解き練習にどうぞ / ABCD問題を35分間走るバチャ / ABC126〜最新-10までのなにかがランダムで出現 / 通知bot https://twitter.com/abc_nanica',
        'everyday_start_time': '14:00',
        'duration_second': 2100,
        'penalty_second': 300,
        'problem_infos': [
            {
                'difficulty_range': (0, 1500),
                'point': 1,
                'include_experimental': True,
                'duplicate_remove_days': 60,
            }
        ]
    },
    {
        'name': '🌙🍰ABCなにか(Dまで)!',
        'title': '🌙🍰ABCなにか(Dまで)!',
        'memo': 'だれでも参加してください!早解き練習にどうぞ / ABCD問題を35分間走るバチャ / ABC126〜最新-10までのなにかがランダムで出現 / 通知bot https://twitter.com/abc_nanica',
        'everyday_start_time': '21:00',
        'duration_second': 2100,
        'penalty_second': 300,
        'problem_infos': [
            {
                'difficulty_range': (0, 1500),
                'point': 1,
                'include_experimental': True,
                'duplicate_remove_days': 60
            }
        ]
    }
]



# arc用
contest_sets_arc = [
    {
        'name': '☀️🍘ARCなにか(Cまで)!',
        'title': '☀️🍘ARCなにか(Cまで)!',
        'memo': 'だれでも参加してください!早解き練習にどうぞ / ABC問題を55分間走るバチャ / ARC104〜最新-2までのなにかがランダムで出現 / 通知bot https://twitter.com/abc_nanica',
        'everyday_start_time': '15:00',
        'duration_second': 3300,
        'penalty_second': 300,
        'problem_infos': [
            {
                'difficulty_range': (0, 4000),
                'point': 1,
                'include_experimental': True,
                'duplicate_remove_days': 60,
            }
        ]
    },
    {
        'name': '🌙🍘ARCなにか(Cまで)!',
        'title': '🌙🍘ARCなにか(Cまで)!',
        'memo': 'だれでも参加してください!早解き練習にどうぞ / ABC問題を55分間走るバチャ / ARC104〜最新-2までのなにかがランダムで出現 / 通知bot https://twitter.com/abc_nanica',
        'everyday_start_time': '22:00',
        'duration_second': 3300,
        'penalty_second': 300,
        'problem_infos': [
            {
                'difficulty_range': (0, 4000),
                'point': 1,
                'include_experimental': True,
                'duplicate_remove_days': 60
            }
        ]
    }
]



# agc用
contest_sets_agc = [
    {
        'name': '☀️🌶AGCなにか(Bまで)!',
        'title': '☀️🌶AGCなにか(Bまで)!',
        'memo': 'だれでも参加してください!早解き練習にどうぞ / AB問題を55分間走るバチャ / AGC001〜最新-2までのなにかがランダムで出現 / 通知bot https://twitter.com/abc_nanica',
        'everyday_start_time': '15:00',
        'duration_second': 3300,
        'penalty_second': 300,
        'problem_infos': [
            {
                'difficulty_range': (0, 10000),
                'point': 1,
                'include_experimental': True,
                'duplicate_remove_days': 60,
            }
        ]
    },
    {
        'name': '🌙🌶AGCなにか(Bまで)!',
        'title': '🌙🌶AGCなにか(Bまで)!',
        'memo': 'だれでも参加してください!早解き練習にどうぞ / AB問題を55分間走るバチャ / AGC001〜最新-2までのなにかがランダムで出現 / 通知bot https://twitter.com/abc_nanica',
        'everyday_start_time': '22:00',
        'duration_second': 3300,
        'penalty_second': 300,
        'problem_infos': [
            {
                'difficulty_range': (0, 10000),
                'point': 1,
                'include_experimental': True,
                'duplicate_remove_days': 60
            }
        ]
    }
]




main.py

どのバチャを立てるか、を入力で受け取る

14行目付近に打ち込みました。ドドパーン!
contest_typeという変数を作り、ターミナルで入力した「abc」または「arc」または「agc」を代入しています。
上記三種類以外の文字が入力されたら、ここで終了するようにしている。

ところで、いつも競プロでは「exit(print('hoge'))」って書いているので、同じ書き方をしそうになった、が、他の部分と合わせて0を返すようにした。
どうして0を返すのかはまだわかっていない…

print('どのバチャをたてる?')
contest_type = input()

if contest_type == 'abc':
    contest_sets = config.contest_sets_abc
elif contest_type == 'arc':
    contest_sets = config.contest_sets_arc
elif contest_type == 'agc':
    contest_sets = config.contest_sets_agc
else:
    print('打ち間違いだよ')
    exit(0)



バチャの日付設定のYYYY(西暦)を打たなくていいようにする

36, 45行目のinput前に'2023' + を書き加え、月日だけ入力すればOKにしました。来年になったら変えるぞ(どなたか、今年のクリスマスくらいに、私にプレゼントとして思い出させてください…)

date = '2023-' + input('開催日を入力してね(MM-DD): ')



指定コンテスト以外の問題は弾く

59行目の、for文内if条件文を書き換え
冒頭で作ったcontest_typeの文字列が入ってない問題は、さようならします

        if not contest_type in problem_id:
            continue



各コンテストにおける問題数の設定

75行目に追加

  • 途中から問題の傾向や、問題の数が変更になっているので、適切な範囲に設定
  • 最後の数字を手動で変えなきゃいけないのが若干めんどい。が、おそらくconfig.pyの設定ではじいてるから、適当なでか数字入れておけば良いのかも…
  • agcはzfillをするのを諦めてしまっているのがバレるぜ。3桁になったらちゃんとやる…
    if contest_type == 'abc':
        problem_id = f'abc{str(random.randint(126, 300))}_'
        problem_type = ['a', 'b', 'c', 'd']
    elif contest_type == 'arc':
        problem_id = f'arc{str(random.randint(104, 158))}_'
        problem_type = ['a', 'b', 'c']
    else:
        problem_id = f'agc0{str(random.randint(10, 60))}_'
        problem_type = ['a', 'b']
    
    
    for d in problem_type:
        problems.append({
            'id': problem_id + d,
            'point': problem_info['point'],
            'order': i
        })



tokenの記述

83行目の値を変更

token = 'gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

ご自身のトークンに置き換えてくださいませ。xの数はかなりてきとうっす



生成したURLをテキストに書き出し、ツイート用文章作成

103行目に追加

だいたい何をやっているかというと

  • ツイート生成用のテキストファイルを4つ作成
  • contest_type別にアイコンと、テキストファイルの名前を設定
  • 昼か夜か別に、ツイート用の文字列と、テキストファイルの名前を設定
  • datetimeから曜日を持ってきて、数値を日本語表記へ変換
  • 一覧用ツイート(【たてたよ】)と、通知用ツイート(今日の〇〇のぶん!)をセット。日付部分だけ抜き出しのスライスがなんか大変だった
# 通知ツート生成
if contest_type == 'abc':
    icon = '🍰'
    text_name = 'tweet_list_abc_'
elif contest_type == 'arc':
    icon = '🍘'
    text_name = 'argc_'
elif contest_type == 'tweet_list_argc':
    icon = '🌶'
    text_name = 'tweet_list_argc_'

if contest_index == 0:
    contest_time = 'おひる'
    text_name += 'day.txt'
else:
    contest_time = 'よる'
    text_name += 'night.txt'

day = start_dt.weekday()
day_of_week = {0: '(月)', 1: '(火)', 2: '(水)', 3: '(木)', 4: '(金)'}

# 一覧生成
f = open(text_name, 'a', encoding="utf-8")
f.write(str(start_dt)[8:10] + day_of_week[day] + '\n')
f.write('https://kenkoooo.com/atcoder/#/contest/show/' + contest_id + '\n\n')
f.close()


# 通知個別ツイート生成
f = open('tweet.txt', 'a', encoding="utf-8")
f.write(problem_id + '\n' + str(start_dt)[8:10] + day_of_week[day] + '\n')
f.write(f'{icon}今日の{contest_time}のぶん!' + '\n')
f.write('https://kenkoooo.com/atcoder/#/contest/show/' + contest_id + '\n\n')
f.close()

printの文言を変更

他、立てていてしあわせになれるよう、変えてみました。
プログラムが終了すると毎回「完了したよ!おつかれさま、いつもありがとう!」と言ってくれるの、愛着が深くなりすぎる…むふふ



こんな感じで立てられるようになったよ!

  • main.pyを実行し、立てたいコンテストを入力(abc, arc, agc
  • 昼か夜かを選ぶ
  • 立てたい日付を入力する(自動で前回立てた日の翌日になるので、違う場合は変更)


たったこれだけで!

自動的に問題がセットされて!

ツイート用の文章も作って!くれる!!

やばやばのやばで元気が出ました



今後どうにかしたいこと

  • 54行目のfor文をしっかり読解し、config.pyに無駄に設定してしまっている領域をすっきりさせたい…
  • if文がアホほど多い中学生コードなので、もうちょっと賢い業プロの書き方を知りたい



やってみて思ったこと

仕事以外で、自分のためにプログラミングをするのが初めてだったので、すごく楽しかったです。
今回はKowerKoint さんの素晴らしいベースから、自分仕様にいじいじしただけでしたが、いつか自分のためのツールを0から作ってみたいぞ!!と思いました。

そして、何より

「競プロで、Pythonを選んでよかった…」

これに尽きます…!
競プロ以外でPythonを使わなかったのですが、ほんとーにやっててよかったー!と心から思いました。
これを機に、Pythonで何かを作る、にもどんどんチャレンジしていきたいです。

読んでくださり、ありがとうございました!
ぜひ、ABCなにかで対よろです💪