2021/12/02の回です。
前日は、Nerves界で大活躍の@mnishiguchiさんによる「ElixirでEnumを使わずEnumする」でした。
本日、私もEnumの話をします。
はじめに
- Elixirを楽しんでいますか
- この記事は、**AtCoder**のB - FizzBuzz Sum問題を題材に、Elixirでどのように解くのかを説明します
- **AtCoder**は、オンラインで参加できるプログラミングコンテスト(競技プログラミング)のサイトです。リアルタイムのコンテストで競い合ったり、約3000問のコンテストの過去問にいつでも挑戦することが出来ます。
対象
- これからElixirをはじめてみようという方に向けて書いています
- すでに、ぼくは/私は/俺は/アタイはAlchemistだよ、という方には簡単すぎる内容ですし、説明がくどいところが多々あります
- そういう方は、初心のころを思い出していただいて、「初心者へ向けて書くのなら、○○の説明を追加したらいいとおもうよ」という編集リクエストにてご批正を賜われば幸甚です
Elixir
- |>でスイスイ、プログラミングしていくことができる素敵なプログラミング言語です
- さっそくプログラムの例を示します
-
Qiita APIを使わせていただいて、
Elixir
タグがついた最新の記事を20件取得しています - ここでは雰囲気をつかんでいただければ大丈夫です
- どうでしょうか
Mix.install([{:jason, "~> 1.2"}, {:httpoison, "~> 1.8"}])
"https://qiita.com/api/v2/items?query=tag:Elixir"
|> URI.encode()
|> HTTPoison.get!()
|> Map.get(:body)
|> Jason.decode!()
|> Enum.map(& Map.take(&1, ["title", "url"]))
インストール
- ワクワクしてきたあなたはきっとよき$\huge{Alchemist}$になれるでしょう
- 早速、イゴかしてみましょう1
- まずはElixirのインストールが必要です
- Windows
- macOS
- お節介がすぎるかもしれませんが、エディタはVisual Studio Codeがオススメです
- オススメのプラグインはElixirLS: Elixir support and debuggerです
Run
-
iex
コマンドが使えるようになっています- Elixir's interactive shell.
- Elixirは1.12以上を使ってください
$ iex
Erlang/OTP 24 [erts-12.1.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit]
Interactive Elixir (1.12.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
- さきほどのプログラム例をコピペしてみてください
- きっと素敵な記事たちと出会えることでしょう
B - FizzBuzz Sum問題
- さていよいよ本題です
- **AtCoder**のB - FizzBuzz Sum問題を解いてみます
- 問題文はリンク先をご参照ください
- 例: 入力が15の場合、FizzBuzzの列は、
1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz
となり、数字だけを足し算して、60が答え
- 例: 入力が15の場合、FizzBuzzの列は、
プロジェクトをつくる
$ mix new fizz_buzz_sum
- どぅわーっとファイルがたくさんできます
- 最初は面食らうかもしれませんが、そのうち景色にみえてきます
- そういうものだとおもってください
- さっそくテストをしてみましょう
$ cd fizz_buzz_sum
$ mix test
Compiling 1 file (.ex)
Generated fizz_buzz_sum app
..
Finished in 0.06 seconds (0.00s async, 0.06s sync)
1 doctest, 1 test, 0 failures
@doc """
Hello world.
## Examples
iex> FizzBuzzSum.hello()
:world
"""
def hello do
:world
end
このコメントに見えるところが実は、Doctestsと呼ばれるもので、テストになっています。これを使ってプログラムを書いていきます。
fizz_buzz/1関数
- まずは
fizz_buzz/1
関数を作ります -
/1
は引数が1個の意味です
@doc """
fizz_buzz
## Examples
iex> FizzBuzzSum.fizz_buzz(1)
1
iex> FizzBuzzSum.fizz_buzz(2)
2
iex> FizzBuzzSum.fizz_buzz(3)
"Fizz"
iex> FizzBuzzSum.fizz_buzz(4)
4
iex> FizzBuzzSum.fizz_buzz(5)
"Buzz"
iex> FizzBuzzSum.fizz_buzz(15)
"FizzBuzz"
"""
def fizz_buzz(n) do
if rem(n, 3) == 0 and rem(n, 5) == 0 do
"FizzBuzz"
else
if rem(n, 3) == 0 do
"Fizz"
else
if rem(n, 5) == 0 do
"Buzz"
else
n
end
end
end
end
- rem/2は、整数同士の割り算の余りを返してくれます
-
mix test
してみましょう- パスします
- イゴいてはいますが、実はElixirっぽくないです
- はじめての方にはなんのことだかわからないとおもいますのでElixirっぽい書き方とはどういうことなのかを実際に書き換えたものを示します
- ここからというかちょっと前からの説明はギアがチェンジしたというか、説明が大雑把になってきたというか、いろいろ説明を端折っています
- 「細かいことはいいんです、四の五の言わずにまずはイゴかしてみましょう」
- 感じてください
- 君はコスモを感じたことがあるか
def fizz_buzz(n) do
do_fizz_buzz(n, rem(n, 3), rem(n, 5))
end
defp do_fizz_buzz(_n, 0, 0), do: "FizzBuzz"
defp do_fizz_buzz(_n, 0, _), do: "Fizz"
defp do_fizz_buzz(_n, _, 0), do: "Buzz"
defp do_fizz_buzz(n, _, _), do: n
- テストがあることで安心して書き換えることができます
- Pattern matchingと呼ばれる書き方を使い、if/2を排除すると、グッとElixirっぽい書き方になります
fizz_buzz_sum/1関数 -- その1
- 続いて与えたれたN項目までに含まれる数の和を計算する
fizz_buzz_sum/1
関数を作ります - ListやRangeなどEnumerablesを操作する関数が集まったモジュールをElixirでは、Enumモジュールと言います
- AtCoderの問題は、このEnumモジュールを使うか前日の@mnishiguchi さんの記事のように再帰を使うかすると解答を導けることが多いです
- この記事では、Enumモジュールを使って解く方法を説明します
@doc """
fizz_buzz_sum
## Examples
iex> FizzBuzzSum.fizz_buzz_sum(15)
60
iex> FizzBuzzSum.fizz_buzz_sum(1000000)
266666333332
"""
def fizz_buzz_sum(n) do
1..n
|> Enum.map(&fizz_buzz/1)
|> Enum.filter(&is_integer/1)
|> Enum.sum()
end
-
1..n
はRangeです-
[1,2,3,...,n]
みたいな〜 ものだとおもってください
-
-
|>
- 前の計算結果を次の関数の第1引数に入れてくれます
-
Enum.map/2
- 与えたれたenumerable(リストやRangeやMap)の各要素に、第2引数で指定した関数を用いて演算を施して結果を返してくれます
- 与えたれたenumerableの要素数と同じ要素数のenumerableが返ってきます
-
Enum.filter/2
- 与えたれたenumerable(リストやRangeやMap)の各要素に、第2引数で指定した関数を用いて、truthyな要素のみを残したenumerableを返します
- truthyとは、
nil
とfalse
以外です - 与えられたenumerableの要素数がNだとすると、結果は0個〜N個のenumerableになります
-
Enum.sum/1
- 与えられたenumerableの各要素を足し算した結果を返します
def fizz_buzz_sum(n) do
Enum.sum(Enum.filter(Enum.map(1..n, &fizz_buzz/1), &is_integer/1))
end
どちらがお好みでしょうか。
人の好みはそれぞれですしあなたの好みまでかえる気はありませんが、|>を使った書き方の方を美しいと感じる方は、きっとよき$\huge{Alchemist}$になれるでしょう
fizz_buzz_sum/1関数 -- その2
- B - FizzBuzz Sum問題は、その1のプログラムでパスできます
- mapして、filterして、足し算してという3ステップを踏んでいることが気になる方がいらっしゃるでしょう
- きっとよき$\huge{Alchemist}$になれるでしょう
-
Enum.reduce/3を使った書き方をご紹介しておきます
- 専門家の間では畳み込みと呼ばれます
- @kuroda@github さんの「Elixir実践ガイド -- インプレス」に詳しく解説されています
def fizz_buzz_sum(n) do
1..n
|> Enum.reduce(0, fn i, acc ->
fizz_buzz(i)
|> to_i()
|> Kernel.+(acc)
end)
end
defp to_i("Fizz"), do: 0
defp to_i("Buzz"), do: 0
defp to_i("FizzBuzz"), do: 0
defp to_i(i), do: i
- Enum.reduce/3を使ったほうがループの回数が減るので、AtCoderの提出環境においては、約100ms弱速くなります
main/0関数
- 入力を読み取って |> 計算して |> 出力する というのが競技プログラミングのスタイルです
- その方法については、「AtCoderをElixirでやってみる」 に詳しく書いています
- プログラム全体を示します
defmodule FizzBuzzSum do
def main do
IO.read(:line)
|> String.trim()
|> String.to_integer()
|> fizz_buzz_sum()
|> IO.puts()
end
@doc """
fizz_buzz
## Examples
iex> FizzBuzzSum.fizz_buzz(1)
1
iex> FizzBuzzSum.fizz_buzz(2)
2
iex> FizzBuzzSum.fizz_buzz(3)
"Fizz"
iex> FizzBuzzSum.fizz_buzz(4)
4
iex> FizzBuzzSum.fizz_buzz(5)
"Buzz"
iex> FizzBuzzSum.fizz_buzz(15)
"FizzBuzz"
"""
def fizz_buzz(n) do
do_fizz_buzz(n, rem(n, 3), rem(n, 5))
end
defp do_fizz_buzz(_n, 0, 0), do: "FizzBuzz"
defp do_fizz_buzz(_n, 0, _), do: "Fizz"
defp do_fizz_buzz(_n, _, 0), do: "Buzz"
defp do_fizz_buzz(n, _, _), do: n
@doc """
fizz_buzz_sum
## Examples
iex> FizzBuzzSum.fizz_buzz_sum(15)
60
iex> FizzBuzzSum.fizz_buzz_sum(1000000)
266666333332
"""
def fizz_buzz_sum(n) do
1..n
|> Enum.reduce(0, fn i, acc ->
fizz_buzz(i)
|> to_i()
|> Kernel.+(acc)
end)
end
defp to_i("Fizz"), do: 0
defp to_i("Buzz"), do: 0
defp to_i("FizzBuzz"), do: 0
defp to_i(i), do: i
end
- 提出の際には、モジュール名を
Main
として提出してください - パスします
整形
- ソースコードはキレイにしておきましょう
- 整形してくれます
$ mix format
Wrapping up
- Enjoy Elixir
- Elixirのプログラムには、|>がよくでてきます
- |>を使ってスイスイ、プログラミングしていくことができます
- Elixirをはじめたばかりの方は、Enumモジュールを眺めておくとよいです
- この記事を読んでくださったあなたは、きっとよき$\huge{Alchemist}$になれるでしょう
コミュニティ
-
elixir.jp Slack workspaceに参加してみてください
- マヂ、やさしい人ばっかりのコミュニティ
- あなたの困ったをきっと解決してくれるでしょう
- NervesJP Slack workspaceでは、NervesやIoTが好きな愉快なfolksたちがあなたの訪れを歓迎します
- たくさんのコミュニティがあります
- @kn339264 さん作の素敵な資料をご紹介します
- Elixirコミュニティ の歩き方〜国内オンライン編〜
(@piacerex さん作 )
もっとElixirのことを知りたい方へオススメの書籍
- プログラミングElixir(第2版) -- オーム社
- Elixir実践ガイド -- インプレス
- アルケミスト − 夢を旅した少年 -- KADOKAWA
この記事を最後まで読んでくださったあなたは、きっとよき$\huge{Alchemist}$になれるでしょう
明日は、@iyanayatudazeさんによる「iexで関数のドキュメントを調べる方法 他3本」です。
お楽しみに〜〜〜
-
「早速動かしてみましょう」の意。おそらく、西日本の方言、たぶん。NervesJPではおなじみ。少し古いオートレースの映像ですが、実況アナウンサーが「針2イゴきます」とはっきり言っています。https://autorace.jp/netstadium/SearchMovie/Movie/iizuka?date=2017-01-04&p=5&race_number=11&pg= ↩
-
大時計の針のこと。針がイゴいてある地点まで到達すると選手はスタートを切って良い発走の合図。針がイゴきはじめると(おそらく)選手は緊張するし、スタートはその後のレース展開に大きく影響するので、車券を握りしめている観客たちがもっとも緊張する瞬間であるため、先の尖った鋭いものを連想させる針は緊張の暗喩としても言い得て妙。 ↩