Edited at

Elixir勉強メモ

More than 1 year has passed since last update.

※この記事は勉強メモです。

基本メモなので解説とかそんなんじゃないです。

【fukuoka.ex】学生エンジニアがElixirに興味を持ったきっかけと初心者にオススメの勉強法でRuby風味らしいとのことでしたので、やってみることにしました。

hackerrankで(結構難しいのでわからないときは普通にdiscussion見てますし、というか↑の記事に標準入力乗ってるじゃん)。


Solve Me First FP


SolveMeFirstFP.exs


defmodule Solution do
def run() do
a = IO.gets ""
b = IO.gets ""
{ a, _ } = Integer.parse a
{ b, _ } = Integer.parse b
IO.puts a + b
end
end

Solution.run



  • IO.gets ""


    関数が実行されると値の入力待ちが発生する。

    この時、aとなる数値を代入する。(その次がb)


  • Integer.parse "3.14"


    binary を Integer に変換する。

    上記なら {3, ".14"}


  • IO.puts a + b


    a+bの結果を出力。


  • { a, _ } = ...


    パターンマッチング。

    Rubyで言えば代入に変わるようなもの?。

    Elixir のパターンマッチを攻略しよう



Hello World


HelloWorld.exs

defmodule Solution do

def run() do
IO.puts "Hello World"
end
end

Solution.run



Hello World N Times


HelloWorldNTimes.exs

defmodule Solution do

def run() do
a = IO.gets ""
{ a, _ } = Integer.parse a
for _ <- 1..a, true, do: IO.puts "Hello World"
end
end
Solution.run


  • forではループ文字は使わないのでnとかは定義しない(errが出る)

    もっといい書き方あるやろ..


List Replication


ListReplication.exs


defmodule Solution do
def times(n, element) do
if (n > 1) do
IO.puts element
times(n-1, element)
else
IO.puts element
end
end

def elements(array) do
[ head | tail ] = array
Enum.map(tail, fn(x) -> times(head, x) end)
# List.first(array)
end

def main() do
IO.read(:stdio, :all)
|> String.split
|> Enum.map(&String.to_integer(&1))
|> elements
end
end

Solution.main



  • IO.read(:stdio, :all)

    入力を全て受け取る...でいいはず(調べたけどなんかあんま出てこんかった。別個ではあるし、多分そんな感じの内容でいいはず)。


input

 1  

2
3
4
5

なら "1\n2\n3\n4\n5\n"を取得する。


  • |>(パイプライン演算子)


    |>の左側が右側の関数の第一引数になる。

    今回の例で言えば、IO.read(:stdio, :all)("1\n2\n3\n4\n5\n")を引数にString.splitを呼び出すということ。


  • String.split


    文字列を空白で分割してる。

    リファレンス読んだ限りだとUnicode空白オカレンスとあるが、よくわからない。

    とりあえず \nでも分割できる。


  • Enum.map


    特に説明は不要ですかね。配列の各値に対して処理を実行する。


  • &String.to_integer(&1)


    謎ですね?

    もう少しだけ突っ込むと、

    Enum.map(["1", "2", "3", "4", "5"], fn(a) -> String.to_integer(a) end)

    こうらしいです(mapの第一引数が一つずつaに代入されてく..)。

    で、

    fn(a) -> String.to_integer(a) en

    こうすると読みやすくなりますね。関数の括りを &()、引数を順に &1,&2..としただけとのことです。


  • [ head | tail ]


    [head|tail] = [1,2,3,4,5]

    head = 1, tail = [2,3,4,5]

    先頭要素とそれ以外に分ける。



Filter Array


FilterArray.exs


defmodule Solution do
def delim(n, element) do
if (element < n) do
IO.puts element
end
end

def elements(array) do
[ head | tail ] = array
Enum.map(tail, fn(x) -> delim(head, x) end)
end

def main() do
IO.read(:stdio, :all)
|> String.split
|> Enum.map(&String.to_integer(&1))
|> elements
end
end

Solution.main



Filter Positions in a List


