POH7をErlangで解いた話

タイトルの通り[POH7](https://paiza.jp/poh/ando?read=true)をErlangで全部解いたのでコードを公開しようと思った次第です。

Erlangで競技プログラミングっぽいことをすることが初めてでいろいろ戸惑いましたが
なんとか問題をすべて解くことができました。
paizaの環境ではErlangは起動に1.4秒程度かかるようで平均実行時間が1.5秒程度になりました。

追記(12月21日 01:45)
追加されていたando16, ando17を追加

コード上ではmodule名がando*になっていますが、paizaに提出する時はmainにしなければいけません。

## コード
### Ando2
```erlang:ando2.erl
-module(ando2).
-export([main/1]).

main([_]) ->
  N = to_i(io:get_line("")),
  io:format("~p~n", [string:copies("Ann", N)]),
  init:stop().

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
"Ann"という文字列をN回繰り返すだけなのでstring:copiesを使うだけでお終いです。

### Ando4
```erlang:ando4.erl
-module(ando4).
-export([main/1]).

main([_]) ->
  A = to_i(io:get_line("")), 
  B = to_i(io:get_line("")), 
  io:format("~p~n", [A + B]),
  init:stop().

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
これはA + Bするだけですね。

### Ando5
```erlang:ando5.erl
-module(ando5).
-export([main/1]).

main([_]) ->
  NS = [io:get_line("") || _ <- lists:seq(1, 5)],
  YC = count(NS, "yes\n"),
  if
    YC > 2 -> io:format("yes~n");
    true -> io:format("no~n")
  end,
  init:stop().

count([], _) -> 0;
count([H|T], X) when H == X -> count(T, X) + 1;
count([_|T], X) -> count(T, X).
```
これはyesかnoという文字列が5回与えられて多い方を出力する問題です。
それと定数回処理を回したいときはどうするのがベストなのでしょうか。
(lists:seqを使って対処してる）
countなんて自作してますが何か良い方法があれば教えて下さい。
(書いてて気づいたのですが、```length(lists:filter(ほげ))```で終わりな気がします。。。)
(そもそも```count([X|T], X) -> count(T, X) + 1```のほうが簡潔ですね)

### Ando6
```erlang:ando6.erl
-module(ando6).
-export([main/1]).

main([_]) ->
  N = to_i(io:get_line("")),
  S = string:join([to_s(X) || X <- lists:seq(N, 0, -1)], "\n"),
  io:format("~s!!~n", [S]),
  init:stop().

to_s(T) -> io_lib:format("~p", [T]).
to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
これはカウントダウンして最後に!!をつける問題です。
to_sなんていう関数を作りましたがinteger_to_listがこれと同じ動きをしそうですね。
あまりにも無知。

### Ando7
```erlang:ando7.erl
-module(ando7).
-export([main/1]).

main([_]) ->
  [C1, P1] = [to_i(X) || X <- string:tokens(io:get_line(""), " ")],
  [C2, P2] = [to_i(X) || X <- string:tokens(io:get_line(""), " ")],
  if
    C1 * P2 > C2 * P1 -> io:format("1~n");
    true -> io:format("2~n")
  end,
  init:stop().

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
コストパフォーマンスが高いほうを出力する問題です。
splitはないのかと絶望していましたがErlangではstring:tokensなんですね。勉強になりました。
第3引数は複数のデリミタを指定することができるのでchars()でマッチしていて、
文字リテラル(? $から始まる一文字のatom)は使えないというところにハマりました。

### Ando9
```erlang:ando9.erl
-module(ando9).
-export([main/1]).

main([_]) ->
  N = to_i(io:get_line("")),
  SS = [string:strip(io:get_line(""), right, $\n) || _ <- lists:seq(1, N)],
  io:format("~p~n", [string:join(SS, "_")]),
  init:stop().

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
入力された文字列を```_```で結合して出力する問題です。
string:join
以上

### Ando10
```erlang:ando10.erl
-module(ando10).
-export([main/1]).

main([_]) ->
  io:format("~p~n", [fact(to_i(io:get_line("")))]),
  init:stop().

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.

fact(0) -> 1;
fact(N) -> fact(N - 1) * N.
```
階乗を出力する問題です。
factを自分で定義するか、```lists:foldl(fun(X,Acc) -> X * Acc end, 1, lists:seq(1, N))```するかのどちらかですね。
前者の方がシンプルに書ける気がしたのでこちらで書きました。

### Ando11
```erlang:ando11.erl
-module(ando11).
-export([main/1]).

main([_]) ->
  N = to_i(io:get_line("")),
  NG = in_grid(N),
  M = to_i(io:get_line("")),
  MG = in_grid(M),
  {Y, X} = search(NG, MG),
  io:format("~p ~p~n", [Y, X]),
  init:stop().

in_grid(N) ->
  [[to_i(T) || T <- string:tokens(io:get_line(""), " ")] || _ <- lists:seq(1, N)].

search(NG, MG) ->
  N = length(NG),
  M = length(MG),
  [{X, Y}|_] = lists:dropwhile(fun({BX, BY}) -> not match(NG, MG, BX, BY) end, [{X + 1, Y + 1} || X <- lists:seq(0, N - M), Y <- lists:seq(0, N - M)]),
  {Y - 1, X - 1}.

match(NG, MG, X, Y) ->
  M = length(MG),
  MG == [lists:sublist(S, X, M) || S <- lists:sublist(NG, Y, M)].

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
模様付きの正方形の中から、別の模様付きの正方形を見つけて出現座標を出力する問題です。
取りうる座標をすべて列挙して、```lists:dropwhile```で先頭要素を取得する形です。
一致しているかは```lists:sublist```を用いて探す対象の正方形を切り出し、listのまま比較しました。
```lists:sublist```が1-indexedなところにハマりました。

### Ando12
```erlang:ando12.erl
-module(ando12).
-export([main/1]).

main([_]) ->
  [X, Y, Z, N] = [to_i(T) || T <- string:tokens(io:get_line(""), " ")],
  {XS, YS} = readCutting(N, [X], [Y]),
  io:format("~p~n", [lists:min([AX * AY || AX <- diffList(XS), AY <- diffList(YS)]) * Z]),
  init:stop().

diffList(L) ->
  {X, _} = lists:mapfoldl(fun(T, Acc) -> {T - Acc, T} end, 0, L),
  X.

readCutting(0, XS, YS) -> {lists:sort(XS), lists:sort(YS)};
readCutting(N, XS, YS) ->
  [D, A] = [to_i(T) || T <- string:tokens(io:get_line(""), " ")],
  if
    D == 0 -> readCutting(N - 1, [A|XS], YS);
    true -> readCutting(N - 1, XS, [A|YS])
  end.

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
X * Y * Zの直方体に対してY軸とX軸に対して並行に切断するクエリが飛んで来るので、
クエリを処理し終わったあとの直方体の最小の体積を出力する問題です。
この問題はZに関してはほとんど考えなくて構いません。X軸とY軸の切られた場所をソートして
差分をみてすべての組み合わせから最小を求めると終わりです。
（差分の最小同士を掛け合わせるだけで良さそうな気がしてきました）
ソートされたリストから差分のリストを作る上で```lists:mapfoldl```が非常に便利でした。

### Ando13
```erlang:ando13
-module(ando13).
-export([main/1]).

main([_]) ->
  N = to_i(io:get_line("")),
  MOD = pow(10, 9),
  {A, Two, Five} = lists:foldl(fun(X, {Prod, T2, T5}) ->
                                   {Y, C2} = divo(X, 2),
                                   {Z, C5} = divo(Y, 5),
                                   {(Prod * Z) rem MOD, T2 + C2, T5 + C5}
                               end, {1, 0, 0}, lists:seq(1, N)),
  io:format("~p~n", [lists:foldl(fun(X,Acc) -> (X * Acc) rem MOD end, A, lists:duplicate(Two - Five, 2))]),
  init:stop().

divo(X, N) -> divo(X, N, 0).
divo(X, N, C) when X rem N == 0 -> divo(X div N, N, C + 1);
divo(X, _, C) -> {X, C}.

pow(_, 0) -> 1;
pow(X, N) -> X * pow(X, N - 1).

to_i(S) ->
  {I, _} = string:to_integer(S),
  I.
```
Nの階乗の下の桁から連続する0を取り除いた下8桁を出力する問題です。
Nが大きいためまともに階乗を計算すると制限時間に間に合いません。
そこで下の桁から連続する0を取り除くという点と下8桁という点に注目します。
下の桁から連続する0を取り除くというのは10で割り切れるうちは10で割り続けると同義です。
10で割り続けながら階乗を計算していきます。計算途中でも下8桁以上は計算する必要がないので
10^9で剰余を取ることで下8桁のみを求めることができます。
10^9*10^9程度の小さい数字だとNが大きくてなり計算回数が増えても高速に解を求めることができます。

### Ando16
```erlang:ando16.erl
-module(ando16).
-export([main/1]).

main([_]) ->
  N = read_int(),
  _ = read_int(),
  X = [to_i(Token) || Token <- string:tokens(io:get_line(""), " ")],
  _ = read_int(),
  Y = [to_i(Token) || Token <- string:tokens(io:get_line(""), " ")],
  All = sets:from_list(lists:seq(1, N)),
  Have = sets:from_list(X),
  Sell = sets:from_list(Y),
  Need = sets:subtract(All, Have),
  Buy = sets:intersection(Sell, Need),
  BuySize = sets:size(Buy),
  if
    BuySize == 0 ->
      io:format("None~n");
    true ->
      io:format("~s~n", [string:join([integer_to_list(V) || V <- lists:sort(sets:to_list(Buy))], " ")])
  end,
  init:stop().

read_int() -> to_i(io:get_line("")).
to_i(S) -> element(1, string:to_integer(S)).
```
持っている本と中古で売っている本が与えられて、買うべき本を出力する問題です。
集合の演算を用いることで簡単に書くことができます。
すべての本の集合から持っている本の集合を引きます。これで持っていない本の集合を求めました。
売っている本と持っていない本の集合が買うべき本の集合であることがわかるのでそれを出力します。
ここでハマったのが```sets:to_list(Set)```が返すリストが昇順じゃない点です。辛い。

### Ando17
```erlang:ando17.erl
-module(ando17).
-import(string, [copies/2]).
-export([main/1]).

main([_]) ->
  N = to_i(io:get_line("")),
  M = to_i(io:get_line("")),
  io:format("~s~n", [string:substr(copies(string:concat(copies("R", N), copies("W", N)), 10), 1, M)]),
  init:stop().

to_i(S) -> element(1, string:to_integer(S)).
```
RをN個、WをN個交互に並べて合計の長さがMになった文字列を出力する問題です。
問題の制約が小さいことを利用して、RをN個、WをN個繰り返した文字列を10回繰り返し切り出すことで
この制約下では正しい答えを返すことができます。
初めて使うべきではないと言われている```-import```を使ったのですが、
小さい使い捨てのコードであれば使っても良いのかなと感じました。

### 終わりに
Erlang初心者が書いたコードなので間違っているところだったりアドバイスが有ればぜひお願いします。