LoginSignup
0
0

More than 3 years have passed since last update.

[AtCoder]問題の入力と期待値を自動取得してみた

Last updated at Posted at 2020-05-24

はじめに

言語の勉強としてAtCoderのABC(AtCoder Beginners Contest)の問題を解いていました。
しかし問題ページにある複数の入力サンプルと出力サンプルをコピペして目視であってるかどうかを確認するのは面倒だなと思いました。
そこでpuppeteerによるスクレイピングで入力サンプルと出力サンプルを自動で取ってきて、自動でテストするスクリプトを書いてみました。
今回はpuppeteer部分でやったことなどを残しておきます。

調査

問題ページのURLは、例えば第88回目のABCのB問題はhttps://atcoder.jp/contests/abc088/tasks/abc088_b
第160回目のABCのD問題はhttps://atcoder.jp/contests/abc160/tasks/abc160_b
となってます。
問題ページのURLは、

https://atcoder.jp/contests/<コンテスト名>/tasks/<コンテスト名>_<問題名>

で取れることがわかりました。
次に各問題ページ入力と出力例のDOM情報を調べてみます。
幸いどの問題でも例えば入力1では、

html > body > div#main-div.float-container > div#main-container.container > \
div.row > div.col-sm-12 > div#task-statement > span.lang > span.lang-ja > \
div.part > section > pre#pre-sample0

ここに配置されていることが確認できました。
pre#pre-sample<数字>の部分に関しては
入力1 -> pre#pre-sample0
出力1 -> pre#pre-sample1
入力2 -> pre#pre-sample2
出力2 -> pre#pre-sample3
入力3 -> pre#pre-sample4
出力3 -> pre#pre-sample5
...
となっていました。
これだけの情報で十分です。

コード例

第xxx回目ABC -> abc<xxx>
A問題 -> a
として例えば第160回目ABCのA問題のサンプルを全部取得したい時は、

node get_sample.js abc160 a

とコマンドライン引数を与えることを前提として以下のようにコードが書けます。

#!/usr/bin/env node
const puppeteer = require('puppeteer');
const fs = require('fs')
//ex; abc161
const contest_number=process.argv[2]
//ex; a,b,c..,f
const question=process.argv[3]
const atcoder_url="https://atcoder.jp/contests/" + contest_number + "/tasks/" + contest_number + "_" + question
(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto(atcoder_url);
    const common_selector = 'html > body > div#main-div.float-container > div#main-container.container > div.row > div.col-sm-12 > div#task-statement > span.lang > span.lang-ja > div.part > section > '

    for(i = 0; ;i += 2){
        const in_selector=common_selector + `pre#pre-sample${i}`;
        const exp_selector=common_selector + `pre#pre-sample${i+1}`;
        //取得する前にそもそもデータが存在するかのチェックを行う
        const in_data =  await page.$(in_selector) != null? await page.$eval(in_selector, item => {return item.textContent;}) : null
        const exp_data = await page.$(exp_selector) != null? await page.$eval(exp_selector, item => {return item.textContent;}) : null
        if(in_data != null){
            const filepath = `./test/in${i/2 + 1}`
            fs.writeFile(filepath, in_data, (err, data) => {
                if(err) console.log(err);
            });
        }else{
            break;
        }
        if(exp_data != null){
            const filepath = `./test/exp${i/2 + 1}`
            fs.writeFile(filepath, exp_data, (err, data) => {
                if(err) console.log(err);
            });
        }else{
            break;
        }
    }
    await browser.close();
})();

これでカレントディレクトリのtestというディレクトリに入力例(in〇)と出力例(exp〇)を保存できます。
このjsにshebangを記載しパスの通ったディレクトリに置いておくことでいつでも呼び出せるようにしておきます。
自動テストスクリプトの実装は省きますが、このjsにshebangを記載することで自動テストスクリプトをshellスクリプトで書いた場合、呼び出しも簡単になります。

get_sample.js <コンテスト名> <問題名>

自動テストスクリプトではカレントディレクトリのtest/in1,in2,in3があることを確認し
あれば、プログラムのビルドし、これらを入力とし、実行。
それぞれの結果とexp1,exp2,exp3が一致するかはdiffコマンドで確認できます。

最後に

最近アルゴリズムの勉強もした方がいいのかなと思う今日この頃、、、AtCoderまずは緑色頑張ります!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0