8
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

yukicoder(競プロ)でElixirの修行をする

はじめに

最近、Elixirの文法をyukicoder(競技プログラミングサイト)の問題を解きながら学んでいます。
提出するプログラムの形式やElixirにおける標準入出力のパターンについて、まとめてみました。

競技プログラミングとは

競技プログラミングにもいろいろ種類はあるのですが、代表的なのはアルゴリズムに関する問題を解くプログラムを作成するというものです。
問題ごとに小規模なプログラムを独立に作成するので、新しく学ぶ言語の基本的な文法を習得するにはうってつけです。
詳しくは、以下のページが参考になります。
競技プログラミングとは?メリットや初心者にもおすすめな理由も解説

Elixirが使える競技プログラミングサイト

残念ながら、Elixirが使える競技プログラミングサイトは少ないです。
国内サイトだと、yukicoder 、海外サイトだとHackerRankでElixirを使えます。
当然、Project EulerGoogle Code Jamのようにローカルで実行させた結果を提出する形式のサイトでも使えます。
今回はタイトルの通り、yukicoderの問題をElixirで解く方法について説明します。

提出するプログラムの形式

ヘルプ - yukicoder の説明の通り、 elixir -e Main.main コマンドにより、提出されたElixirプログラムは実行されます。
したがって、以下の形式がyukicoderにおけるテンプレートになります。

defmodule Main do
  def main do
  # ここに問題を解くプログラムを記述する
  end
end

入力

標準入力からパラメータを受け取るので、IO.gets/2 を使います。
IO.gets/2 の必須引数 prompt には空文字列を指定してください。
また、IO.gets/2 で得た値は文字列なので必要に応じて型変換もしてあげます。
そして、 IO.gets/2 で得た文字列には改行文字が含まれるので要注意です。
末尾の改行文字の除去にはString.trim/1を使います。

単一の値を入力

変数 a に単一の値を入力するパターンです。

入力される値が文字列の場合

IO.gets("") で得た入力値を String.trim/1 にパイプします。

a = IO.gets("") |> String.trim

IO.gets/2 の引数の括弧は省略できないことに注意してください。つまり、以下のように記述できません。

a = IO.gets "" |> String.trim # NG!

入力される値が整数の場合

String.to_integer/1 を使って、文字列として受け取った入力値を整数に変換します。

a = IO.gets("") |> String.trim |> String.to_integer

パイプ演算子が気持ちよいですね。

入力される値が浮動小数点数の場合

String.to_float/1 を使って、文字列をとして受け取った入力を浮動小数点数に変換します。

a = IO.gets("") |> String.trim |> String.to_float

定数個の値を入力

空白区切りで定個数の値が入力されるケースです。
例えば、 A B C のような入力形式です。
String.split/1 を使い、空白で値を分割します。

入力値が文字列の場合

String.split/1 の戻り値はリストなので、パターンマッチングを利用して入力値をバインドさせます。

[a, b, c] = IO.gets("") |> String.trim |> String.split

入力値が整数(浮動小数点数)の場合

String.split/1 により分割した文字列のリストに対して、 Enum.map/2 の引数にString.to_integer/1 または String.to_float/1 を指定し、
リストの要素である各文字列を整数(浮動小数点数)に変換します。
以下はすべての入力値が整数の場合の例です。

[a, b, c] = IO.gets("") |> String.trim |> String.split |> Enum.map(&to_integer/1)

可変個の値を入力

最初に与えられた整数 N のあとに、 N 個の値が入力されるパターンです。
例えば、

3
foo
bar
baz

のような入力形式です。
このような形式の場合、リストの内包表記を使うと便利です。

入力値が文字列の場合

リストの内包表記を用いて、 IO.gets("") |> String.trim により 値をn 回受け取ります。

n = IO.gets("") |> String.trim |> String.to_integer
a = for _ <- 1..n, do: IO.gets("") |> String.trim

