Ruby 中需要带有字母数字的唯一随机字符串

Sno*_*Fox 3 ruby random

我正在使用以下代码[A-Z a-z 0-9]在 Ruby 中生成一个唯一的 10 个字符的随机字符串:

random_code = [*('a'..'z'),*('0'..'9'),*('A'..'Z')].shuffle[0, 10].join
Run Code Online (Sandbox Code Playgroud)

但是,有时这个随机字符串不包含数字或大写字符。你能帮我找到一种方法来生成一个唯一的随机字符串,该字符串至少需要一个数字、一个大写和一个小写字符吗?

Car*_*and 5

down   = ('a'..'z').to_a
up     = ('A'..'Z').to_a
digits = ('0'..'9').to_a
all    = down + up + digits
[down.sample, up.sample, digits.sample].
  concat(7.times.map { all.sample }).
  shuffle.
  join
  #=> "TioS8TYw0F"
Run Code Online (Sandbox Code Playgroud)

[编辑:以上反映了对问题的误解。不过,我会离开它。不让任何字符出现多次:

def rnd_str
  down   = ('a'..'z').to_a
  up     = ('A'..'Z').to_a
  digits = ('0'..'9').to_a
  [extract1(down), extract1(up), extract1(digits)].
    concat(((down+up+digits).sample(7))).shuffle.join
end

def extract1(arr)
  i = arr.size.times.to_a.sample
  c = arr[i]
  arr.delete_at(i)
  c
end

rnd_str #=> "YTLe0WGoa1" 
rnd_str #=> "NrBmAnE9bT"
Run Code Online (Sandbox Code Playgroud)

down.sample.shift(等)会比 更紧凑extract1,但效率低下实在是难以忍受。

如果您不想重复随机字符串,只需保留您生成的字符串列表。如果您生成列表中的另一个,则丢弃它并生成另一个。但是,您不太可能需要生成任何额外的内容。例如,如果您生成 100 个随机字符串(满足至少一个小写字母、大写字母和数字的要求),则出现一个或多个重复字符串的几率约为 700,000 分之一:

t = 107_518_933_731
n = t+1
t = t.to_f
(1.0 - 100.times.reduce(1.0) { |prod,_| prod * (n -= 1)/t }).round(10)
  #=> 1.39e-07
Run Code Online (Sandbox Code Playgroud)

其中t = C(62,10)C(62,10)定义如下。

替代

有一种非常简单的方法可以做到这一点,结果证明是非常有效的:只采样而不替换,直到找到满足至少小写字母、一个大写字母和一个数字的要求的样本。我们可以这样做:

DOWN   = ('a'..'z').to_a
UP     = ('A'..'Z').to_a
DIGITS = ('0'..'9').to_a
ALL    = DOWN + UP + DIGITS

def rnd_str
  loop do
    arr = ALL.sample(10)
    break arr.shuffle.join unless (DOWN&&arr).empty? || (UP&&arr).empty? || 
    (DIGITS&&arr).empty?
  end
end

rnd_str #=> "3jRkHcP7Ge" 
rnd_str #=> "B0s81x4Jto
Run Code Online (Sandbox Code Playgroud)

在找到“好的”样本之前,我们平均必须拒绝多少个样本?事实证明(如果您真的非常感兴趣请参见下文)获得“坏”字符串的概率(即,从 的 62 个元素中随机选择 10 个字符all,没有替换,没有小写字母,没有大写字母或者没有数字,只有大约 0.15. (15%)。这意味着在找到好的样本之前,85% 的时间不会拒绝坏样本。

事实证明,在采样一个好的字符串之前,将被采样的坏字符串的预期数量是:

0.15/0.85 =~ 0.17
Run Code Online (Sandbox Code Playgroud)

下面显示了上述概率是如何得出的,如果有人感兴趣的话。

假设n_down可以抽取 10 个没有小写字母的样本的方法数:

n_down = C(36,10) = 36!/(10!*(36-10)!)
Run Code Online (Sandbox Code Playgroud)

其中(二项式系数)C(36,10)等于 36 个“事物”的组合数,可以一次“取”10 个,等于:

C(36,10) = 36!/(10!*(36-10)!) #=> 254_186_856
Run Code Online (Sandbox Code Playgroud)

相似地,

n_up = n_down #=> 254_186_856
Run Code Online (Sandbox Code Playgroud)

n_digits = C(52,10) #=> 15_820_024_220
Run Code Online (Sandbox Code Playgroud)

我们可以将这三个数字相加得到:

n_down + n_up + n_digits #=> 16_328_397_932
Run Code Online (Sandbox Code Playgroud)

这几乎是(但不完全是)绘制 10 个字符的方法数,无需替换,不包含小写字母字符、大写字母或数字。“不完全”,因为有一些重复计算。必要的调整如下:

n_down + n_up + n_digits - 2*C(26,10) - 3
  #=> 16_317_774_459
Run Code Online (Sandbox Code Playgroud)

为了获得从 62 人的总体中抽取 10 个样本的概率,没有替换,没有小写字母,没有大写字母或没有数字,我们用这个数字除以从 62 个没有替换的情况下可以抽取 10 个字符的方式总数替代品:

(16_317_774_459.0/c(62,10)).round(2)
  #=> 0.15
Run Code Online (Sandbox Code Playgroud)