如何限制仅生成[0-9a-Z]字符的随机字符串?

时间:2017-08-31 14:39:52

标签: elixir

我在Elixir中生成一个随机字符串,如下所示:

  len = 10
  val = :crypto.strong_rand_bytes(len)
          |> Base.url_encode64()
          |> binary_part(0, len)

此代码的输出可以包含我不想要的连字符和下划线。将字母表限制为仅[0-9a-Z]个字符的方法是什么?

4 个答案:

答案 0 :(得分:7)

我会用:

defmodule Generator do
  @alphabet Enum.concat([?0..?9, ?A..?Z, ?a..?z])

  def randstring(count) do
    # Technically not needed, but just to illustrate we're
    # relying on the PRNG for this in random/1
    :rand.seed(:exsplus, :os.timestamp())
    Stream.repeatedly(&random_char_from_alphabet/0)
    |> Enum.take(count)
    |> List.to_string()
  end
  defp random_char_from_alphabet() do
    Enum.random(@alphabet)
  end
end

iex> Generator.randstring(8)
"ydKPsdwP"

这将生成一个由[0-9A-Za-z]组成的任意长度字符串,无需通过:crypto生成随机字节并进行过滤,直到获得所需条件的足够随机字节为止,特别是因为我怀疑弱化随机性的强度显着,因此无论如何都使:crypto成为一个有争议的点。

答案 1 :(得分:2)

这是一个解决方案,我会立即遵循为什么您可能不应该使用它或任何类似的解决方案:

defmodule RandomString do
  @chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  @max String.length(@chars)-1
  defp random_char do
    ndx = Enum.random 0..@max
    String.slice @chars, ndx..ndx
  end
  def len(len) do
    list = for _ <- :lists.seq(1,len), do: random_char
    List.foldl(list, "", fn(e,acc) -> acc <> e end)
  end
end

iex> RandomString.len 12
"Z7Qb3xwzlKKj"

现在,为什么你可能不应该使用它。您尚未透露的是您首先生成随机字符串的原因。我假设您出于某种目的想要唯一字符串的极有可能的情况。无论如何,几乎所有解决各种版本的解决方案都需要随机字符串&#34;问题使用有缺陷的规范来处理解决方案,即字符串长度。如果你为随机字符串指定字符串长度,你无疑会猜测你的真正需要,唯一性。

有两种主要方法可以获得严格的唯一性:确定性(不是随机的)和存储/比较(这是繁重的)。该怎么办?放弃鬼魂。改为使用概率唯一性。也就是说,接受一些(但是很小的)风险,你的字符串不会是唯一的。这是理解碰撞概率和熵有用的地方。

例如,考虑长度为12的字符串。可以随机生成多少而不重复?这个问题实际上是不明确的。让我们改一下吧。有多少可以随机生成,重复几率不到1十亿?大约254万。为什么?因为每个弦的承载能力约为71.5比特的熵。

但是你没有指明你需要产生254万个随机字符串的潜力,其风险可能低于十亿分之一的重复。你也没有指定你需要长度为12的字符串。希望你能看到前一个规范比猜测字符串长度更明确。

计算我们真正需要的熵量可能有点麻烦。 EntropyString可以提供帮助的地方。让我们假设您需要产生多达50万个ID,并且重复小于1万亿的风险。

iex> defmodule Id do
...>   use EntropyString, charset: charset64
...>   @bits entropy_bits(0.5e6, 1.0e12)
...>   def random, do: Id.random_string(@bits)
...> end
iex> Id.random
"tY0W9tyrq_P08"
哎呀,那里强调了你并不想要。 charset64包含URL和文件系统安全字符。出于效率原因,EntropyString仅使用功能为2个字符的字符集。

iex> defmodule Id do
...>   use EntropyString, charset: charset32
...>   @bits entropy_bits(0.5e6, 1.0e12)
...>   def random, do: Id.random_string(@bits)
...> end
iex> Id.random
"dTPmjTq7pgPjqBjT"

琴弦稍长,但可能更具视觉吸引力。更重要的是,在指定数量的字符串中重复的风险是明确的。不再猜测字符串长度。

答案 2 :(得分:0)

alphabet = 
  ?a..?z 
  |> Enum.concat(?A..?Z) 
  |> Enum.concat(?0..?9) 
  |> to_string 
  |> String.codepoints
len = 10

在这里,你有一个字母,字母a-z,A-Z,0-9作为代码点列表。

Enum.reduce((1..len), [], fn (_i, acc) -> [Enum.random(alphabet) | acc] end) 
|> Enum.join()

现在Enum.reduce会将字母表中的随机字符(Enum.random(alphabet))10次((1..len))添加到acc的列表中。然后列表被加入,你有一个长度为len的随机字符串。

答案 3 :(得分:0)

我会在编译时将可能的字符存储在二进制文件中,并在运行时从len次中选择一个随机字节。

defmodule A do
  @bytes Enum.concat([?a..?z, ?A..?Z, ?0..?9]) |> List.to_string
  def random(length) do
    for _ <- 1..length, into: <<>> do
      index = :rand.uniform(byte_size(@bytes)) - 1
      <<:binary.at(@bytes, index)>>
    end
  end
end

IO.inspect A.random(8)
IO.inspect A.random(16)

这应该是相当有效的,因为有效的字符集在编译时生成,:binary.at比从列表中选择第n个值({(1)vs O(n))更高效(Enum.random {{1}} 1}}列表)。