📝

Elixir 始めた

2020/10/21に公開

まだ始めて間もないですが、気づいた点を記します。

文中のカッコ内の数字

(p.56)など。
書籍「プログラミングElixir 」(第2版)でのページ数を記載しています。

環境

GalliumOS(Linux) 3.0 on Chromebook Edger(Acer CB3-431)
Elixir 1.10.3 on docker

コメント (p.35)

# のあとに書きます。pythonと同じ。

関数

名前付き関数 (p.53)

モジュールの中で定義します。モジュール名は大文字で始めます。関数名は小文字から始めます。

defmodule Basic do  # モジュール定義
  def sky(first) do  # 名前付き関数定義
    first |> Enum.map(fn x -> x + 10 end)
  end
  def allindex(list, value) do
    0..length(list)-1 |> Enum.filter(fn x -> Enum.at(list, x) == value end)
  end
end

名前付き関数の呼び出し方

モジュール名から始めます。引数をカッコ内に書きます。

iex> Basic.sky([3, 5, 7, 9])
[13, 15, 17, 19]

無名関数 (p.41)

名前付き関数内などで定義されます。

(1) 引数を元に処理をする

関数定義

iex> type = fn x, y -> x * y end

(2) 引数でパターンマッチングする(p.43)

関数定義

type = fn
  _, 0 -> "excpetion!"
  x, y -> x * y
end

無名関数の呼び出し

引数は関数名の後にドットをつけてから書きます。
引数が無い場合でも呼び出し時のカッコは省略不可です。

iex> type.(3, 5)
15
iex> type.(7, 0)
"exception!"

無名関数での注意点

無名関数へのパイプ演算子

第一引数に渡されます。

iex> func = fn list -> length(list) end
iex> [7, 9, 5, 0] |> func.()
4

無名関数で回帰は書けない

無名関数は自分自身を呼べ出せないようです。
回帰を記述にするには名前付き関数を使用します。

iex> func = fn
...>   9, sum -> sum
...>   n, sum -> func.(n+1, sum + n)
...> end
iex> func.(1, 0) # iexでの関数定義ではエラーはでませんが、呼び出すとエラーとなります。

関数を引数として渡す(p.47)

基本的に関数を引数として渡すことができるのは無名関数だけで、名前付き関数はそのままでは渡せません。名前付き関数を渡す場合は、無名関数にかぶせて渡します。

iex> apply = fn {a, b, c}, func -> func(a, b, c) end  # 名前付き関数では未定義でエラー
iex> apply = fn {a, b, c}, func -> func.(a, b, c) end # 無名関数ならOK
iex> ({x, y, z}
...>  |> apply.(&func1/3) 
...>  |> apply.(&func2/3)
...> )
...> # func1/3, func2/3とも名前付き関数、引数:3個、返り値:タプル/要素3個 とする
...> # もしくは
iex> ({x, y, z}
...>  |> (fn {a, b, c} -> func1(a, b, c) end).()
...>  |> (fn {a, b, c} -> func2(a, b, c) end).()
...> )

パターンマッチング後の処理を複数書く場合の書き方

withを使わない場合は、doendは不要で、つぎのパターンまでずらずらと記述するだけです。

iex> func = fn 
...>   :false, list, _ -> list
...>    :true, list, n ->
...>       key = Enum.at(list, n) # ずらずら
...>       [key | list]           # ずらずら
...> end

簡略化記法 (p.48)

& を使います。&1は1個目の引数を指します。

iex> Enum.map([1, 2, 3, 4, 5], &(&1 * 2) )
[2, 4, 6, 8, 10]

関数の返り値(名前付き関数,無名関数とも)

関数の処理の一番最後の部分に記述します。
パイプ演算子を用いる場合は最後の処理が終わった後のデータが返り値になります。

def main(s) do
  :
  処理
  :
  main関数の返り値
end
defmodule Spl do
  def getkey(x, y) do
    threestr = fn intg ->
      intg |> Integer.to_string() |> String.pad_leading(3, "0") end
    threestr.(x) <> threestr.(y) # 返り値
  end
  def main(s) do
    s |> String.split() |> Enum.map(fn x -> String.to_integer(x) end) |> Enum.sum()
  end
end
iex> Spl.getkey(15, 3)
"015003"
iex> Spl.main("6 13 25 34")
78

変数を保護を行いたい場合は、with式を使います。

回帰リストの基本的書き方 (p.71)

名前付き関数とリストで書きます。
呼び出し関数の最後の呼び出し部を最初に書きます。

def func1([]), do: 定数
def func1([head | tail]) do
  func2(head) (演算子) func1(tail)
end

先日twitterに載っていたコラッツ予想の計算は以下のように書けました。

defmodule Main do
  def main(1, acc), do: Enum.reverse([1 | acc])
  def main(num, acc) when rem(num, 2) == 0, do: main(div(num, 2), [num | acc])
  def main(num, acc), do: main(num * 3 + 1, [num | acc])
