はじめに
前回は はじめてな elixir(1) として荒っぽいプログラムを書いてみました。サンプルとしてパスワード生成器を取り上げました。もう少し書いてみようと思います。
改良したところ
前回は、小文字、大文字、数字、特殊文字、をそれぞれ1文字は最低含むようにしました。今回はそれぞれの文字種別に最低文字数を指定できるような関数にしてみます。
丸一日かけて書いたプログラムがこれだ
mkpwd3.ex
defmodule Password do
def gen(len, l, u, n, s) do
if len >= l + u + n + s do
base_str()
|> Enum.reduce("", & &2<>&1)
|> random_stream()
|> Stream.reject(&(&1 in ambiguous_chars()))
# |> Stream.drop_while(&(&1 in ambiguous_chars())) # 初稿から修正した。こっちではいかんらしい。
|> filter(len, l, u, n, s)
|> to_string()
else
raise ArgumentError, message: "The length must equals to or be greater than the sum of the numbers of each letter types."
end
end
def ambiguous_chars() do
String.split("0oO1iIjJlL9qQ", "", trim: true)
end
def filter(str, len, l, u, n, s) do
str_list = Enum.take(str, len)
if (base_str()
|> Enum.map(& Enum.count(str_list,
fn s -> s in String.split(&1, "", trim: true) end))
|> Enum.zip([l, u, n, s])
|> Enum.map(fn({x, y}) -> x >= y end)
|> Enum.all?)
do
str_list
else
filter(str, len, l, u, n, s)
end
end
def random_stream(str) do
Stream.unfold(nil, & {random_char(str), &1})
end
defp random_char(str) do
Enum.random(String.split(str, "", trim: true))
end
def base_str() do
str_lower = "abcdefghijklmnopqrstuvwxyz"
str_upper = String.upcase(str_lower)
str_number = Enum.to_list(0..9) |> Enum.map(& Integer.to_string(&1)) |> Enum.map_join(& &1)
str_special = "-/"
# str_special = "\#$%;,.?/"
[ str_lower, str_upper, str_number, str_special ]
end
end
実行結果の例
iex(40)> Password.gen(4,1,1,1,1)
"6u-M"
iex(41)> Password.gen(5,1,1,1,1)
"-nI1X"
iex(42)> Password.gen(5,2,1,1,1)
"r5/oE"
iex(43)> Password.gen(6,2,1,1,0)
"KDwK2f"
iex(44)> Password.gen(4,2,1,1,0)
"s7hI"
iex(45)> Password.gen(4,2,1,1,1)
** (ArgumentError) The length must equals to or be greater than the sum of the numbers of each letter types.
mkpwd3.ex:11: Password.gen/5
考察(ちゅうか疑問だらけだけど)
- まだ記法にとまどう。
- リスト、マップ、キーワードリスト…、の使い分けがまだ落ちてきてない。
- [0..9] とは書けるけど [0..] とは書けない。無限リストをニュルッと生成できないものか。
- ちなみに ['a'..'z'] とも書けない。
- filter 関数は本当は unix コマンドの tee みたいにしたかった。
- 合格したのを IO.puts で表示しつつパイプの後ろに出し続けるみたいな…
- あいかわらずパターンマッチはうまく使えない。if が多いのがそれを表している。
- パイプライン構文とパターンマッチと相性が悪い気がしてる
次回は spawn 使って、パーツを別々のプロセスで動かしてみるのをやってみるつもりです。
参考文献
[プログラミングElixir] (https://www.amazon.co.jp/dp/4274219151/)
[Elixir v1.7.3] (https://hexdocs.pm/elixir/)