6
3

More than 5 years have passed since last update.

Elixir勉強メモ

Last updated at Posted at 2018-08-30

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

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

【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の練習が足りないのでしょう。
引き続きやってきます(随時更新)。

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

6
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3