end
iex> Main.main(7, [])  # accの初期値は必ず[]とする。
[7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]

リスト (p.29, p.71)

[]に囲まれたデータ列。

リストの要素を得る(インデックス指定)

Enum.at/3 を使います。インデックスは0から始めます。

iex> Enum.at([1, 2, 3, 4, 5], 3)
4

リストのスライス

Enum.slice/3 を使います。

iex> Enum.slice([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2, 5)  # index:2 から5個を切り出す
[3, 4, 5, 6, 7]

リストの要素の書き換え

List.replace_at/3 を使います。正しくは書き換えではなく、新しいリストの作成です。

iex> List.replace_at([1, 2, 3, 4, 5], 2, 9)
[1, 2, 9, 4, 5]

リストの要素の追加

List.insert_at/3 を使います。こちらも新しいリストの作成です。

iex> List.insert_at([1, 2, 3, 4, 5], 2, 17)
[1, 2, 17, 3, 4, 5]

数字のリストが文字リストで表示されないようにする (p.121)

:io.format/2もしくは:io.fwrite/2を用います。
両者は機能的にはほぼ同じのようです。

iex> [67, 65, 84]  # 数字リストは文字リストで表示されることがある。
'CAT'
iex> :io.format("~w~n", [ [67, 65, 84] ])
[67,65,84]
iex> :io.fwrite("~w~n", ['CAT'])
[67,65,84]

フォーマットの意味
~w: データをそのまま表示する。
~p: データを整形して表示する。
~s: 文字列を表示する。
~n: 改行

タプル (p.28, p.84)

{}に囲まれたデータ列。関数呼び出しの返り値を得るのに使用されます。
タプルのデータの変更は高ストのため、頻繁な要素の入れ替えには向いていないようです。

タプルの要素を得る(インデックス指定)

要素数が少ない場合はパターンマッチングで取り出せます。

iex> {_, sec, _ } = {"first", "second", "third"}
{"first", "second", "third"}
iex> sec
"second"

Kernel.elem/2 も使えます。

iex> elem({"first", "second", "third"}, 1)
"second"

マップ (p.28)

まだ使いこなせてませんが、MapモジュールとEnumモジュールがあれば、大抵のことはできそうです。

マップの作成

Map.put/3を用います。

iex> map = %{"NY" => "New York"}
iex> map = Map.put(%{}, "NY", "New York")
%{"NY" => "New York"}

マップの値の取り出し

Map.get/3を使います。

iex> Map.get(map, "NY")
"New York"

マップの更新(p.87)

Map.put/3で新しく値を入れることができます。

iex> Map.put(map, "NY", "NEW YORK")
%{"NY" => "NEW YORK"}

Map.update!/3を使うと現在の値を使った表記ができます。

iex> mapc = Map.update!(map, "NY", fn value -> value <> " city" end)
%{"NY" => "New York city"}

文字列 (p.117)

" で囲います。

文字列の要素を得る(インデックス指定)

String.at/2 を使います。

iex> String.at("Elixir", 3)
"x"

1文字ごとのリストの作成

String.graphemes/1 を使います。

iex> String.graphemes("Elixir")
["E", "l", "i", "x", "i", "r"]

文字列中の特定の文字の個数を数える

一例です。

iex> str = "what you have to do is to sleep now."
iex> str |> String.graphemes() |> Enum.count(fn x -> x == "a" end) # "a"の個数を数える
2

注意点

  1. whenガード節では使用できる関数が限られている。(p.58)
  2. パターンマッチング部では関数や演算子は使用できない。
    関数や演算子を置きたい場合は、呼び出し側に置く。
func = fn 
  i,   i -> "no change"  # これは問題なし
  i, i+1 -> "one by one" # i+1 部分がエラーとなる
  _,   _ -> "others"
end
iex> func.(head1, head2)
func1 = fn
  i, i -> "no change"
  _, _ -> "others"
end
func2 = fn
  i, i -> "one by one"
  _, _ -> "others"
end
iex> func1.(head1, head2)
iex> func2.(head1, head2 -1)  # 呼び出し側の引数内で演算する
  1. ピン演算子はパターンマッチング部でしか使用できない。

競技プログラミングでの入力の受け方

1行の受け取り

IO.gets("") もしくは IO.read(:line) を使います。

入力文字列
7 18 9 3
iex> givendata = IO.read(:line) |> String.trim() |> String.split() |> Enum.map(&String.to_integer/1)
iex> givendata
[7, 18, 9, 3]
入力文字列
5
3
14
11
iex> givendata = for _ <- 1..4, do: IO.read(:line) |> String.trim() |> String.to_integer()
iex > givendata
[5, 3, 14, 11]

変数の値を出力 (p.117)

"#{variable}" を使います。

iex> {x, y} = {"Romeo", "Julliet"}
iex> first = "#{x} et #{y}"
iex> first
"Romeo et Julliet"

以上

Discussion