やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

【第7回清流elixir勉強会@fukuoka.exさん】Elixirのシギルについて調べて色々試した【リモートでジョイン】

トピック

昨週に引き続き、清流elixirの第7回目の勉強会を開催させて頂きました
今回は清流elixirのみではなく福岡を拠点に活動されているfukuoka.exさんのもくもく会
リモートでジョインさせて頂きました

fukuokaex.connpass.com

もくもく会を主催されているkogaさん
色々とご準備頂きまして有難うございました

なんと昨日のリモートのもくもく会

  • 北海道
  • 東京
  • 愛知
  • 高知
  • 福岡

が繋がったらしいです。実質日本一週ですわ

それで僕はElixirのシギルたるものがずっと気になっていたのでもくもくとまとめた
ほとんど、喋りながらやってた気がする笑

シギルって

そもそもシジル? シギルどっち?
両方存在してるけどどっちが正しいのか?
(プログラミングelixirではシジルだけど...)

とりあえず以下、シギルとして扱う

「~」を先頭に書くことでシギルというものを扱うことが可能
プログラミングelixirによると

  • ~C
  • ~c
  • ~S
  • ~s
  • ~W
  • ~w
  • ~r
  • ~R

の4種類(小文字と大文字)が用意されている
とりあえず~sは動いた

~s(hello world)
# "hello world"

いろいろと試しつつ動かしてみる

~sと~Sについて

さきほどは~sに英字を渡したが日本語でも問題なくいけた

~s(こんにちは)
# "こんにちは"

数値でも問題ない模様

~s(114514)
# "114514"

~sと~Sの違いは?

~sをつかうと演算やら値の埋め込みしてくれるようだ
1/2(0.5)の演算結果が出力値に含まれている

