latexmk設定メモ
LaTeX文書のコンパイルをよしなにやってくれるlatexmkについて。
全体設定である ~/.latexmkrc
には以下のように記入してある。
#!/usr/bin/env perl $pdf_mode = 3; $latex = 'uplatex -kanji=utf8 -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S'; $bibtex = 'upbibtex %O %B'; $dvipdf = 'dvipdfmx %O -o %D %S'; $biber = 'biber --bblencoding=utf8 -u -U %O %S'; $makeindex = 'mendex %O -o %D %S'; $pvc_view_file_via_temporary = 0; $pdf_previewer = 'SumatraPDF -reuse-instance'
この設定は、ディレクトリごとの.latexmkrc
で上書きできる。
運用
ディレクトリごとに.latexmkrc
を置いて作業している。上の設定ファイルのうちビューワー設定以外の部分を、各状況にあわせて変更して使っている。
latexmk
pdfを作りたいだけのときlatexmk -pvc
継続ビルド。pdfを見ながら編集したいときlatexmk -pvc document.tex
上に同じ、ただしカレントディレクトリに*.tex
が複数あるときはこのように指定する
ビューワー設定
$pvc_view_file_via_temporary = 0; $pdf_previewer = 'SumatraPDF -reuse-instance'
$pvc_view_file_via_temporary
...latexmk -pvc
でPDFを作成するとき、既定では一時ファイルに出力してから目的のファイルに移動する仕様になっているが、この設定ではこれを0
にしている。$pdf_previewer
... Adobeなどファイルをロックするビューワーは使わないほうがよいだろう。この環境ではSumatraPDFにパスを通しているが、普通はフルパスを指定する
各種latexに共通のオプション
$latex = 'uplatex -kanji=utf8 -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S';
-synctex=1
...*.synctex.gz
を生成する。-file-line-error
... エラー出力がわかりやすくなるらしい。-interaction=nonstopmode
... 文法エラーなどのときにユーザーに尋ねなくなる。-halt-on-error
... ファイルが存在しないなどのときにユーザーに尋ねなくなる。
特に最後の2つは、継続ビルドして作業するのには必須だろう
なお、%O %S
はそれぞれオプションとソースファイルを表す。コマンド指定系の変数では、%
が全くない場合は自動的に適切なものが付与される。位置を変えたら何故か-interaction=nonstopmode
が効かなくなったので上のような順番がよいだろう。
latexごとの設定
生成経路に応じて数パターンあるが、ここではtex→dvi→pdfの場合と、tex→pdfの場合を書く。$pdf_mode
が生成経路を指定している。
tex→dvi→pdf
uplatexを使う例。
$pdf_mode = 3; $latex = 'uplatex -kanji=utf8 -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S'; $bibtex = 'upbibtex %O %B'; $dvipdf = 'dvipdfmx %O -o %D %S';
dvipdf
とdvipdfm(x)
ではオプションの順序が違うらしいので注意
tex→pdf
lualatexを使う例。(upbibtexにしない理由があるらしいが、こちらでは肯定的にも否定的にも未確認)
$pdf_mode = 1; $pdflatex = 'lualatex -synctex=1 -file-line-error -halt-on-error -interaction=nonstopmode %O %S'; $bibtex = 'pbibtex %O %B';
auxdirについて
-auxdir
/ $aux_dir
というオプションがあり、これを使うと各種の中間生成物の場所を別のディレクトリにできるような気がしてくるが、これは半分正しくない。現在はこのオプションが有効なのはMikTeX版のlatex系コマンドだけらしいので、残念ながら避けるのが無難だろう。
volatileとatomicの違い
volatileとatomicの違いを調べるために、以下のC++プログラムをコンパイルしてみる。
#include <atomic> void func1(int *p) { ++*p; ++*p; } void func2(volatile int *p) { ++*p; ++*p; } void func3(std::atomic_int *p) { ++*p; ++*p; }
$ g++ -std=c++11 -pthread -O2 -Wall -Wextra -g -c func.cpp -o func.o
環境による可能性はあるが、出力された機械語は端的に言うと次のようなものになる。(なおアーキテクチャはLinux x86-64)
func1: addl $2, (%rdi) ret func2: movl (%rdi), %eax addl $1, %eax movl %eax, (%rdi) movl (%rdi), %eax addl $1, %eax movl %eax, (%rdi) ret func3: lock addl $1, (%rdi) lock addl $1, (%rdi) ret
func1
は、「2を足す」という動作をしている。func2
は、「メモリから読み込んで1を足して書き込む」という動作を2回している。func3
は、「lock
しながら1を足す」という動作を2回している。
これは以下の違いによる:
volatile
は、メモリの読み込みや書き込みを、副作用を伴う動作と見なす。*1 そのため、読み込みや書き込み動作を減らす最適化を行わない。ただし、回数や順番さえ合っていればよいので、他のスレッドに干渉されるかどうかは考えない。atomic
は、他のスレッドが同時に読み書きしようとしても、あるひとまとまりの動作の間は独占的に動作するような振舞いになる。x86の場合はlock
プレフィックスで実現できる。
動作確認
この動作は以下のプログラムで確認できる。
#include <cstdio> #include <atomic> #include <thread> void func1(int *p); void func2(volatile int *p); void func3(std::atomic_int *p); void count1(int *p) { for(int i = 0; i < 1000000; ++i) { func1(p); } } void count2(volatile int *p) { for(int i = 0; i < 1000000; ++i) { func2(p); } } void count3(std::atomic_int *p) { for(int i = 0; i < 1000000; ++i) { func3(p); } } int main(int argc, char *argv[]) { int num = -1; if(argc > 1) { std::sscanf(argv[1], "%d", &num); } if(num == 1) { int x = 0; std::thread th0(&count1, &x); std::thread th1(&count1, &x); th0.join(); th1.join(); std::printf("%d\n", x); } else if(num == 2) { volatile int x = 0; std::thread th0(&count2, &x); std::thread th1(&count2, &x); th0.join(); th1.join(); std::printf("%d\n", x); } else if(num == 3) { std::atomic_int x = ATOMIC_VAR_INIT(0); std::thread th0(&count3, &x); std::thread th1(&count3, &x); th0.join(); th1.join(); std::printf("%d\n", (int)x); } return 0; }
$ g++ -std=c++11 -pthread -O2 -Wall -Wextra -g -c main.cpp -o main.o $ g++ -std=c++11 -pthread -O2 -Wall -Wextra -g func.o main.o -o main
$ ./main 1 2497250 $ ./main 1 2432386 $ ./main 1 3136510 $ ./main 1 2411326 $ ./main 1 3466956 $ ./main 1 2367656 $ ./main 1 2297168
$ ./main 2 2324260 $ ./main 2 3137164 $ ./main 2 2374254 $ ./main 2 2627152 $ ./main 2 2593840 $ ./main 2 2871581 $ ./main 2 2218822 $ ./main 2 2617198
$ ./main 3 4000000 $ ./main 3 4000000 $ ./main 3 4000000 $ ./main 3 4000000 $ ./main 3 4000000 $ ./main 3 4000000 $ ./main 3 4000000
これはマルチコアの動作結果に関わるので環境によって異なる動作をするかもしれない。手元の環境はVirtualBoxで仮想化されたLinux x86-64であった。
atomicでは不十分な場合もある
atomicで一まとまりの動作と見なされる範囲はごく小さい。例えば*p
が偶数であったとすると、以下の関数を繰り返し実行しても*p
の値が変わらないことが意図される(シングルスレッドではそうなる)が、マルチスレッドではそうはならない。
#include <atomic> void func(std:atomic_int *p) { *p += 1; *p ^= 1; }
このような場合は単に*p
をatomicにするだけでは不十分だが、mutexなどを使えばうまくいく。
*1:これはマルチスレッドのための仕組みというよりも、Memory-Mapped I/Oなどでメモリの読み書きが動作を伴う場合が想定されていると思われる。
文脈自由文法(CFG)と解析表現文法(PEG)をHaskellのモナドで説明する話
文脈自由文法(Context Free Grammar) と 解析表現文法(Parsing Expression Grammar) は記法が似ているものの、その性質は大きく異なっている。しかし、以下のようにHaskellのモナドを用いて、左再帰的でない文脈自由文法をそのままパーサーコンビネーターとして変換すると、PEGはList monadをMaybe monadとして置き換えたものとして説明できる。
-- CFGとPEGの関係を List vs. Maybe として説明するサンプル import Control.Monad import Control.Monad.State -- 型の説明 : StateT String m a -- StateT String ... 構文解析の残りの文字列を記憶している。 -- m ... 正否をあらわす。ListにするとCFG, MaybeにするとPEGになる -- a ... 今回は構文解析の正否のみに興味があるので () にする。 -- 演算子の説明 -- A >> B ... 構文要素の連接を表す。 -- A `mplus` B ... 構文要素の選択を表す。 -- CFG/PEG共通 : 指定した1文字を読む。 readChar :: MonadPlus m => Char -> StateT String m () readChar ch = do (ch0:str) <- get guard $ ch0 == ch put str -- CFG/PEG共通 : 文字列の終端か検査する。 eof :: MonadPlus m => StateT String m () eof = do [] <- get return () -- 文法その1 -- S -> A $ -- A -> "a" A "a" | "a" a :: MonadPlus m => StateT String m () a = (readChar 'a' >> a >> readChar 'a') `mplus` readChar 'a' grammar1 :: MonadPlus m => StateT String m () grammar1 = a >> eof -- 文法その2 -- S -> B $ -- B -> "a" B "a" | "b" B "b" | "" b :: MonadPlus m => StateT String m () b = (readChar 'a' >> b >> readChar 'a') `mplus` (readChar 'b' >> b >> readChar 'b') `mplus` return () grammar2 :: MonadPlus m => StateT String m () grammar2 = b >> eof -- 文法を(左再帰的でない)CFGとして検査する checkCFG :: StateT String [] () -> String -> Bool checkCFG grammar str = runStateT grammar str /= [] -- 文法をPEGとして検査する checkPEG :: StateT String Maybe () -> String -> Bool checkPEG grammar str = runStateT grammar str /= Nothing main :: IO () main = do forM_ [ "", "a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa", "aaaaaaa" ] (\str -> do putStrLn $ "checkCFG grammar1 " ++ show str ++ " = " ++ show (checkCFG grammar1 str) putStrLn $ "checkPEG grammar1 " ++ show str ++ " = " ++ show (checkPEG grammar1 str) ) forM_ [ "aa", "abba", "bbbbbb", "baaaab" ] (\str -> do putStrLn $ "checkCFG grammar2 " ++ show str ++ " = " ++ show (checkCFG grammar2 str) putStrLn $ "checkPEG grammar2 " ++ show str ++ " = " ++ show (checkPEG grammar2 str) )
実行結果
checkCFG grammar1 "" = False checkPEG grammar1 "" = False checkCFG grammar1 "a" = True checkPEG grammar1 "a" = True checkCFG grammar1 "aa" = False checkPEG grammar1 "aa" = False checkCFG grammar1 "aaa" = True checkPEG grammar1 "aaa" = True checkCFG grammar1 "aaaa" = False checkPEG grammar1 "aaaa" = False checkCFG grammar1 "aaaaa" = True checkPEG grammar1 "aaaaa" = False checkCFG grammar1 "aaaaaa" = False checkPEG grammar1 "aaaaaa" = False checkCFG grammar1 "aaaaaaa" = True checkPEG grammar1 "aaaaaaa" = True checkCFG grammar2 "aa" = True checkPEG grammar2 "aa" = True checkCFG grammar2 "abba" = True checkPEG grammar2 "abba" = True checkCFG grammar2 "bbbbbb" = True checkPEG grammar2 "bbbbbb" = True checkCFG grammar2 "baaaab" = True checkPEG grammar2 "baaaab" = False
文法の例として挙げたもの
S -> A A -> "a" A "a" A -> "a"
このCFGは奇数個のaからなる文字列を受理する。
S <- A A <- "a" A "a" / "a"
このPEGは(2の冪-1)個のaからなる文字列を受理する。
S -> B B -> "a" B "a" B -> "b" B "b" B -> ε
このCFGはaとbからなる偶数文字の回文を受理する。
S <- B B <- "a" B "a" / "b" B "b" / ε
このPEGはよくわからないが回文でも受理しない場合がある。(上の実行結果を見よ。)
GCC拡張の無効化 (と、それにまつわる細かい話)
端的に言えば-pedantic-errors
を使えばよい。(できれば-std=...
も併用したほうがいいだろう)
以下解説
GCCのC/C++コンパイラは、独自の拡張機能を導入している。これを無効化するオプションには2種類あり、意味が異なる。
実は、C言語の規格は、それぞれのコンパイラの拡張機能を全面的に禁止しているわけではない。「正しいプログラム(strictly conforming program)を正しく動かす」ことさえ満たせばいいので、いくつかの拡張機能が有効化されたままでも、標準に準拠していると言える場合があるのだ。
C標準と矛盾する拡張機能を無効化する
C標準と矛盾する拡張機能を無効化するには、 -std
オプションでISO C/C++を指定すればいい。 C Dialect Options - Using the GNU Compiler Collection (GCC) このオプションは、「C/C++のバージョン」「ISO準拠またはGNU独自」の2つの内容を指定する。
このオプションのデフォルトは、-std=gnu11
や-std=gnu++03
のように、gnuが含まれている設定である。これには、C標準と矛盾する拡張機能が含まれている。これを-std=c11
や-std=c++14
のように、cが含まれている設定に変えれば、C標準に準拠した挙動になる。
C標準と矛盾する拡張機能の例
- 1行コメント : C89と矛盾する。一見矛盾しないように見えるが、1行コメントの有無により挙動が変わるコードが存在する。
- Trigraphの無効化 :
"??/"
のようなコードの解釈が変わる。 typeof
: C++11以降のdecltype
に相当する機能。このような予約語をC標準の範囲内で追加することはできない(変数名と衝突する可能性があるからである)。同じ機能をもつ__typeof__
は問題ない。
多くのGCC拡張機能は、既存のCコードの意味を変えないように配慮されている。したがって、この方法で拡張機能を無効化することによる影響はそれほど大きくはない。
拡張機能を無効化する/または警告を出す
- 拡張機能に警告を出すには、
-Wpedantic
オプション*1を使う。 C Dialect Options - Using the GNU Compiler Collection (GCC) - 拡張機能をエラーにするには、
-pedantic-errors
オプションを使う。
これは、C/C++コード中で拡張機能が利用されていた時に、警告またはエラーにする機能である。
このオプションの挙動も明快に説明できるわけではなく、色々な注釈が必要である。
long long
など、「以前のバージョンではGCC拡張であったが、新しいバージョンでは標準」という機能は多数存在する。これらは、-std=c...
で指定されたバージョンに応じて判定する。-std=gnu...
が指定されていた場合は、対応するISO C/C++標準に基づいて判定する。しかし、-pedantic-errors
を指定していても、例えば-std=c90
と-std=gnu90
では意味が違う。上記の1行コメントの例は、前者ではコンパイルが通るが、後者ではコンパイルエラーになる。-Werror=pedantic
とすると、-Wpedantic
で出る警告すべてをエラーにすることができる。これは-pedantic-errors
とは微妙に意味が異なる。*2- 明示的に「拡張機能」とされているものを判定することはできるが、これによってプログラムが本当の意味でISO C/C++準拠であるかを確認できるとは限らない。
64bit windows上でのalloy* (hola)
Alloy (hola) のバックエンドはSATソルバであり、いくつかのバックエンドから選べる。しかし、64bit windows上の64bit JREでは、SAT4J以外は動作しない。
以下の手順で自分でjarを作成すれば、minisat等が動くバイナリが作れる。
kodkodのネイティブライブラリをダウンロードして展開
/some/path$ wget http://alloy.mit.edu/kodkod/release/win_x86_64.zip /some/path$ unzip win_x86_64.zip
Alloyをダウンロードして展開
/some/path$ wget http://alloy.mit.edu/alloy/hola/downloads/hola-0.2.jar /some/path$ mkdir hola-0.2 /some/path$ cd hola-0.2 /some/path/hola-0.2$ jar xvf ../hola-0.2.jar
kodkodのネイティブライブラリをコピー
/some/path/hola-0.2$ cp -r ../win_x86_64 amd64-windows
jarを作成
/some/path/hola-0.2$ jar cvfm ../hola-0.2.jar META-INF/MANIFEST.MF .
以上により、64bit windows用のネイティブライブラリが含まれたhola-0.2.jarが作成された。
C++11 random 覚え書き
C++11以降では<random>ヘッダで良質な擬似乱数を得ることができる。
#include <random>
外部からの乱数
外部から乱数を得るにはrandom_deviceを使う。
#include <random> #include <iostream> int main() { std::random_device rand_dev; std::cout << rand_dev() << std::endl; std::cout << rand_dev() << std::endl; return 0; }
上のように、外部からの乱数源は関数として呼び出すことで乱数を生成する。
このプログラムは実行するごとに異なる値を出力する。
擬似乱数
擬似乱数は、シード値を放り込むと乱数っぽい数列を吐き出すプログラム。
生成方法が何種類かあるが、mt19937だけ把握していればよい。
#include <random> #include <iostream> int main() { std::mt19937 rand_src(12345); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
上のように、擬似乱数生成器は関数として呼び出すことで擬似乱数を生成する。
このプログラムは異なる整数を2つ出力するが、プログラムの実行ごとに同じ整数を出力する。
ソース中の12345がシード値である。この数値を変更すると、異なる整数を出力するようになる。
乱数の加工
上で生成した乱数は32bit一様乱数等なので、必要な分布が出るように加工する。
#include <random> #include <iostream> int main() { std::mt19937 rand_src(12345); std::uniform_int_distribution<int> rand_dist(0, 99); std::cout << rand_dist(rand_src) << std::endl; std::cout << rand_dist(rand_src) << std::endl; return 0; }
上のように、分布を関数として使うときは、引数に乱数源への参照を渡す。内部で乱数源が(必要に応じて複数回)呼ばれている。
上のソースコードでは、MTで生成される擬似乱数列をもとに、一様分布に従う0以上99以下の整数を2つ生成している。
よく使う分布:
- 離散一様分布 uniform_int_distribution
- テンプレート引数 : 戻り値の型(int等)
- コンストラクタの引数 : a以上b以下の乱数を生成
- 連続一様分布 uniform_real_distribution
- テンプレート引数 : 戻り値の型(double等)
- コンストラクタの引数 : a以上b未満の乱数を生成
- 正規分布 normal_distribution
暗号論的擬似乱数
たぶんこのライブラリには無い。
分布と擬似乱数生成器を合体させる
分布に毎回擬似乱数生成器をつけるのは面倒であるから、<functional>ヘッダにあるbindとrefを使って以下のようにする。
#include <random> #include <iostream> #include <functional> int main() { std::mt19937 rand_src(12345); std::uniform_int_distribution<int> rand_dist(0, 9); auto rand_dist_bound = std::bind(rand_dist, std::ref(rand_src)); std::cout << rand_dist_bound() << std::endl; std::cout << rand_dist_bound() << std::endl; return 0; }
refを使わずに、
auto rand_dist_bound = std::bind(rand_dist, rand_src);
としても一応動作するが、これは擬似乱数生成器のコピーを作成してしまうので意味が変わる。同じ擬似乱数生成器を複数の分布で使う場合は注意が必要(通常、refをつけるほうが好ましい)
これは以下のようにしても同じことである:
#include <random> #include <iostream> #include <functional> int main() { std::mt19937 rand_src(12345); auto rand_dist_bound = std::bind( std::uniform_int_distribution<int>(0, 9), std::ref(rand_src)); std::cout << rand_dist_bound() << std::endl; std::cout << rand_dist_bound() << std::endl; return 0; }
さらに、この擬似乱数生成器はこの分布でしか使わないというのであれば、以下のようにもできる(この場合はrefは使わない):
#include <random> #include <iostream> #include <functional> int main() { auto rand_dist_bound = std::bind( std::uniform_int_distribution<int>(0, 9), std::mt19937(12345)); std::cout << rand_dist_bound() << std::endl; std::cout << rand_dist_bound() << std::endl; return 0; }
シードの決め方
シードの決め方は場合による。大雑把に言うと次の2種類:
- 外部からの乱数に基づいて決める
- 決め打ち
ただし、以下のことに注意が必要だ:
外部からの乱数に基づいてシードを決める
外部からの乱数に基づいてシードを決めるには、単に以下のようにすればよい:
#include <random> #include <iostream> int main() { std::random_device rand_dev; std::mt19937 rand_src(rand_dev()); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
このようにすると、毎回シードが変わるので、実行するごとに異なる整数が表示される。
外部からの乱数源を1回しか使っていないので、以下のようにもできる:
#include <random> #include <iostream> int main() { std::mt19937 rand_src(std::random_device{}()); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
コマンドライン引数に基づいてシードを決める
例えば、コマンドライン引数の1番目がある場合はそれをシードとして使うようにするには:
#include <random> #include <iostream> #include <string> int main(int argc, char *argv[]) { int seed = 12345; if(argc > 1) { seed = std::stoi(argv[1]); } std::mt19937 rand_src(seed); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
また、コマンドライン引数を与えない場合のデフォルトは、外部からの乱数でもよいかもしれない:
#include <random> #include <iostream> #include <string> int main(int argc, char *argv[]) { int seed; if(argc > 1) { seed = std::stoi(argv[1]); } else { seed = std::random_device{}(); } std::mt19937 rand_src(seed); std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
サンプルコード
POSIX usleep(3)を使って、1秒間あたり平均5個のランダムな(ポアソン過程に基づく)イベントを発生させる (十分たくさんの粒子があるときの放射線の発生とかがこれ)
#include <random> #include <iostream> #include <string> #include <functional> #include <unistd.h> int main(int argc, char *argv[]) { int seed; if(argc > 1) { seed = std::stoi(argv[1]); } else { seed = std::random_device{}(); } std::mt19937 rand_src(seed); auto rand = std::bind( std::exponential_distribution<double>(5.0 / 1000000.0), std::ref(rand_src)); while(true) { std::cout << "a" << std::flush; usleep(rand()); } std::cout << rand_src() << std::endl; std::cout << rand_src() << std::endl; return 0; }
いわゆる「中卒」は数学/情報オリンピックには参加できるのか否か
「高校生に相当する年齢だが、高校等の教育機関に在学していない」場合、数学オリンピックや情報オリンピックには参加できるのか、という問題がある。
該当する組織に直接訊ねるのが最も正確だと思われるが、2015/06/26時点の資料を読む限り、おそらく以下の通りだと思われる。
- 数学オリンピック・情報オリンピックともに、年齢制限がある。
- 数学オリンピック(国際・日本)は、大学等に入学していなければ参加できる。すなわち、いわゆる「中卒」でも参加できる。
- 情報オリンピック(国際・日本)は、高校等に在学していれば参加できる。すなわち、いわゆる「中卒」は参加できない。
- (JOIの予選は誰でも参加できる。)
以下、根拠となる文書を引用する。
日本数学オリンピック
2016年1月時点で大学教育(またはそれに相当する教育)を受けていない20歳未満の者。但し、IMO代表資格は、IMO大会時点で大学教育等を受けていない20歳未満の者。
http://www.imojp.org/mo2016/jmo2016/recruit.html
国際数学オリンピック
A Country’s Contestants should normally be citizens or residents of that Country, and should be selected through that Country’s national Mathematical Olympiad or equivalent selection programme. Contestants must not have formally enrolled at a university or any other equivalent post-secondary institution, and they must have been born less than twenty years before the day of the second Contest paper.
https://www.imo-official.org/documents/RegulationsIMO.pdf
以下、qnighyによる訳:
通常、各国の競技者(参加者)はその国の市民ないしは住民であるべきであり、その国の国内数学オリンピックまたはそれと同等の選抜プログラムによって選ばれた者であるべきである。競技者は、大学やそれと同等の高等教育機関に正式に入学したことのある者であってはならない。また、競技者の誕生日は、競技日のうち2日目から数えて20年前よりも後でなくてはならない。
日本情報オリンピック
http://www.ioi-jp.org/joi/2015/joi-2016-outline.pdf
- 2016 年 2 月 14 日(日)の第 15 回日本情報オリンピック本選競技実施時点で、高等学校、高等専門学校、中学校、中等教育学校、小学校、特別支援学校に在学し、学年が高等学校2年以下(中等教育学校や高等専門学校などの在校生は高等学校2年に相当する学年以下)であること。 日本国内において学校以外の初等・中等教育機関に所属している人は個別に相談。
- 生年月日が 1996 年 4 月 2 日以降であること。
国際情報オリンピック
A Contestant is a student who was enrolled at a school for secondary education, in the Country they are representing, during the period September to December in the year before IOI’n, and is not older than twenty years on the 1st of July of the year of IOI’n.
http://www.ioinformatics.org/rules/reg14.pdf
以下、qnighyによる訳:
競技者(参加者)とは、該当年のIOIの前年9月から12月の間に、その者が代表する国にある中等教育のための学校に在学し、かつ、該当年のIOIのある年の7月1日時点で20歳以下である生徒のことである。
(was enrolledって在学と解釈していいんだろうか。)