Qiita Engineer Festa 2024(キータ・エンジニア・フェスタ 2024) - Qiita
において、約1ヶ月で38記事という大量の記事の投稿を要求されることがわかった。
そこで、あまりコストをかけずに記事数を稼ぐ方法を考えた結果、「Welcome to AtCoder を様々な言語で解く」ことを思いついた。
単に解くだけでなく、使用する言語仕様の解説を入れれば、記事として一応成立するだろう。
Welcome to AtCoder
PracticeA - Welcome to AtCoder
Welcome to AtCoder では、以下の形式で整数 $a$, $b$, $c$ および文字列 $s$ が入力として与えられる。
a
b c
s
この入力をもとに、与えられた整数の和 $sum = a + b + c$ および文字列 $s$ を、以下の形式で出力することが求められる。
sum s
今回用いる Mercury の機能
プログラムの構造
Mercury 入門 (1) Hello, World! - Mercury 勉強メモ
Mercury のプログラムは、以下の構造である。
各行の %
以降はコメントを表す。
:- module main.
:- interface.
% 外部に公開する述語の宣言
:- implementation.
% 述語の定義や、外部に公開しない述語の宣言
:- end_module main.
module
および end_module
の後の main
の部分はモジュール名であり、ファイル名に合わせなければならない。
AtCoder の環境では main
と書けば動くようである。
述語と変数の定義・入出力
Mercury 入門 (1) Hello, World! - Mercury 勉強メモ
よく使う述語: 入出力編 - Mercury 勉強メモ
Mercury サンプル: 入力を大文字に変換する - Mercury 勉強メモ
Mercuryにおける「述語」とは、引数の関係を定義することで計算を表現するものである。
述語には英大文字から始まる名前を、変数には英小文字から始まる名前を用いる。
述語の宣言は、以下のように行う。
:- pred 名前(引数リスト) is det.
最後の det
は、この述語が入力に対して必ず1個だけの出力を返すことを表す。
他の種類もあるが、ここでは扱わない。
述語の宣言における引数は、型::使用方法
の形式で表す。
型は、整数 int
、文字列 string
、入出力の状態 io
などがある。
使用方法は、入力 in
、出力 out
などがある。
述語の定義は、以下のように行う。
名前(引数リスト) :- 定義内容.
述語の定義における引数は、その引数に対応する変数名を書く。
定義内容は、この述語の引数の関係を表す述語などを書く。
定義内容を複数の述語などで表す場合は、コンマで区切る。
入出力は、以下のように行うことができる。
:- module main.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
main(IO1, IO3) :-
read_line_as_string(Line_raw, IO1, IO2),
(
Line_raw = ok(Line),
write_string(Line, IO2, IO3)
;
Line_raw = error(Err),
IO3 = IO2
;
Line_raw = eof,
IO3 = IO2
).
:- end_module main.
述語 main
は、第1引数の入出力の状態から、第2引数の入出力の状態への変化を定義する。
述語 read_line_as_string
により、標準入力から1行読み込むことができる。
引数は、左から順に、読み込んだ結果、読み込む前の入出力の状態、読み込んだ後の入出力の状態である。
読み込んだ結果からは、パターンマッチを用いて文字列 (読み込んだ行) を取り出すことができる。
この例では、Line_raw = ok(Line),
の後 ;
の前の部分が読み込みに成功した場合の処理である。
読み込んだ行を表す文字列は変数 Line
に割り当てられる。この文字列は (読み込んだ場合) 改行文字を含む。
述語 write_string
により、標準出力に文字列を出力することができる。
引数は、左から順に、出力する文字列、出力前の入出力の状態、出力後の入出力の状態である。
まとめると、この例は標準入力から1行読み込むことを試み、読み込めた場合はそれを出力し、読み込めなかった場合は何もしないプログラムである。
semidet の述語の扱い
Mercury 入門 (2) 式の計算と独自の述語の定義 - Mercury 勉強メモ
Mercury 入門 (6) 準決定的な述語と非決定的な述語 - Mercury 勉強メモ
標準ライブラリで定義されている述語の中には、semidet
のものがある。
この種類の述語は、入力に対して成功すれば1個だけの出力を返すが、成功しない可能性がある。
このような述語を用いる際は、以下のように if then else ゴールを用いると、述語が成功したかどうかで処理を分け、if
の中で用いる述語が失敗しても定義中の述語が失敗しないようにできる。
(
if 失敗する可能性のある述語
then 成功した場合の処理
else 失敗した場合の処理
)
数値の処理
Mercury 入門 (2) 式の計算と独自の述語の定義 - Mercury 勉強メモ
:- import_module int.
を書くことで、int
モジュールをインポートし、数値の演算ができるようになる。
A + B
により、数値 A
と B
の加算ができる。
文字列の処理
:- import_module string.
を書くことで、string
モジュールをインポートし、文字列の処理ができるようになる。
A ++ B
により、文字列 A
と B
を結合した文字列を求めることができる。
strip(A)
により、文字列 A
の最初および最後に連続する空白文字や改行文字を取り除いた文字列を得ることができる。
words(A)
により、文字列 A
を空白で分割した文字列のリストを得ることができる。
from_int(A)
により、数値 A
を文字列に変換した文字列を得ることができる。
to_int(A, B)
により、文字列 A
を数値に変換した結果を B
に割り当てることができる。
この述語は semidet である。
リストの処理
:- import_module list.
を書くことで、list
モジュールをインポートし、リストの処理ができるようになる。
index0(A, B, C)
により、リスト A
の B
番目 (0-origin) の要素を C
に割り当てることができる。
この述語は semidet である。
提出コード
今回はエラー処理を省略するため、(入力を含む) 失敗する可能性のある述語についてラッパーの述語を定義し、もとの述語が失敗した場合は適当な値を返すようにすることで必ず成功するようにしている。
:- module main.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module string.
:- import_module list.
:- import_module int.
:-pred read_line_as_string_det(string::out, io::di, io::uo) is det.
read_line_as_string_det(Res, IO1, IO2) :-
read_line_as_string(Read, IO1, IO2),
(
Read = ok(Str),
Res = Str
;
Read = error(Err),
Res = ""
;
Read = eof,
Res = ""
).
:-pred to_int_det(string::in, int::out) is det.
to_int_det(Str, Int) :-
(
if to_int(Str, Int_imm)
then Int = Int_imm
else Int = 0
).
:-pred index0_det(list(T)::in, int::in, T::in, T::out) is det.
index0_det(List, Idx, Default, Item) :-
(
if index0(List, Idx, Item_imm)
then Item = Item_imm
else Item = Default
).
main(IO1, IO5) :-
read_line_as_string_det(Line1, IO1, IO2),
read_line_as_string_det(Line2, IO2, IO3),
read_line_as_string_det(Line3, IO3, IO4),
A_str = strip(Line1),
BC = words(strip(Line2)),
index0_det(BC, 0, "", B_str),
index0_det(BC, 1, "", C_str),
to_int_det(A_str, A),
to_int_det(B_str, B),
to_int_det(C_str, C),
Sum = A + B + C,
write_string(from_int(Sum) ++ " " ++ Line3, IO4, IO5).
:- end_module main.