プログラミングを上達させたい

情報学専攻の大学院→放送局でCMの営業など@大阪→舞台俳優&IT営業@東京

AtCoderでProlog使えるようになってるじゃん

ふとAtCoderのルールを見てみると、言語にPrologが追加されてました。
今僕はAtCoderをJavaかSchemeで解いています。よくこのブログではSchemeを使う理由を「扱える整数の幅がでっけぇ」みたいに書いてますが、もっというと
・大学で習った言語のうちの1つだから
・他にAtCoderで使っている人が少なくて嬉しいから
というのが根本的な理由です。
ただですね、、、人生で一番最初に習った言語はPrologなんですよ。。。
ちなみに、それゆえプログラミングに取り組むのが2年くらい遅れました。大学2年?のときくらいに授業でやって、「さっぱり分からん、プログラミングってあまり得意じゃないな」と思って、なんとなく避けるようになったという経緯です。
そのあと、大学院の研究でRを使い→誘われた競技プログラミングの大会(オンサイト)でRを使って必死に解き→競技プログラミングにハマる、という流れで現在に至ります。

前置きが長くなりましたが、私は上記の条件を満たすPrologでAtCoderを解かなければいけない。いけないんだ。
ということで、とりあえずPrologでAtCoderの問題をいくつか解くぞという記事です。

参考にさせていただいた記事

qiita.com
@n4o847さん、Prologは無事、AtCoderで使えるようになりました・・・!

入出力について

AtCoderをやる上で一番難しいでおなじみ、標準入出力。上記の初めて出た競技プログラミングの大会でも、大会時間の半分は"Rでの標準入出力のやり方"を調べるのに使ったのを鮮明に覚えています。
@n4o847さんのQiita記事を参考にすると、数字や文字列を受け取るときは、自分で新しい関数を定義しなきゃいけないようです。

get_string(String) :-
  current_input(Input),
  read_string(Input, ' \n', '', _, String).

get_number(Number) :-
  get_string(String),
  number_string(Number, String).

get_numberの中にget_stringを使っていることに注意。get_numberのみでは動作しません。

文字は、もともと関数があります。

get_char(A).

N個の数字を入力するには、引数2個の下記のような関数を定義します。

get_numbers(0, []).
get_numbers(N, [X|Xs]) :-
  get_number(X),
  N1 is N - 1,
  get_numbers(N1, Xs).

解いてみるぞ!

直近のAtCoderBeginngerContestのA問題を解いてみます。

ABC170のA問題
入力される数字5個を足し、15から引くとよい。

get_string(String) :-
  current_input(Input),
  read_string(Input, ' \n', '', _, String).

get_number(Number) :-
  get_string(String),
  number_string(Number, String).

main :-
  get_number(X1),
  get_number(X2),
  get_number(X3),
  get_number(X4),
  get_number(X5),
  Ans is 15 - X1 - X2 - X3 - X4 - X5,
  write(Ans).
:- main.

無事AC。

もうちょいムズいのも解いてみるぞ!

次は、inputが可変個の問題にチャレンジ。最近の問題から、下記を解いてみます。
atcoder.jp

入力される数の個数であるNがN <= 1000だから、挿入ソートでも充分間に合うなぁと書こうとするも・・・・
全く書けず・・・・・・・

普段もSchemeでプログラムを書いたりするから、リスト的な書き方には慣れてると思っていたものの、Prologは論理型。やはり関数型と違う・・・

例えば、リストの先頭k個の数の合計を算出する関数get_k_numsというものを書く場合、Schemeだとこうなります。

(define (get_k_nums k list)
  (if (= k 0) 0
      (+ (car list) (get_k_nums (- k 1) (cdr list)))))

ただ、Prologの場合はこう。

get_k_nums(X, 0, 0).
get_k_nums([X|Xs], C, Sum) :-
  C1 is C - 1,
  get_k_nums(Xs, C1, Nextsum),
  Sum is Nextsum + X.

特に、 『get_k_nums(Xs, C1, Nextsum)』と『Sum is Nextsum + X.』の順番にとても違和感。
色々トライして、エラーになる原因を考えて(AtCoderのコードテストだとエラー原因がちゃんと出ず)、とやってきたから今は少し分かるようになってきましたが、取り組み始めた段階では全く上手くいかず・・・

そして、最終的にはWikipediaのPrologのページ(めちゃくちゃ例が充実している)を参照して挿入ソートを書き、無事ACとなりました。。。
コードはこんな感じ。

get_string(String) :-
  current_input(Input),
  read_string(Input, ' \n', '', _, String).
 
get_number(Number) :-
  get_string(String),
  number_string(Number, String).
 
insert_sort(L1,L2) :-
        insert_sort(L1,[],L2).
 
insert_sort([],L,L).
insert_sort([A|R],L1,L) :-
        insert(A,L1,L2),
        insert_sort(R,L2,L).
 
insert(A,[],[A]).
insert(A,[B|R],[A,B|R]) :-
        A @=< B,!.
insert(A,[B|R1],[B|R2]) :-
        A @> B,
        insert(A,R1,R2).
 
get_numbers(0, []).
get_numbers(N, [X|Xs]) :-
  get_number(X),
  N1 is N - 1,
  get_numbers(N1, Xs).
 
get_k_nums(X, 0, 0).
get_k_nums([X|Xs], C, Sum) :-
  C1 is C - 1,
  get_k_nums(Xs, C1, Nextsum),
  Sum is Nextsum + X.
 
main :-
  get_number(N),
  get_number(K),
  get_numbers(N, A),
  insert_sort(A, B),
  get_k_nums(B, K, Ans),
  write(Ans).
 
:- main.

ちなみに、かかった計算時間は89ms。計算速度自体は問題なさそうです。


まだまだ上手く書けないですが、たまにはPrologにも取り組んで行きたいところです。
まずは自分がこれまで書いてきたSchemeのコードをPrologに直していくところからでしょうか。もしナイスな勉強方法や、Prologゆえにキレイに解けるAtCoderの問題があればご教示いただけると幸いです。

マイナー言語でAtCoder解く、頑張るぞ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!