如何生成长度达到一定长度的随机字符串?

时间:2010-06-18 01:37:33

标签: algorithm language-agnostic string

我想生成一个随机字符串(或一系列随机字符串,允许重复),长度介于1和n个字符之间。每个字符串应该具有相同的可能性(换句话说,字符串应该是均匀分布的)。

均匀性要求意味着这样的算法不起作用:

alphabet = "abcdefghijklmnopqrstuvwxyz"
len = rand(1, n)
s = ""
for(i = 0; i < len; ++i)
    s = s + alphabet[rand(0, 25)]

(伪代码,rand(a, b)返回ab之间的整数,包括每个整数的可能性。

此算法生成具有均匀分布长度的字符串,但实际分布应该朝向更长的字符串加权(长度为2的字符串数量是长度为1的字符串的26倍,依此类推。)如何实现此目的?

9 个答案:

答案 0 :(得分:11)

您需要做的是生成您的长度,然后将您的字符串作为两个不同的步骤。您需要首先使用加权方法选择长度。您可以将l符号字母的给定长度k的字符串数计算为k^l。将它们相加,然后你得到任意长度的字符串总数,你的第一步是生成一个介于1和该值之间的随机数,然后相应地对其进行bin。通过一个错误模数,您将在26,26 ^ 2,26 ^ 3,26 ^ 4等处打破。基于符号数的对数对此任务非常有用。

一旦你拥有了长度,那么就可以像上面那样生成字符串。

答案 1 :(得分:7)

好的,1个字符的字符串有26种可能性,2个字符的字符串有26个 2 ,而26个字符串可能有26个 26 的可能性。 -character string。

这意味着(N)字符串的可能性是(N-1)字符串的26倍。您可以使用该事实来选择长度:

def getlen(maxlen):
    sz = maxlen
    while sz != 1:
        if rnd(27) != 1:
            return sz
        sz--;
    return 1

我在上面的代码中使用了27,因为从“ab”中选择字符串的总样本空间是26个1字符的可能性和26个 2 2字符的可能性。换句话说,这个比例是1:26所以1个字符的概率是1/27(而不是我第一次回答的1/26)。

此解决方案不是完美,因为您多次调用rnd并且最好在可能的范围为26 N +26 N-1 +26 1 并根据返回的数字在那里的位置选择长度,但可能很难找到一个随机数生成器处理大数字(10个字符给你的可能范围26 10 + ... + 26 1 除非我做错了数学,否则是146,813,779,479,510 )。

如果您可以限制最大尺寸以使rnd功能在该范围内起作用,那么这样的事情应该是可行的:

def getlen(chars,maxlen):
    assert maxlen >= 1
    range = chars
    sampspace = 0
    for i in 1 .. maxlen:
        sampspace = sampspace + range
        range = range * chars
    range = range / chars
    val = rnd(sampspace)
    sz = maxlen
    while val < sampspace - range:
        sampspace = sampspace - range
        range = range / chars
        sz = sz - 1
    return sz

一旦你有了长度,我就会使用你当前的算法来选择填充字符串的实际字符。


进一步解释:

假设我们的字母表只包含“ab”。长度为3的可能设置为[ab](2),[ab][ab](4)和[ab][ab][ab](8)。所以有8/14的机会获得长度为3,长度为4的4/14和长度为1的2/14。

14是神奇的数字:它是所有2 n 的总和,n = 1到最大长度。因此,使用chars = 2maxlen = 3测试上面的伪代码:

    assert maxlen >= 1 [okay]
    range = chars [2]
    sampspace = 0
    for i in 1 .. 3:
        i = 1:
            sampspace = sampspace + range [0 + 2 = 2]
            range = range * chars [2 * 2 = 4]
        i = 2:
            sampspace = sampspace + range [2 + 4 = 6]
            range = range * chars [4 * 2 = 8]
        i = 3:
            sampspace = sampspace + range [6 + 8 = 14]
            range = range * chars [8 * 2 = 16]
    range = range / chars [16 / 2 = 8]
    val = rnd(sampspace) [number from 0 to 13 inclusive]
    sz = maxlen [3]
    while val < sampspace - range: [see below]
        sampspace = sampspace - range
        range = range / chars
        sz = sz - 1
    return sz

因此,从该代码开始,如果sz = 3大于或等于val,则最终循环的第一次迭代将以sampspace - range [14 - 8 = 6]退出。换句话说,对于值6到13,包括14种可能性中的8种。

否则,sampspace变为sampspace - range [14 - 8 = 6]range变为range / chars [8 / 2 = 4]

如果sz = 2大于或等于val,则最终循环的第二次迭代将以sampspace - range [6 - 4 = 2]退出。换句话说,对于值2到5,包括14种可能性中的4种。

否则,sampspace变为sampspace - range [6 - 4 = 2]range变为range / chars [4 / 2 = 2]

如果sz = 1大于或等于val,则最终循环的第三次迭代将以sampspace - range [2 - 2 = 0]退出。换句话说,对于值0到1(包括0和1),14种可能性中的2种(此迭代将始终退出,因为该值必须大于或等于零。


回想起来,第二种解决方案有点像噩梦。在我个人看来,我会寻求第一个解决方案,因为它简单,并避免相当大的数字。

答案 2 :(得分:4)

不是选择均匀分布的长度,而是根据给定长度的字符串数量来加权。如果您的字母表大小为m,则有m x 字符串,大小为x,(1-m n + 1 )/(1-m)字符串,长度为n或减。选择长度为x的字符串的概率应为m x *(1-m)/(1-m n + 1 )。

修改

关于溢出 - 使用浮点而不是整数将扩大范围,因此对于26个字符的字母和单精度浮点数,直接权重计算不应该溢出n <26。

更强大的方法是迭代处理它。这也应该最小化下溢的影响:

int randomLength() {
  for(int i = n; i > 0; i--) {
    double d = Math.random();
    if(d > (m - 1) / (m - Math.pow(m, -i))) {
      return i;
    }
  }
  return 0;
}

为了通过计算更少的随机数来提高效率,我们可以通过在多个地方分割间隔来重复使用它们:

int randomLength() {
  for(int i = n; i > 0; i -= 5) {
    double d = Math.random();
    double c = (m - 1) / (m - Math.pow(m, -i))
    for(int j = 0; j < 5; j++) {
      if(d > c) {
        return i - j;
      }
      c /= m;
    }
  }
  for(int i = n % 0; i > 0; i--) {
    double d = Math.random();
    if(d > (m - 1) / (m - Math.pow(m, -i))) {
      return i;
    }
  }
  return 0;
}

答案 3 :(得分:4)

根据我的评论发布,作为对OP的回复:

  

我认为这是一个基础练习   转换。你只是生成一个   “基地26”中的“随机数”,其中   a = 0且z = 25。对于随机字符串   长度n,生成1之间的数字   和26 ^ n。从基数10转换为基数   26,使用你选择的符号   字母表。

这是一个PHP实现。我不保证这里没有一两个错误,但是任何这样的错误都应该是次要的:

<?php
$n = 5;

var_dump(randstr($n));

function randstr($maxlen) {
        $dict = 'abcdefghijklmnopqrstuvwxyz';
        $rand = rand(0, pow(strlen($dict), $maxlen));
        $str = base_convert($rand, 10, 26);
        //base convert returns base 26 using 0-9 and 15 letters a-p(?)
        //we must convert those to our own set of symbols
        return strtr($str, '1234567890abcdefghijklmnopqrstuvwxyz', $dict);
}

答案 4 :(得分:2)

编辑:这个答案不太正确。查看底部是否有防伪功能。我现在就把它留下来,希望有人能想出一个修复它的变种。

有可能在不单独计算长度的情况下这样做 - 正如其他人所指出的那样,这需要将数字提高到一个大功率,对我来说通常看起来像是一个混乱的解决方案。

证明这是正确的有点困难,我不确定我是否相信我的说明权力能够说清楚,但请耐心等待。出于解释的目的,我们从n个字符的a字母生成最多|a|个字符串。

首先,假设您的最大长度为n,并且您已经确定要生成至少长度为n-1的字符串。很明显,|a|+1同样存在可能性:我们可以从字母表中生成任何|a|字符,或者我们可以选择以n-1个字符终止。要做出决定,我们只需在x0(包括)之间选择一个随机数|a|;如果x|a|,我们会以n-1个字符终止;否则,我们将a的x th 字符附加到字符串。以下是Python中此过程的简单实现:

def pick_character(alphabet):
  x = random.randrange(len(alphabet) + 1)
  if x == len(alphabet):
    return ''
  else:
    return alphabet[x]

现在,我们可以递归地应用它。要生成字符串的k th 字符,我们首先尝试在k之后生成字符。如果我们的递归调用返回任何内容,那么我们知道字符串应该至少为k长度,并且我们从字母表中生成我们自己的字符并返回它。但是,如果递归调用没有返回任何内容,我们知道字符串不会超过k,并且我们使用上面的例程来选择最终字符或没有字符。以下是Python中的实现:

def uniform_random_string(alphabet, max_len):
  if max_len == 1:
    return pick_character(alphabet)
  suffix = uniform_random_string(alphabet, max_len - 1)
  if suffix:
    # String contains characters after ours
    return random.choice(alphabet) + suffix
  else:
    # String contains no characters after our own
    return pick_character(alphabet)

如果您怀疑此函数的一致性,您可以尝试反驳它:建议一个字符串,有两种不同的生成方式,或者没有。如果没有这样的字符串 - 唉,我没有这个事实的有力证据,虽然我很确定它是真的 - 并且鉴于个别选择是统一的,那么结果也必须选择具有均匀概率的任何字符串

正如所承诺的那样,与迄今为止发布的所有其他解决方案不同,不需要向大国提供数字;存储结果不需要任意长度整数或浮点数,至少在我看来,有效性很容易证明。到目前为止,它还比任何完全指定的解决方案短。 ;)

如果有人想要充分证明功能的一致性,我将非常感激。

编辑:拒绝,由朋友提供:

dato: so imagine alphabet = 'abc' and n = 2
dato: you have 9 strings of length 2, 3 of length 1, 1 of length 0
dato: that's 13 in total
dato: so probability of getting a length 2 string should be 9/13
dato: and probability of getting a length 1 or a length 0 should be 4/13
dato: now if you call uniform_random_string('abc', 2)
dato: that transforms itself into a call to uniform_random_string('abc', 1)
dato: which is an uniform distribution over ['a', 'b', 'c', '']
dato: the first three of those yield all the 2 length strings
dato: and the latter produce all the 1 length strings and the empty strings
dato: but 0.75 > 9/13
dato: and 0.25 < 4/13

答案 5 :(得分:0)

// Note space as an available char
alphabet = "abcdefghijklmnopqrstuvwxyz "

result_string = ""

for( ;; )
{
    s = ""

    for( i = 0; i < n; i++ )
        s += alphabet[rand(0, 26)]

    first_space = n;

    for( i = 0; i < n; i++ )
        if( s[ i ] == ' ' )
        {
            first_space = i;
            break;
        }

    ok = true;

    // Reject "duplicate" shorter strings
    for( i = first_space + 1; i < n; i++ )
        if( s[ i ] != ' ' )
        {
            ok = false;
            break;
        }

    if( !ok )
        continue;

    // Extract the short version of the string
    for( i = 0; i < first_space; i++ )
        result_string += s[ i ];

    break;
}

编辑:我忘了禁用0长度字符串,这将需要更多代码,我现在没有时间添加。

编辑:在考虑我的答案如何不扩展到大n(花了太长时间才能获得幸运并找到一个可接受的字符串)之后,我更喜欢paxdiablo的答案。也减少了代码。

答案 6 :(得分:0)

就我个人而言,我这样做:

假设您的字母表中包含Z个字符。然后,每个长度L的可能字符串数为:

L | Z
--------------------------
1 | 26
2 | 676 (= 26 * 26)
3 | 17576 (= 26 * 26 * 26)

......等等。

现在假设您所需的最大长度为N。然后,您的函数可以生成的长度为1到N的可能字符串总数为the sum of a geometric sequence

(1 - (Z ^ (N + 1))) / (1 - Z) 

让我们调用此值S。那么生成任意长度L的字符串的概率应为:

(Z ^ L) / S
好的,好的。这一切都很好;但是如果给出非均匀概率分布,我们如何生成随机数呢?

简短的回答是:你没有。找一个图书馆为你做这件事。我主要在.NET中开发,所以我可能会转向Math.NET

那就是说,它真的不是所以很难想出一个基本的方法来自己做这件事。

这是一种方法:使用一个生成器,在已知的统一分布中为您提供随机值,并根据您所需的分布在该分布范围内分配范围。然后通过确定它落入哪个范围来解释生成器提供的随机值。

以下是C#中您可以实现此想法的一种示例(滚动到底部以获取示例输出):

RandomStringGenerator class

public class RandomStringGenerator
{
    private readonly Random _random;
    private readonly char[] _alphabet;

    public RandomStringGenerator(string alphabet)
    {
        if (string.IsNullOrEmpty(alphabet))
            throw new ArgumentException("alphabet");

        _random = new Random();
        _alphabet = alphabet.Distinct().ToArray();
    }

    public string NextString(int maxLength)
    {
        // Get a value randomly distributed between 0.0 and 1.0 --
        // this is approximately what the System.Random class provides.
        double value = _random.NextDouble();

        // This is where the magic happens: we "translate" the above number
        // to a length based on our computed probability distribution for the given
        // alphabet and the desired maximum string length.
        int length = GetLengthFromRandomValue(value, _alphabet.Length, maxLength);

        // The rest is easy: allocate a char array of the length determined above...
        char[] chars = new char[length];

        // ...populate it with a bunch of random values from the alphabet...
        for (int i = 0; i < length; ++i)
        {
            chars[i] = _alphabet[_random.Next(0, _alphabet.Length)];
        }

        // ...and return a newly constructed string.
        return new string(chars);
    }

    static int GetLengthFromRandomValue(double value, int alphabetSize, int maxLength)
    {
        // Looping really might not be the smartest way to do this,
        // but it's the most obvious way that immediately springs to my mind.
        for (int length = 1; length <= maxLength; ++length)
        {
            Range r = GetRangeForLength(length, alphabetSize, maxLength);
            if (r.Contains(value))
                return length;
        }

        return maxLength;
    }

    static Range GetRangeForLength(int length, int alphabetSize, int maxLength)
    {
        int L = length;
        int Z = alphabetSize;
        int N = maxLength;

        double possibleStrings = (1 - (Math.Pow(Z, N + 1)) / (1 - Z));
        double stringsOfGivenLength = Math.Pow(Z, L);
        double possibleSmallerStrings = (1 - Math.Pow(Z, L)) / (1 - Z);

        double probabilityOfGivenLength = ((double)stringsOfGivenLength / possibleStrings);
        double probabilityOfShorterLength = ((double)possibleSmallerStrings / possibleStrings);

        double startPoint = probabilityOfShorterLength;
        double endPoint = probabilityOfShorterLength + probabilityOfGivenLength;

        return new Range(startPoint, endPoint);
    }
}

Range struct

public struct Range
{
    public readonly double StartPoint;
    public readonly double EndPoint;

    public Range(double startPoint, double endPoint)
        : this()
    {
        this.StartPoint = startPoint;
        this.EndPoint = endPoint;
    }

    public bool Contains(double value)
    {
        return this.StartPoint <= value && value <= this.EndPoint;
    }
}

测试

static void Main(string[] args)
{
    const int N = 5;
    const string alphabet = "acegikmoqstvwy";
    int Z = alphabet.Length;

    var rand = new RandomStringGenerator(alphabet);

    var strings = new List<string>();
    for (int i = 0; i < 100000; ++i)
    {
        strings.Add(rand.NextString(N));
    }

    Console.WriteLine("First 10 results:");
    for (int i = 0; i < 10; ++i)
    {
        Console.WriteLine(strings[i]);
    }

    // sanity check
    double sumOfProbabilities = 0.0;

    for (int i = 1; i <= N; ++i)
    {
        double probability = Math.Pow(Z, i) / ((1 - (Math.Pow(Z, N + 1))) / (1 - Z));
        int numStrings = strings.Count(str => str.Length == i);

        Console.WriteLine("# strings of length {0}: {1} (probability = {2:0.00%})", i, numStrings, probability);

        sumOfProbabilities += probability;
    }

    Console.WriteLine("Probabilities sum to {0:0.00%}.", sumOfProbabilities);

    Console.ReadLine();
}

输出:

First 10 results:
wmkyw
qqowc
ackai
tokmo
eeiyw
cakgg
vceec
qwqyq
aiomt
qkyav
# strings of length 1: 1 (probability = 0.00%)
# strings of length 2: 38 (probability = 0.03%)
# strings of length 3: 475 (probability = 0.47%)
# strings of length 4: 6633 (probability = 6.63%)
# strings of length 5: 92853 (probability = 92.86%)
Probabilities sum to 100.00%.

答案 7 :(得分:0)

我对此的想法是:

你有1-n长度的字符串。有26个可能的1长度字符串,26 * 26 2长度字符串,依此类推。 您可以找出总可能字符串中每个长度字符串的百分比。例如,单个长度字符串的百分比就像

((26 /(TOTAL_POSSIBLE_STRINGS_OF_ALL_LENGTH))* 100)。

类似地,您可以找出其他长度字符串的百分比。 将它们标记在1到100之间的数字行上。假设单个长度字符串的百分比为3,双倍长度字符串为6,则数字行单个长度字符串位于0-3之间,而双倍长度字符串位于3-9之间,依此类推。 现在取一个介于1到100之间的随机数。找出这个数字所在的范围。我的意思是假设例如你随机选择的数字是2.现在这个数字介于0-3之间,所以去1长度字符串或者随机选择的数字是7,然后选择双倍长度字符串。

以这种方式,您可以看到每个选择的字符串的长度将与该长度字符串的总数占所有可能字符串的百分比成比例。

希望我很清楚。 免责声明:除了一两个解决方案之外,我没有经历过上述解决方案。所以,如果它与某个解决方案匹配,那将纯属机会。 此外,如果我错了,我会欢迎所有的建议和积极的批评并纠正我。

谢谢并尊重 Mawia

答案 8 :(得分:0)

Matthieu:你的想法不起作用,因为更有可能生成带有空格的字符串。在你的情况下,当n = 4时,你可以将字符串'ab'生成为'a'+'b'+''+''或''+'a'+'b'+'',或其他组合。因此,并非所有字符串都有相同的出现机会。