1..n1 から n までのrangeです。 n 回繰り返すために使います(n 個の要素を含むrangeであることがポイント。各値は興味なし)。
リストの内包表記の束縛変数が _ となっていますが、これはrangeの各値に興味がないことを表します。

入力値が整数(浮動小数点数)の場合

String.trim/1 のあとに String.to_integer/1 または String.to_float/1 をパイプするだけです。

n = IO.gets("") |> String.trim |> String.to_integer
a = for _ <- 1..n, do: IO.gets("") |> String.trim |> String.to_integer

出力

標準出力に解を出力するので、IO.puts/2 を使います。

単一の値を出力

最もよくあるパターンです。例えば、変数 ans に格納されている値を出力するならば、

IO.puts ans

と書きます。

定数個の値を出力

3個の値を空白区切りで出力する場合などです。
String interpolation を使うとよさそうです。
例えば、変数 x, y, z に格納されている値を空白区切りで出力するならば、

IO.puts "#{x} #{y} #{z}"

と書けます。

可変個の値を出力

リストに格納されている値を出力するパターンです。Enum.join/2 が便利です。

リスト xs の各要素を改行区切りで出力するならば、

IO.puts Enum.join(xs, "\n")

と書けます。

同様に、リスト xs の各要素を空白区切りで出力するならば、

IO.puts Enum.join(xs, " ")

と書けます。

例: No.712 赤旗を解いてみる

yukicoderのNo.712 赤旗をElixirで解いてみます。

プログラムの形式

まず、提出するプログラムの形式の通りにテンプレートを写経します。

defmodule Main do
  def main do
  # ここに問題を解くプログラムを記述する
  end
end

入力

問題文のページにもある通り、入力形式は以下です。

N M
A_1
...
A_N

まず、 定数個の整数を入力する例を参考に、 N, M を以下のように受け取ります。

[n, m] = IO.gets("") |> String.trim |> String.split |> Enum.map(&String.to_integer/1)

ここで、 問題文に合わせて nm を大文字にしないように注意してください。
Elixirでは大文字から始まる識別子を変数に対して与えられないのです。

次に、可変個の文字列を入力する例を参考に、 A_1 から A_n を以下のように受け取ります。

a = for _ <- 1..n, do: IO.gets("") |> String.trim

入力はこれでおしまいです。ここまでをまとめると、

defmodule Main do
  def main do
    # 入力
    [n, m] = IO.gets("") |> String.trim |> String.split |> Enum.map(&String.to_integer/1)
    a = for _ <- 1..n, do: IO.gets("") |> String.trim
    # 問題を解くプログラムを記述する
    # 解を出力するプログラムを記述する
  end
end

例えば、入力サンプル1である以下の入力に対して、 n = 3, m = 3, a = ["WRW", "RWR", "WRW"] がバインドされます。

3 3
WRW
RWR
WRW

出力

出力は整数1個なので、

IO.puts ans

とするだけです。 ここで、 ans には出力する整数が格納されているものとします。
したがって、提出するプログラムは以下となります。

defmodule Main do
  def main do
    # 入力
    [n, m] = IO.gets("") |> String.trim |> String.split |> Enum.map(&String.to_integer/1)
    a = for _ <- 1..n, do: IO.gets("") |> String.trim    

    # 問題を解くプログラムを記述する
    ans = # これは自力で書いてみましょう。

    # 出力
    IO.puts ans    
  end
end

この問題の ans の求め方については本記事では解説しないでおきます。

おわりに

競技プログラミングの問題をたくさん解くと、いろいろな構文やライブラリの関数を知ることができて有益です。
一日一問やるだけでもだいぶ力がつくんじゃないかと。
問題を解く際には、美しく書けないかを調べながら解くと、より多くの知識が得られるのではないかと思います(何でもかんでも自力でゴリゴリ書かない)。

ちなみに、yukicoderでElixirコードを提出すると、実行時間がとても大きいので、制限時間が厳しい問題は通せないと思われます...

リンク

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
8
Help us understand the problem. What are the problem?