LoginSignup
10
1

More than 5 years have passed since last update.

はじめてなelixir(2) パスワード生成器をちょっと高級にする

Last updated at Posted at 2018-09-15

はじめに

前回は はじめてな 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
Elixir v1.7.3

10
1
8

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
10
1