我正在使用以下代码[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)
但是,有时这个随机字符串不包含数字或大写字符。你能帮我找到一种方法来生成一个唯一的随机字符串,该字符串至少需要一个数字、一个大写和一个小写字符吗?
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)