※この記事は勉強メモです。
基本メモなので解説とかそんなんじゃないです。
【fukuoka.ex】学生エンジニアがElixirに興味を持ったきっかけと初心者にオススメの勉強法でRuby風味らしいとのことでしたので、やってみることにしました。
hackerrankで(結構難しいのでわからないときは普通にdiscussion見てますし、というか↑の記事に標準入力乗ってるじゃん)。
Solve Me First FP
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
defmodule Solution do
def run() do
IO.puts "Hello World"
end
end
Solution.run
Hello World N Times
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
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)
入力を全て受け取る...でいいはず(調べたけどなんかあんま出てこんかった。別個ではあるし、多分そんな感じの内容でいいはず)。
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
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
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を与える。
8
15
22
1
10
6
2
18
18
1
[
{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
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
これもまだ未解明です。
そもそもイミュータブルなのになぜ追加できるのか。オブジェクトが別なのか?そうか、連結して新しいリストを返してるのか!
この辺はもうちょっと時間あるときに仕様を読みます。
というか6つのうち2つのテストケースで失敗してた。
input 100, output 100, wrong answer
input 90, output 90, wrong answer
ってあったけど意味わからん。回答ないか探して突っ込んで走らせたけど、同じ。これなんだ?
→これわかる方いらっしゃったらコメントか何かいただければ幸いです。
Hackerrank-Elixir/array-of-n-elements.exs
Reverse a List
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
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
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
とか考えましたが、どう考えてもこうですね。
defmodule Solution do
def main() do
IO.read(:stdio, :all)
|> String.split
|> length
|> IO.inspect
end
end
Solution.main
- length
リストサイズを返す。
Update List
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.
これちょっとわかんなかった。いや、かけたことはかけたんだけど、なんか異様に長くなったというかなんかもう...あれ?みたいな感じになったからディスカッションで引っ張ってきた。
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
なんか新しいのきたな?とりあえず抜粋。
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(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 にする慣習らしい。
String.trim
stripは非推奨。空白文字の削除。:math.pow(x, i)
xのi乗計算factorial(i)
再帰でn!部分を計算
別に終わりじゃないまとめ
えっ!?本当にRuby風味?という感じです。しかも雰囲気というかノリで書いてるので、わかってないで変な書き方してるとこいっぱいある気がします。
きっとRubyエンジニアとしての実力かElixirの練習が足りないのでしょう。
引き続きやってきます(随時更新)。
というかこういう回答って記事にしていいのかな...。何か問題あったら教えてください。消して個人で楽しむことにします。