先日書いた↓の続き、というか忘れてたこと。
環境変数ATCODER
ご存じの人はご存じでしょうが、AtCoderの実行環境には1つの特殊(?)な環境変数があります。それがATCODER
です。
次のようなシンプルなコードを、言語をJavaScriptにしてコードテストで実行してみます。
console.log(process.env.ATCODER)
実行結果は次のようになります。
これが何の役に立つかというと、実行環境がAtCoderか否かを判別できるわけです。
いやだからそれが何の役に立つのさって話になるんですが、言語問わずに1つ、JavaScriptでの実行においてはもう1つ使い道があります。まずは後者から。
入力データの取得元を切り替える
JavaScriptで実行する際の入力の受け取りについては以前書いた通りですが、入力における問題が実はもう1点あります。それはWindows環境においては同じ方法で入力を受け取れないということです。
なんだかんだ、普段使いのPCがWindowsだという人は少なくないはず。けど、提出コードがWindowsでは動かないのでは、コードテストから実行するしかなくテストもままならない。
そこで役立つのが環境変数ATCODER
。これを使えば実行環境がローカル環境(≒Windows)か否かが判別できるわけです。
前回のとおり、入力は1つの文字列として受け取ります。つまりテスト用の入力データをコード中で定義し、それをMain関数の引数として渡せば実際の入力を再現できるわけです。
なので環境変数ATCODER
の値によってMain関数に渡す値を、/dev/stdin
から読み込んだ値と、事前に用意したテストデータとで切り替えてあげればコードの改変なしにWindows環境とAtCoder環境それぞれに対応できるようになります。
function getReader(input) {
input = input.split("\n");
// 配列アクセス用の変数
let idx = 0;
// 配列から1行読み込む関数
return () =>
{
return input[idx++];
}
}
function Main(input) {
// 読み込み用クロージャを取得
let Read = getReader(input);
let n = parseInt(Read());
for (let i = 0; i < n; i++)
{
let a_i = parseInt(Read());
console.log(a_i);
}
let q = parseInt(Read());
for (let i = 0; i < q; i++)
{
let query_i = parseInt(Read());
console.log(query_i);
}
}
// テストデータ
const testData = `5
1
3
5
7
9
3
2
4
6
`
Main(process.env.ATCODER == 1 ? require("fs").readFileSync("/dev/stdin", "utf8") : testData);
デバッグログの埋め込み
次は言語問わずに使える手法。デバッグログの埋め込みです。
デバッグログなんてのはデバッグ実行してブレークポイント置けば良いって話でもあるんですが、都度値見るよりもパパッとログ出力ベースで値を見たいこともあるかもしれません。
そんなときにデバッグログを出力するんですが、普通にconsole.log
とかって出力してしまうと、うっかりそのコードを消し忘れて提出したが最後、WAとなってしまいます。
この問題を回避するにはAtCoderの環境ではログ出力されない仕組みが必要となります。そこで役立つのが環境変数ATCODER
です。
単純に考えるなら、console.log
の前にif文で環境変数をチェックすればいいわけです。
if (process.env.ATCODER != 1) console.log("デバッグログ");
まあただ、毎回if文書くのも時間がもったいないわけですから、こういうのは関数化しちゃいましょう。
function dlog(msg)
{
if (process.env.ATCODER == 1) return;
console.log(msg);
}
これでdlog("デバッグログ")
のように呼び出せば、ローカル環境でだけデバッグログが出力されるわけです。
ただ注意したいのは、このdlog
の呼び出しを$10^6$回も行えばそれなりに時間を浪費するということ。なんせ呼び出す度にif文で環境変数をチェックしてるわけですから。
実際に試してみましょう。AtCoderのコードテストにて次のようなコードを1000000
という値を入力値として実行してみます。
function getReader(input) {
input = input.split("\n");
// 配列アクセス用の変数
let idx = 0;
// 配列から1行読み込む関数
return () =>
{
return input[idx++];
}
}
function dlog(msg)
{
if (process.env.ATCODER == 1) return;
console.log(msg);
}
function Main(input) {
// 読み込み用クロージャを取得
let Read = getReader(input);
const n = parseInt(Read());
let cnt = 0;
for (let i = 0; i < n; i++)
{
cnt++;
dlog("test");
}
console.log(cnt);
}
const testData = `10
`
Main(process.env.ATCODER == 1 ? require("fs").readFileSync("/dev/stdin", "utf8") : testData);
実行時間は次の通り。
次にdlog("test");
の行をコメントアウトして実行してみます。
全然違いますね。ループ1回の中でたった1回if文を置いているだけでこれだけの差が出てしまいます。
ただこのif文、毎回見なくても良いですよね。環境変数は不変ですので、たった1回だけ判定すればいいだけのはずです。
なので「環境変数を見てログ出力するかを判別する関数」ではなくて、「環境変数を見て、ログ出力する関数か、何もしない関数を選ぶ関数」を定義します。
function getLogger()
{
if (process.env.ATCODER == 1)
{
return function(s) {};
}
return console.log
}
使い方は次のようになります。
function Main(input) {
let Read = getReader(input);
let dlog = getLogger();
const n = parseInt(Read());
let cnt = 0;
for (let i = 0; i < n; i++)
{
cnt++;
dlog("test");
}
console.log(cnt);
}
このコードを使って同じように$10^6$回ループした結果が次の通りです。
環境変数の判定はgetLogger関数の呼び出しの時に1回しているだけなので、$10^6$回の呼び出しでも十分高速に処理できます。
おわりに
実はデバッグログの実行コスト重たいんじゃないかなあと前々から思っていて、ビビって提出時には呼び出しを消してたこともありました。
今回試してみた結果、実際に実行時間に明らかな差が出てたので自分の予想は間違ってなかったようですね。
改善したコードで今日のコンテストに臨もうと思います。
本題の環境変数ATCODER
ですが、正直これ以外の使い道が今のところ考えつかなくて。他に何か使い道ありませんかね?