FilterPositionsinaList.exs


defmodule Solution do
def even_num(k, v) do
if (0 == rem(k, 2)) do
IO.puts v
end
end

def main() do
IO.read(:stdio, :all)
|> String.split
|> Enum.map(&String.to_integer(&1))
|> Enum.with_index(1) |> Enum.map(fn { k, v } -> even_num(v, k) end)
end
end

Solution.main



  • Enum.with_index(1)

    配列に対して、括弧内指定の数からのindexを与える。


before

8

15
22
1
10
6
2
18
18
1


after

[

{8, 1},
{15, 2},
{22, 3},
{1, 4},
{10, 5},
{6, 6},
{2, 7},
{18, 8},
{18, 9},
{1, 10}
]


  • Enum.map(fn { k, v } -> even_num(v, k) end


    ↑で整形したハッシュを引数にmapで関数を呼び出してる(実際のkey,valueが逆なので注意)。


  • rem(k, 2)


    あまりを求める。kを2で割ったあまりを求めてます。elixirには %2とかの表現はないとのこと。



Array Of N Elements


ArrayOfNElements.exs

defmodule Solution do

def main() do
a = IO.read(:stdio, :all)
{ a, _ } = Integer.parse a
IO.inspect for n <- 1..a, true, do: []++n
end
end

Solution.main



  • for n <- x..y, true, do: (処理)


    ループの返り値が配列になるのは僥倖でした。

    イミュータブルなので、どのように配列を作成するのかはよくわかりません。


  • []++n


    これもまだ未解明です。

    そもそもイミュータブルなのになぜ追加できるのか。オブジェクトが別なのか?そうか、連結して新しいリストを返してるのか!

    この辺はもうちょっと時間あるときに仕様を読みます。


Add new element to list

というか6つのうち2つのテストケースで失敗してた。

input 100, output 100, wrong answer

input 90, output 90, wrong answer

ってあったけど意味わからん。回答ないか探して突っ込んで走らせたけど、同じ。これなんだ?

→これわかる方いらっしゃったらコメントか何かいただければ幸いです。

Hackerrank-Elixir/array-of-n-elements.exs


Reverse a List


ReverseaList.exs

defmodule Solution do

def main() do
IO.read(:stdio, :all)
|> String.split
|> Enum.map(&String.to_integer(&1))
|> Enum.reverse |> Enum.map(&IO.inspect(&1))
end
end

Solution.main



  • Enum.reverse

    リストの反転

なんかこういう記事もあるみたいですがよくわからないのでとりあえず置いておきます


Sum of Odd Elements


SumofOddElements.exs

defmodule Solution do

def main() do
a = IO.read(:stdio, :all)
|> String.split
|> Enum.map(&String.to_integer(&1))
|> Enum.reject(&rem(&1, 2) == 0)
|> Enum.reduce(0, fn(x, acc) -> x + acc end)
IO.inspect a
end
end

Solution.main



  • Enum.reject


    条件に合う値を除外したリストを返す。

    今回なら各要素のうち偶数(2で割れる値)を除外しています。


  • Enum.reduce


    リストをまとめて単一の値を出力できる(今回はそのためのメソッドとして全数の足し算)。

    初期値を0として記載してなければ、最初の値が初期値になる。



List Length


ListLength.exs

defmodule Solution do

def main() do
a = IO.read(:stdio, :all)
|> String.split
|> Enum.reduce(0, fn(_, acc) -> acc + 1 end)
IO.inspect a
end
end

Solution.main


とか考えましたが、どう考えてもこうですね。


ListLength2.exs

defmodule Solution do

def main() do
IO.read(:stdio, :all)
|> String.split
|> length
|> IO.inspect
end
end

Solution.main



  • length

    リストサイズを返す。


Update List


UpdateList.exs

defmodule Solution do

def main() do
IO.read(:stdio, :all)
|> String.split
|> Enum.map(&String.to_integer(&1))
|> Enum.map(&abs(&1))
|> Enum.map(&IO.inspect(&1))
end
end

Solution.main



Evaluating e^x


Evaluate e^x for given values of x by using the above expansion for the first 10 terms.


これちょっとわかんなかった。いや、かけたことはかけたんだけど、なんか異様に長くなったというかなんかもう...あれ?みたいな感じになったからディスカッションで引っ張ってきた。


Evaluatinge^x.exs

defmodule Solution do

def factorial(x) do
case x do
0 -> 1
f -> f * Solution.factorial(x-1)
end
end
def ecounter(x) do
Enum.map(0..9, fn i -> :math.pow(x, i)/factorial(i) end)
|> Enum.sum
end
def read do
IO.read(:stdio, :line)
IO.stream(:stdio, :line)
|> Stream.map(&String.trim(&1))
|> Stream.map(&String.to_float(&1))
|> Stream.map(&Solution.ecounter(&1))
|> Enum.to_list
|> Enum.map(&Float.round(&1, 4))
|> Enum.map(&IO.inspect(&1))
end
end
Solution.read


  • IO.read IO.stream

    なんか新しいのきたな?とりあえず抜粋。


io.exs

    a = IO.read(:stdio, :line)

b = IO.stream(:stdio, :line)
IO.inspect a
IO.inspect b
# "4\n"
# %IO.Stream{device: :standard_io, line_or_bytes: :line, raw: false}

readに関してはバイト数単位での読み出しの様ですが引数の意味がよくわかりませんね。。


:stdioと書いた場合,実際にはメッセージをグループリーダーへ送っています.グループリーダーはSTDIOファイルディスクリプタへと書きこみます


どうやら Process.group_leaderこれと同じらしい。わからん。


:lineなら行全体、 :allなら全てを読みます。


こちらはそのまんま。

binread(device \ :erlang.group_leader(), chars_or_line)

Stream: 遅延実行Enum?とでも言えばいいのかな。返り値はStream構造体。


実際にデータに対して何か計算が即時に行われるのではなく将来適用される関数をこの構造体が保持している


とのことらしい。詳しくはこちら

(参考記事の)実装をコピーしてきました。


iex.exs

iex(3)> 1..1000000  \

...(3)> |> Stream.map(&("This is number #{&1}")) \
...(3)> |> Enum.take(2)
["This is number 1", "This is number 2"]


これから理解すべき大事な点は1,000,000個ものアイテムからなるレンジの変換を表現するのにStream.mapを使っていますが、たったの2回の計算しか実行していないということです。Enum.take関数は2つのアイテムしか要求していません。つまりストリームはこのレンジの最初の2つのアイテムにしか計算をしていないことになります。これは非常に大きな(あるいは無限の大きさの)データのセットに対する計算の表現を取り扱いやすい方法で行うのに非常に有益です。


確かにこれは素晴らしい。

ということは、IO.Streamの意味がだいたい想像つくけども、:allならともかく、:lineで使う必要があるのかは疑問。とりあえずこれ以上は後日。

別にHackerRankで使う分には:lineにする必要性はなさそう(入力した値全部使うし)

と、思ったが、どうやら何か違うのか?

IO.readとIO.streamを連続させてるのに何か秘密がある...?

話の流れとしてはあれだけど、↑読んでたら、知らないこと結構あったので貼っておく。


Elixir はコンパイルしてもしなくても実行できる。

コンパイルするファイルは拡張子を .ex にし、

コンパイルせずに実行するファイルは .exs にする慣習らしい。


Elixir で Hello world!


  • String.trim


    stripは非推奨。空白文字の削除。


  • :math.pow(x, i)

    xのi乗計算


  • factorial(i)


    再帰でn!部分を計算



別に終わりじゃないまとめ

えっ!?本当にRuby風味?という感じです。しかも雰囲気というかノリで書いてるので、わかってないで変な書き方してるとこいっぱいある気がします。

きっとRubyエンジニアとしての実力かElixirの練習が足りないのでしょう。

引き続きやってきます(随時更新)。

というかこういう回答って記事にしていいのかな...。何か問題あったら教えてください。消して個人で楽しむことにします。