~s(hello world #{1 / 2})
# "hello world 0.5"

変数の埋め込みも問題ない

val = "okb"
~s(hello world #{val})
# "hello world okb"

合わせて""やら''を使えるっぽい

~s(hello world #{"okb"})
# "hello world okb"

~s(hello world #{'okb'})
# "hello world okb"

~Sの場合は演算やら値の埋め込みは行われない

~S(hello world #{1 / 2})
# "hello world \#{1 / 2}"

~Sは変数名や関数名を文字列に変換するのに便利そう
基本的には~sの方が使い勝手は良さげですね

~cと~Cについて

~cの場合はシングルクオートで表示される
これは文字リストというものらしく、~s(string)とは仕様が異なる
文字リストに関しては下記に改めて記述する
~cでも同様に演算渡せるっぽい

~c(hello world)
# 'hello world'
~c(hello world #{1/2})
# 'hello world 0.5'

~Cの場合はやはり値の埋め込みが出来ない

~C(hello world #{1/2})
# 'hello world \#{1/2}'
~C(hello world)
# 'hello world'

ここまでで分かったことは

  • ~sのような小文字のものは値の埋め込みや演算が可能
  • ~Sのような大文字のものはそのまま出力される

ということ

シギルをStringモジュールに渡せるかどうか

どうやら~cだとerrorになってしまう
これは文字リストであって文字列ではないからということ

String.first(~c(hello world))
# (FunctionClauseError) no function clause matching in String.Unicode.next_grapheme_size/1

~sだとStringモジュールが使えた
やはり~sはstringとして扱われている

String.first(~s(hello world))
# "h"
String.graphemes(~s(hello world))
# ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]

パイプで遊べた(最高

String.graphemes(~s(hello world)) |> Enum.filter(&(&1 == "h"))
# ["h"]

文字リストについて

文字列はUTF-8エンコードされたバイナリ
文字リストは文字(コードポイント)のリスト

なるほど完全に理解した(すっとぼけ
文字リストは``で囲ってあげるとシギルでなくても作れるらしい
コードポイントってなんやねん~
?a(アルファベット)と記述することでコードポイントを確認することが可能

?a
# 97

?b
# 98

?c
# 99

何か出た。逆でも出た

[98]
# 'b'

厳密に考えるとリストin数値が文字リストとして扱われているということか
''で囲われているからstringではないということになるので普通にEnumやらが使えるということ?

'okb' |> Enum.map(&(IO.puts(&1)))
111
107
98
# [:ok, :ok, :ok]

おお、思った通りにEnumが使える
ただの数値として文字リストの要素が扱われているかどうかを確認してみる

Enum.map('okb', &(is_integer(&1)))
# [true, true, true]

ということなので以下が通る

Enum.sum('okb')
# 316

長くなりそうなので文字リストに関しては今回のテーマとそれるので、ここらへんで終了
文字をリストで扱えることに気づく
使い道はまだ思いつけていない。便利そうでまだ、うーんって感じ

~wと~Wについて

~wとすることで受け取った情報がリストに変換されている

~w[hello world okb]
["hello", "world", "okb"]

やはり受け渡しが出来た

val = "okb"

~w[hello world #{val}]
["hello", "world", "okb"]

~Wでは...もう語ることはない

~W[hello world #{val}]
["hello", "world", "\#{val}"]

リストになるのでもちろんパイプで遊べる(最高

~w[hello world #{val}]
|> Enum.filter(&(String.length(&1) > 4))
|> Enum.reduce(&(&2 <> &1))
"helloworld"

どうやら最後に付け合わせる文字でモードが変わる模様

  • a -> atom
  • c -> string list
  • s -> string
#a
~w[hello world #{val}]a
[:hello, :world, :okb]

#c
~w[hello world #{val}]c
['hello', 'world', 'okb']

#s
~w[hello world #{val}]s
["hello", "world", "okb"]

これは便利
String.splitでは全てがstringになるのでatomを生成できれば
マップのkeyを生成したりと使い道は色々とありそう

以上、シギルについて2時間で調査できた内容です

今回の勉強会の感想

もくもく会には何回か参加したことがあったが、リモートでの勉強会参加は初めてだった
あまり表に顔は出す人間ではないが非常に新鮮で同じものに興味のある方と共に学べたことが素晴らしい体験だった

ぜひまた参加したいです
fukuoka.exさんが月1回程度の頻度で開催されていらっしゃるので
ぜひ会場(全国各地)でお会いしましょう

【Elixirで学ぶCS】Elixirで加算器を実装するまで

なにこれ(既視感

前回の記事の続編です

www.okb-shelf.work

まだ挫折してないです

www.oreilly.co.jp

この本を勉強しているつもりなのですが、普通に難しい
僕のレベルが低いのもあるが、説明があるようでなかったりと結局ググらないと分からない部分が多い
まぁ、そこは自分で実装して理解しろやってコンセプトなんだと思いますがね

加算器について

数日前は唸りに唸って考えていたが、今思えば何も難しくなくてやっていることはシンプルだった
「ただの足し算マシーン」ということでした

一応の専門用語として

  • carry(桁上がり)
  • sum(下の例では1桁目の値)

というものがある 以下の例を見て頂きたい。aとbはそれぞれ入力値を指している

a b carry sum
1 0 0 1
0 0 0 0
1 1 1 0

ただの2進数の足し算でしかない
a+bが2になった時に桁上がりが発生するため、条件a = b = 1のときにcarryの値が1になっており
sumの値が0になっている
普段使っている10進数の計算となんら変わらないということが伝われば幸いです

今行ったこの演算をするための演算器を半加算器というそうです

しかしながら半加算器のみではcarry(桁上がり)の値を次の加算に用いることが出来ない
どういうことかというと

a0 = 1
b0 = 1
carry0 = 1
sum0 = 0

#carry(桁上がりなので10)を用いて半加算器でもう一度、加算を行う
a1(先ほどのcarry値) = 1
b1 = 1
carry1 = 1
sum = 0

せっかくの桁上がりが消滅して、また同じ桁で加算を行ってしまっている
これだと役に立たないので桁上がりを考慮した加算器が必要になる
それが全加算器とよばれるもので桁上がりを考慮した加算器というわけになる

全加算器では入力がa, bに合わせてz(桁上がり値)の3つを受け取る
イメージしやすいように先に論理表をお見せする

a b z carry sum
1 0 0 0 1
1 1 0 1 0
1 0 1 1 0
1 1 1 1 1

見てお判りかと思うが、単にa+bの加算を行った後にzを加算しているだけ
zを桁上がり値と見なせば全加算器と半加算器を組み合わせれば
上手く桁上がりを考慮した加算器が作れそうだと思う

#半加算器の加算
a0 = 1
b0 = 1
carry0 = 1 = z
sum0 = 0 

#全加算器の加算
a1 = 1
b1 = 1
z = 1
carry = 1
sum = 1

全加算器を複数個用意すれば、複数桁の加算を行うことが可能となる

elixirでの実装

まずはcarryとsumを求める関数を作成する
このcarryとsumはそれぞれ

  • carry = andゲート
  • sum = xorゲート

の出力に等しい
carryは入力が(1, 1)の時に1を返す
sumは入力が(1, 0)もしくは(0, 1)の時に1を返すため上記が当てはまる

前回に実装した論理ゲートを用いてそれぞれを無名関数で実装するとこんな感じに

sum_ = fn x, y -> RogicalGate.xor_(x,y) end
carry_ = fn x, y -> RogicalGate.and_(x,y) end

で、あとはこいつらを組み合わせて半加算器を作成する
(回路図はググってね)

half_adder = fn x, y -> {carry_.(x,y), sum_.(x,y)} end

タプルで返していることに深い意味はない(戻り値の要素が4以下だったのでタプルを採用)
簡単なテストを実行してみる

half_adder_test = [
  half_adder.(0, 0) == {0, 0},
  half_adder.(0, 1) == {0, 1},
  half_adder.(1, 0) == {0, 1},
  half_adder.(1, 1) == {1, 0}
]

IO.inspect(half_adder_test)
[true, true, true, true]

いいね
続いて全加算器を実装する
回路上では2つの半加算器を用意して、それぞれの出力をorに通すことで作成可能
(回路図はググってね)

all_adder = fn x, y, z ->
  {res1_x, res1_y} = half_adder.(x, y)
  {res2_x, res2_y} = half_adder.(res1_y, z)
  {RogicalGate.or_(res1_x, res2_x), res2_y}
end

同様に簡単なテストを実行する

all_adder_test = [
  all_adder.(0,0,0) == {0,0},
  all_adder.(0,1,0) == {0,1},
  all_adder.(1,0,0) == {0,1},
  all_adder.(1,1,0) == {1,0},
  all_adder.(0,0,1) == {0,1},
  all_adder.(0,1,1) == {1,0},
  all_adder.(1,0,1) == {1,0},
  all_adder.(1,1,1) == {1,1},
]

IO.inspect(all_adder_test)
[true, true, true, true, true, true, true, true]

いいね
先ほども述べたように後は全加算器を追加していくだけで演算の桁を増やすことが可能であり
試しに4bitの加算器を作成してみた(4桁目はcarry用(桁上がり))

bit_adder_4 = fn [a0, b0], [a1, b1], [a2, b2] ->
  {c0, s0} = half_adder.(a0, b0)
  {c1, s1} = all_adder.(a1, b1, c0)
  {c2, s2} = all_adder.(a2, b2, c1)
  [c2, s2, s1, s0]
end

IO.inspect(bit_adder_4.([1,1], [1,1], [1,1]))
[1, 1, 1, 0]

引数は以下のように捉えて頂きたい

  1 1 1  
+ 1 1 1  
--------  
 1 1 1 0

4bitにもなるとテストパターンが多すぎるので割愛
せっかくなのでnbitに対応する加算器も作ってみた

今まで作成したcarryやらhalf_adderを全てモジュール内関数に置き換えている
すっきりしたね
nbit対応には再帰関数を使用しているが、Enum.reduceとかで書ける気がする

defmodule NbitAdder do
  def sum_(x, y), do: RogicalGate.xor_(x,y)
  def carry_(x, y), do: RogicalGate.and_(x,y)
  def half_adder(x, y), do: {carry_(x,y), sum_(x,y)}
  def full_adder(x, y, z) do
    {res1_x, res1_y} = half_adder(x, y)
    {res2_x, res2_y} = half_adder(res1_y, z)
    {RogicalGate.or_(res1_x, res2_x), res2_y}
  end
  #例外処理: 引数が不正な場合に:errorを返す
  def nbit_adder([]), do: :error
  def nbit_adder([[]]), do: :error
  def nbit_adder([[a0, b0] | tail]) do
    {c0, s0} = half_adder(a0, b0)
    _nbit_adder(tail, c0, [s0])
  end
  
  #再帰関数: リストが空になったら終了
  defp _nbit_adder([], carry, accum), do: [carry] ++ accum
  defp _nbit_adder([[a1, b1] | tail], carry, accum) do
    {c0, s0} = full_adder(a1, b1, carry)
    _nbit_adder(tail, c0, [s0] ++ accum)
  end
end

基本的に難しいことは特にやってなくて、算出した結果をaccumlatorに随時、追加しているだけである
再帰関数が終了した際に、carryの値を加えるのを忘れたぐらいで割とスラスラ書けた
とりあえず、何か実行してみる

res1 = NbitAdder.nbit_adder([[1,1], [1, 1], [1,1], [1,1]])
IO.inspect(res)

#[1, 1, 1, 1, 0]

res2 = NbitAdder.nbit_adder([[1,0], [0, 1], [1,1], [1,0]])
IO.inspect(res)

#[1, 0, 0, 0, 1]

手計算で確認しましたが、良さげですね
この加算器の組み合わせてでコンピューターが動いていると思うと
考えた人はマジで天才ですわ...

【レポート】第6回清流elixir勉強会inMisoca様を開催しました【競プロ@Elixir】

トピック

今回で第6回目の勉強会を開催致しました
elixir-sr.connpass.com

2019年4月から清流elixirの運営を初めて早2ヶ月
Elixirを全く知らない状態 & 新卒のクソザコでも何とかなりました
昨週、東京に行って感じたことは名古屋はまだ多くの技術がブルーオーシャン
connpass等での勉強会も開催されていないということ

Elixirは元々の知名度がアレですが、例えばReact専用の勉強会(nagoya.js的な)がなかったりして
先輩エンジニアから「名古屋でReactの勉強会あればねー」という話はよく耳にする
Haskel勉強会とかあったら普通に行きたい

こんな僕でも運営できているので、どなたでも始められると思います
自身で勉強会を運営すると顔が広くなっていろんな方と知り合えて
チャンスも生まれるし、強くなれます

本当にオススメです
なければ自分で作る。エンジニアの鑑ですね(唐突な自画自賛

また今回は開催場所に株式会社Misoca様のセミナールームを使用させて頂きました
めちゃ広くて快適な部屋を無料で使用させて頂きました
本当に有難うございます
地味にレンタルオフィスの料金が値上げしてるので非常に助かります

tech.misoca.jp

清流elixir-infomation
開催場所: 丸の内(愛知)
参加人数: 4 -> 3
コミュニティ参加人数 : 8 -> 10 update!!
20190607現在

第6回の活動内容

今まである程度、Elixirの文法の基礎部分は触ってきた
あとはElixirでの実装力を高めていき、サクサクとコードが書けるようになると良い
ということを常連参加者の方から提案があり、今回はAtCoderにある問題をElixirで解いてみることに

コードの成否判定には提案者の方が作ってきてくれたRailsのwebアプリを使った
はえーすっごい。感謝しかない。有難うございます

今回取り組んだ問題はこちらの2問

1問目

整数a,b,cと文字列sが与えられます。a+b+cの計算結果と文字列sを並べて表示する
PracticeA - Welcome to AtCoder
条件
- 1 ≤ a,b,c ≤1,000
- 1 ≤ |s| ≤ 100
入力例
1
2 3
test
出力例
6 test

2問目

1,2,3の番号のついて3つのマスからなるマス目があります。各マスには0か1が書かれています。
いくつの1があるか求めて下さい
条件
マスは必ず1か0
入力例
1000 出力例
1

Elixirでの標準入力の受け取り

これが結構難しい
以下の関数を使用することで受け取りが可能

IO.gets("")
iex(107)> IO.gets("")
111
"111\n"

しかし、このままだと改行記号である「\n」が含まれてしまうので除去したい

iex(108)> IO.gets("") |> String.trim()
111
"111"

任意の回数、入力を受け取りたい場合は自己流ですが以下を採用
第1問目が入力が3回固定なのでrangeで0..2を使っています

Stream.map(0..2, fn _ -> IO.gets("") end)
|> Stream.map(&(String.trim(&1)))
|> Stream.map(&(String.split(&1)))
|> Enum.to_list
|> List.flatten()
iex> Stream.map(0..2, fn _ -> IO.gets("") end)
|> Stream.map(&(String.trim(&1)))
|> Stream.map(&(String.split(&1)))
|> Enum.to_list
|> List.flatten()
1 2 3
4 5 6
test
["1", "2", "3", "4", "5", "6", "test"]

入力を受け取る準備が整ったので上記の2問に取り組んでみる
ちなみに、ここに到着するまで1時間ぐらいかかってしまった
競プロ経験の少なさを反省。かなり興味が湧いたので時間があればゆっくり取り組んでみたい

整数a,b,cと文字列sが与えられます。a+b+cの計算結果と文字列sを並べて表示する

先ほどの複数入力を使用する
まずは入力を受け取るところから

input_ = Stream.map(0..2, fn _ -> IO.gets("") end) |> Stream.map(&(String.trim(&1))) |> Stream.map(&(String.split(&1))) |> Enum.to_list |> List.flatten()
#input_ ["1", "2", "3", "4", "5", "6", "test"]

あとはシンプルで一番最後の値以外を取得して数値に変換して合計すれば良い
脳筋で問題を解くとこんな感じに落ち着いた

input_ = Stream.map(0..2, fn _ -> IO.gets("") end) |> Stream.map(&(String.trim(&1))) |> Stream.map(&(String.split(&1))) |> Enum.to_list |> List.flatten()
sum_ = Enum.take(input_, Enum.count(input_)-1) |> Enum.map(&(String.to_integer(&1))) |> Enum.sum()
str_ = Enum.at(input_, -1)
IO.puts("#{sum_} #{str_}")

#21 test

ブログではお見せできないが無事に全てのcaseでtrueになった

他の参加者の方からインスパイアを受けて2行にまで短縮することができた(パイプでつないでのは1行でカウント)
すごい。発想に脱帽

[str_ | nums] = Stream.map(0..2, fn _ -> IO.gets("") end) 
|> Stream.map(&(String.trim(&1))) 
|> Stream.map(&(String.split(&1))) 
|> Enum.to_list 
|> List.flatten() 
|> Enum.reverse()
IO.puts("#{Enum.sum(Enum.map(nums, &(String.to_integer(&1))))} #{str_}")

さらに突き詰めた結果、ワンライナーで書くことができた

with [str_ | nums] = Stream.map(0..2, fn _ -> IO.gets("") end) 
|> Stream.map(&(String.trim(&1))) 
|> Stream.map(&(String.split(&1))) 
|> Enum.to_list 
|> List.flatten() 
|> Enum.reverse(), do: 
IO.puts("#{Enum.map(nums, &(String.to_integer(&1))) |> Enum.sum()} #{str_}")

1,2,3の番号のついて3つのマスから...いくつの1があるか求めて下さい

こちらは入力が1つで固定なので先ほどより簡単
僕ですらワンライナーで書くことが何とかできた

Stringにクッソ便利なgraphemes()という関数が用意されており
文字列を(eg: "elixir")を1つ1つに分解してくれる(eg: ["e", "l", "i", "x", "i", "r"])
あとはこのリストから"1"以外(stringのまま)を除去してカウントすれば良い

IO.gets("") |> String.graphemes() |> Enum.filter(&(&1 == "1")) |> Enum.count() |> IO.puts()
IO.gets("") |> String.graphemes() |> Enum.filter(&(&1 == "1")) |> Enum.count() |> IO.puts()
101
2

やったぜ。
あれ、競プロっておもろい?

次回の告知

急ですが、来週(6/14(金))の19:30より福岡で活動されています
fukuoka.exさんのもくもく勉強会にリモートでジョインさせて頂くことになりました
fukuokaex.connpass.com

昨週に行ったErlang ElixirFest2019でお話を頂きまして光栄の限りです
開催場所がいつものレンタルオフィスではなく、名駅付近のレンタル会議室となりますのでご注意ください

【実装コード有り】Elixirでpandasっぽいcsvファイルを触るモジュールを作ってみた

ゆるふわな動機

pythonのライブラリにpandasと呼ばれるものがある
厳密にはC言語で書かれているのでpythonかというとそうでもないが
このpandasが提供しているAPIにread_csvというものがあり
csv形式のファイルをdf形式で読み込みpandasでお気軽にゴリゴリと編集することが可能
(shif-jisで読み込んでいるのは日本語を含むcsvのため。defaultはutf-8だったはず)

df = pd.read_csv("sample_data.csv", encoding = "shift-jis")
print(type(df))

#pandas.core.frame.DataFrame

このdfに対して

  • この条件で~
  • この条件で~
  • こういうカラムを追加して~
  • csvに保存

なんてことをよくやる
テキトーにコードを妄想するとこんな感じになる

df = pd.read_csv("sample_data.csv", encoding = "shift-jis")
df = df[df["code"] == "00"]
df = df.query('year > 2010')
df.to_csv("sample_new.data.csv", header=True, index=False)

もしくはdf.assignを使ってゴリゴリやる方法もある
こちらのqiitaの記事が素晴らしいです
https://qiita.com/piroyoung/items/dd209801ca60a0b00c11qiita.com

で、結局何が言いたいの?って話なんですけど、このデータ処理の流れってまさに
Elixirのパイプ演算じゃんってことを思う訳ですね

csv
|> Aカラムを削除
|> Bカラムに空値があるレコードを除去
|> Bカラムの100以下の数値を持つレコードを除去
|> csv形式で.csvファイルを出力

あ、いい。ここに並列処理が加わると...あとは分かるね?

とりあえず関数作った

全体コードはこちら。レポジトリ名は気分でつけました。
github.com

一番良く行う操作 ==> 特定条件での抽出 なのでこの操作を完結できる関数を用意してみた
csvライブラリを使って.csvファイルを読み込んでいる
読み込んだ時点ではデータはStream形式になっているのでStreamを可能な限り保って値を変換するようにしている
課題は並列処理の実装をしていないこと

パターンマッチを使って3種類の条件渡しに対応した

  • 条件(bool値を返す)を関数を渡すパターン(is_function)
  • 複数の値(リスト)からOR検索を行うパターン(is_list)
  • 完全一致する値を渡すパターン(上記以外)

記述は可能な限り、一般化したつもりだが
補助関数に無名関数を渡してて若干可読性が悪いような気もする
すっきりしててElixirっぽくて良いかなと思ってますが
インデントについてはオレ流ですいません

defmodule CsvEditer do
  require CsvColumn
  
  # 関数を第3引数に受け取った場合
  def filter(cdl, column_name, func) when is_function(func) do
    # 引数で受け取った関数を実行する無名関数を作成
    stream_func = fn enum, index, ope_func -> Stream.filter(enum, &(ope_func.(Enum.at(&1, index)))) end
    _filter_helper(cdl, column_name, stream_func, func)
  end

  # 複数の値(リスト)を第3引数に受け取った場合
  def filter(cdl, column_name, val) when is_list(val) do
    # 引数で受け取ったリストの値を持つかどうかを確認する無名関数を作成
    stream_func = fn enum, index, val -> Stream.filter(enum, &(Enum.member?(val, Enum.at(&1, index)))) end
    _filter_helper(cdl, column_name, stream_func, val)
  end

  # 単純に値を第3引数に受け取った場合
  def filter(cdl, column_name, val) do
    # 引数で受け取った値と一致するかどうかを確認する無名関数を作成
    stream_func = fn enum, index, val -> Stream.filter(enum, &(Enum.at(&1, index) == val)) end
    _filter_helper(cdl, column_name, stream_func, val)
  end

  # 一般化された補助関数
  defp _filter_helper(cdl, column_name, stream_func, val) do
    # 受け取ったカラムがリストのどの位置(index)にあるかを検索
    index_num = CsvColumn.val_index_in_header(cdl, column_name)
    body =
      cdl
      |> Stream.drop(1) #headerを一時除去
      |> stream_func.(index_num, val) #受け取った関数を実行
    Stream.concat(Stream.take(cdl, 1), body) #headerをつけて返す
  end
end

CsvColumnモジュール については特に難しいことはしていない

defmodule CsvColumn
  def cdl_header_stream(cdl, false) do
    # header(リストinリストのhead)をリストで返す
    cdl
    |> Stream.take(1)
    |> Enum.to_list()
    |> List.first()
  end
  # Streamのまま返す
  def cdl_header_stream(cdl, _), do: cdl |> Stream.take(1)

  # header(リスト)から引数の値を検索してindex番号を返す
  def val_index_in_header(cdl, column_name) do
    cdl
    |> cdl_header_stream(false)
    |> Enum.find_index(fn val -> val == column_name end)
  end
end

今回、検証に使用したのはこちら
よくサンプルとして広く扱われるiris(ゆり)のデータです

紹介するのを忘れていましたが、こちらがcsvを読み込む関数

defmodule CsvReader do
  def read_csv(file_path) do
    case File.exists?(file_path) do
      true ->
        file_stream =
          File.stream!(file_path)
          |> CSV.decode
          |> Stream.map(fn row ->
            {_, value_lst} = row
            value_lst
          end)
        {:ok, file_stream}
      false ->
        {:error, "#{file_path}: no such file or directory"}
    end
  end
end

以下を目標にデータをゴリゴリする

  • varietyはSetosaのみを抽出
  • sepal.lengthが6.0以下のみを抽出
  • sepal.widthが3.4もしくは3.5の値のみを抽出

では、さっそく
(ちなみにcdlというのは「csv data list」から命名している)

$ iex -S mix

#成功すればstatusとstreamの値が帰ってくる
iex> {_status, cdl} = CsvReader.read_csv("iris.csv")
{:ok,
 #Stream<[
   enum: #Function<63.126435914/2 in Stream.transform/3>,
   funs: [#Function<49.126435914/1 in Stream.map/2>,
    #Function<49.126435914/1 in Stream.map/2>]
 ]>}

# 一回データの中身を見てみる(リストinリスト)
iex> cdl |> Enum.to_list
[
  ["sepal.length", "sepal.width", "petal.length", "petal.width", "variety"],
  ["5.1", "3.5", "1.4", ".2", "Setosa"],
  ["4.9", "3", "1.4", ".2", "Setosa"],
  ["4.7", "3.2", "1.3", ".2", "Setosa"],
  :
  :
  ["4.6", "3.1", "1.5", ".2", "Setosa"]
]

数値(float)への変換はブログ記事の簡略化のため、裏作業で行なった

[
  ["sepal.length", "sepal.width", "petal.length", "petal.width", "variety"],
  [5.1, 3.5, 1.4, 0.2, "Setosa"],
  [4.9, 3.0, 1.4, 0.2, "Setosa"],
  [4.7, 3.2, 1.3, 0.2, "Setosa"]
]

準備完了。あとはパイプで遊ぶだけ

iex> csv |> 
        CsvEditer.filter("variety", "Setosa") |>
        CsvEditer.filter("sepal.length", fn x -> x <= 6.0 end) |>
        CsvEditer.filter("sepal.width", [3.4, 3.5]) |>
        Enum.to_list
[
  ["sepal.length", "sepal.width", "petal.length", "petal.width", "variety"],
  [5.1, 3.5, 1.4, 0.2, "Setosa"],
  [4.6, 3.4, 1.4, 0.3, "Setosa"],
  [5.0, 3.4, 1.5, 0.2, "Setosa"],
  [4.8, 3.4, 1.6, 0.2, "Setosa"],
  [5.1, 3.5, 1.4, 0.3, "Setosa"],
  [5.4, 3.4, 1.7, 0.2, "Setosa"],
  [4.8, 3.4, 1.9, 0.2, "Setosa"],
  [5.0, 3.4, 1.6, 0.4, "Setosa"],
  [5.2, 3.5, 1.5, 0.2, "Setosa"],
  [5.2, 3.4, 1.4, 0.2, "Setosa"],
  [5.4, 3.4, 1.5, 0.4, "Setosa"],
  [5.5, 3.5, 1.3, 0.2, "Setosa"],
  [5.1, 3.4, 1.5, 0.2, "Setosa"],
  [5.0, 3.5, 1.3, 0.3, "Setosa"],
  [5.0, 3.5, 1.6, 0.6, "Setosa"]
]

おぉ...ええやんけ
パイプ演算子のおかげで何がしたいのかが一発で分かる
問題は速度。記事の文字数が多くなったため、別記事で検証してみようと思う

ただ、可読性はあきらかに上がった(個人的にね

レッツElixir

岐阜県民だけどErlang&ElixirFest2019のために初ソロ東京に行ってきた

Erlang & ElixirFest2019とは

f:id:takamizawa46:20190602130917j:plain Erlang & Elixirが大好きな人やら興味のある人が集まる万博みたいなお祭り
年に一度開催されていらっしゃるようで今年で3度目になるそうです

Erlang & ElixirFest2019
elixirconf.connpass.com

開催場所はご想像の通り、東京です
Erlang、Elixir界隈ではかなり名のある方々が集まり、かなり技術レベルの高いカンファレンスとなっています

他の方と比べるとElixirとErlangの知見はほぼ皆無に等しいですが 岐阜に住んでて名古屋で仕事していると、こういうハイレベルな空気感に触れられる機会は、ほぼ皆無と言っていいので

  • ウン万円払ってでも!!
  • 恥をかいてでも!!

と割り切って思い切って行ってみました
ただ東京に行くのが人生二度目かつソロで新幹線乗ったのが初めてだったので、めちゃくちゃ迷いました
田舎者丸出しで恥ずかしや〜💦

内容について

以下、connpassのページから引用です

  • Keynote: Erlang/OTP と ejabberd を活用した Nintendo Switch(TM)向けプッシュ通知システム「NPNS」の開発事例 (任天堂 ネットワークシステム部 渡邉大洋)
  • 超並列高速実行処理系 Hastega 〜 Lonestar ElixirConf 凱旋帰国 (山崎 進)
  • Nervesが開拓する「ElixirでIoT」の新世界 (高瀬 英希)
  • 工場の制御をElixirで 〜ラダー・ロジックを実行する〜 (菊池 豊)
  • Elixir プロセス入門
  • ElixirでやるDDD (ドメイン駆動設計) (ne_Sachirou)
  • Phoenix1.4とVueによるサービス構築のノウハウ (山口 強)
  • ProtocolBuffer(proto3)をElixirで実装する (にく)
  • 4才から24才までプログラミングを教えた結果、高校生にElixirを教えるに至った気付き (YOSUKENAKAO.me)
  • XFLAG × スポーツ × Elixir (村山 寛明)
  • Serverless BEAM with FaaS (大原 常徳)
  • ロマサガRS における Elixir サーバー開発実践 〜生産性を上げてゲームの面白さに注力〜 (梶原 星平/関山 友輝)
  • Erlang/OTP で作るリアルタイムサーバー (清水 佑吾)
  • Erlang/OTP で WebRTC と QUIC (voluntas)
  • Enum.mapから始めるElixirデータサイエンス(piacere)

圧倒的...ッ!!! ボリューム...ッ!!
ラーメンに餃子とチャーハンと天津飯と唐揚げつけて頼んだかってぐらい
一日でこれだけの情報量を摂取できるのやばい
半分ぐらいは自分のレベル感で理解できる内容だったと信じているが
技術レベルの差にただ頷くことしかLTもあり本当に世の中は広いなと感じざるを得なかった

その反面、私の主催している「清流elixir」の常連メンバーが
elixir-sr.connpass.com

普通にErlang&ElixirFest2019に来ていて、世間は狭いなと思った
Fest行きたいなって決定打に思ったのが今回のKeynote
任天堂ErlangかElixirをswitchの通知システムに使ってるらしいよ」って話は風の噂で聞いたことがあった
ただ、その部分の任天堂さんのオープンな話(事実含めて)ってどこにも落ちてなくて
今回公の場に登壇されてお話できる範囲で詳細を初めて語って下さりました
終了後に拍手が鳴り止まず、何と色々と許可が出れば書籍化されるとかされないとか

有志の方がLTのリンクや関連スライドをqiitaの記事にまとめて下さっているので展開しておきます
qiita.com

20190601追記
keynoteでの渡邊さんのLT資料が公開されたので貼っておきます
Keynote: Erlang/OTP と ejabberd を活用した Nintendo Switch(TM)向けプッシュ通知システム「NPNS」の開発事例 (任天堂 ネットワークシステム部 渡邉大洋)
speakerdeck.com

以下、気になった&理解できているLTの内容についてざっくりとまとめてます
内容は理解できたレベルでまとめているが誤りがあれば教えてください

超並列高速実行処理系 Hastega

Elixirの並列処理ライブラリのFlowにインスパイアを受けてより速いものを作りたいという
研究者ならではの視点から開発を進められているライブラリ
GPU上でも動作し、Flowの10倍, pythonの3倍で動作するようです(化け物か

Elixir界隈では - 再帰関数 - Enum.map

を使うかでしばしば争いが起こりますが
山崎さんは後者を「Elixir Zen Style」と名付られ、こっちの方がいいじゃんと
ただ実は再帰関数の方がEnum.mapよりもパフォーマンスが良いらしいが
Hastegaを使えば、この事実がひっくり返りElixir Zen Styleで速く美しいコードがかけるとのこと

名前はFinalFantasyのヘイスガ(パーティのスピードを上げる魔法)から来ているとのことですが
実はJose(Elixirの作者)はFFをやったことがないことが判明して、会場がドワッと笑いに包まれた

僕もElixirでFFから来ていると思ってたのでワロタ
hypeやnifっていう話が前提知識がなさすぎてついていけなくなってしまったので
別の機会にインプットして記事にでもしてみようと思う

Nervesとラダーロジック

僕のイメージElixirはweb系でサーバーやらネットワークの開発がメインでしょってのがぶち壊された
IoT(ラズパイの規模から発電所)に対してもElixirはガンガン使えるぜっていうLTで
普段自分が目を向けないレイヤーの話で非常に面白かった

会場ではラズパイを動かすデモもあり、起動した際には拍手が沸き起こった(ウオオオオオ

Nervesが海外ではPhoenixと同レベルの立ち位置にいるらしく、どれだけ注目されているかが分かる
なんと2019 09/06(木) ~ 09/07(金)にかけて下呂(岐阜県やで)にてNervesの作者がやってくるそうです
岐阜はんぱないって!!

ラダーロジックというものに関しての知識は業界不一致で皆無だったが
発電所などをクラウドで管理するにはElixirは要件を満たしくれるとのこと
pythonを使って再帰で動かした際には異常があった場合に例外投げてしまって
現状どうなっているのかが分からず、管理に凄く苦労した」

「Elixirのプロセスであれば一回、killして立ち上げ直すだけだからぴったり」

おっしゃる通りです
この2つのLTからElixirの組み込み, IoT業界での伸び代を感じぜざるを得ませんでした

Elixir プロセス入門

spawnから始めり、メッセージを送受信して、Task触って、死活管理して....
個々の知識はあって穏やかな心で聞くことが出来た

Elixirの並列処理についてどの順で学べばいいかということがフロー化されており
自分のほしかったものはこれなんだよな感が半端ない

このまま清流elixirの勉強会に使えそうで非常にありがたいです

Enum.mapから始めるElixirデータサイエンス

僕が最初にElixirを選んだ理由はまさにこれ
データ処理のしやすさ(Enum)と軽量プロセスによる並列処理
並列にゴリゴリとデータ処理ができたらpythonの弱点を克服できるやんけ!!
こんなんデータ処理のためにあるみたいなもんじゃんと調べている内に先駆者であるpiacereさんを発見

LTの内容も好きな物尽くしでElixir愛がビシビシ伝わってきました

Erlang/OTP と ejabberd を活用した Nintendo Switch(TM)向けプッシュ通知システム「NPNS」の開発事例 (任天堂 ネットワークシステム部 渡邉大洋)

凄かった。さすが任天堂やで...

この一言に尽きる
詳しい話はLTを見て頂ければいいので思ったことを書く

本質的に行なっていることは自分と変わらないと思った
問題があってそれを解決するということ(ただ数字がコネクション1億とか笑えた)

1つの課題を達成するのに半年かかったこともあるとのことで
半年間の間、正解も分からない暗い森を歩き続けられるマインドが凄い

あとは会社の技術力にも脱帽
先輩エンジニアたちの知見があるからこそ成せる技。さすが任天堂

あとErlangやりたくなった。正確にはもっとErlangについて知りたくなった

総評

いい空気を吸うことができた
いくらネットで調べたら出るといえども、地方にいると技術の風を肌で感じることができない
とくにElixirっていう日本ではマイナーな部類に入る言語ならなおさら

改めて自分の技術レベルの低さを感じた
少しでも追いつけるようにインプットしなければと思う
強くなるきっかけを得られてErlang&ElixirFest2019に本当に感謝です

おまけのコーナー

休憩時間中に行われたハンズオンのコードを置いておきます

ErlangElixirFestHandsOn2019
github.com

以下を実装

(1) 1000以下のランダムな数を生成し、その時間(ミリ秒)スリープした後に、その数字を返す
(5) (1)の処理を100回、並行に実行する

ランダム数値の作成やsleepの仕方、Taskについては上記のレポジトリに記述がある
Taskについては以下の記事でも触れている

www.okb-shelf.work

defmodule HandsOn do
  def random_and_sleep(sec) do
    r_num = :rand.uniform(sec)
    IO.puts("stop: #{sec} ")
    :timer.sleep(sec)
    r_num
  end
  
  def main(sec, parallel_num) do
    Enum.map(0..parallel_num, fn _ -> Task.async(HandsOn, :random_and_sleep, [sec]) end)
      |> Enum.map(&(Task.await(&1)))
  end
end

sec = 100
parallel_num = 100
IO.inspect(HandsOn.main(sec, parallel_num))

結果

stop: 100
stop: 100
stop: 100
:
:
stop: 100
[84, 52, 86, 8, 23, 79, 29, 67, 28, 5, 17, 69, 98, 18, 98, 39, 94, 73,
 59, 78, 36, 25, 22, 77, 59, 60, 2, 86, 83, 29, 24, 97, 70, 18, 11, 89,
 34, 25, 27, 80, 61, 37, 53, 80, 26, 60, 22, 11, 43, 40, ...]