Как создать случайную строку длиной до определенной длины? - PullRequest
15 голосов
/ 18 июня 2010

Я хотел бы сгенерировать случайную строку (или серию случайных строк, допускаются повторения) длиной от 1 до n символов из некоторого (конечного) алфавита. Каждая строка должна быть одинаково вероятной (другими словами, строки должны быть равномерно распределены).

Требование единообразия означает, что подобный алгоритм не работает:

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

(псевдокод, rand(a, b) возвращает целое число от a до b включительно, каждое целое число равно вероятно)

Этот алгоритм генерирует строки с равномерно распределенными длинами, но фактическое распределение должно быть взвешено в сторону более длинных строк (строк с длиной 2 в 26 раз больше, чем с длиной 1 и т. Д.) Как я могу этого добиться

Ответы [ 9 ]

11 голосов
/ 18 июня 2010

Что вам нужно сделать, это сгенерировать свою длину, а затем строку в виде двух отдельных шагов. Сначала вам нужно будет выбрать длину, используя взвешенный подход. Вы можете рассчитать количество строк заданной длины l для алфавита k символов как k^l. Суммируйте их, и тогда у вас будет общее количество строк любой длины, ваш первый шаг - сгенерировать случайное число в диапазоне от 1 до этого значения, а затем скопировать его соответствующим образом. По модулю одной ошибки вы бы сломались на 26, 26 ^ 2, 26 ^ 3, 26 ^ 4 и так далее. Логарифм, основанный на количестве символов, будет полезен для этой задачи.

Если у вас есть длина, вы можете сгенерировать строку, как у вас выше.

7 голосов
/ 18 июня 2010

Хорошо, существует 26 возможностей для строки из 1 символа, 26 2 для строки из 2 символов и т. Д. До 26 26 возможностей для 26 символов строка.

Это означает, что для строки (N) -характера имеется в 26 раз больше возможностей, чем для строки (N-1) -характера. Вы можете использовать этот факт, чтобы выбрать свою длину:

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/14 длины 2 и 2/14 длины 1.

14 - это волшебная фигура: это сумма всех 2 n для n = 1 до максимальной длины. Итак, тестируем этот псевдокод выше с chars = 2 и maxlen = 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 включительно 8 из 14 возможных.

В противном случае sampspace становится sampspace - range [14 - 8 = 6], а range становится range / chars [8 / 2 = 4].

Тогда вторая итерация последнего цикла завершится с sz = 2, если val больше или равно sampspace - range [6 - 4 = 2]. Другими словами, для значений от 2 до 5 включительно 4 из 14 возможных.

В противном случае sampspace становится sampspace - range [6 - 4 = 2], а range становится range / chars [4 / 2 = 2].

Тогда третья итерация последнего цикла завершится с sz = 1, если val больше или равно sampspace - range [2 - 2 = 0]. Другими словами, для значений от 0 до 1 включительно, 2 из 14 возможностей (эта итерация будет всегда завершаться, так как значение должно быть больше или равно нулю.


В ретроспективе это второе решение похоже на кошмар. По моему личному мнению, я бы выбрал первое решение из-за его простоты и избежания возможности довольно больших чисел.

4 голосов
/ 18 июня 2010

Опираясь на мой комментарий, опубликованный в ответ на ФП:

Я бы посчитал это упражнением в конвертации базы.Вы просто генерируете «случайное число» в «базе 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 голосов
/ 18 июня 2010

Вместо того, чтобы выбирать длину с равномерным распределением, взвесьте ее в соответствии с количеством строк данной длины. Если ваш алфавит имеет размер m, то есть m x строк размера x и (1-m n + 1 ) / (1-m) строк длины n или менее. Вероятность выбора строки длиной x должна составлять m x * (1-m) / (1-m n + 1 ).

Edit:

Относительно переполнения - использование числа с плавающей запятой вместо целых будет расширять диапазон, поэтому для алфавита с 26 символами и с плавающей запятой одинарной точности прямой расчет веса не должен переполняться при n <26. </p>

Более надежный подход - работать с ним итеративно. Это также должно минимизировать последствия недостаточного расхода:

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;
}
2 голосов
/ 18 июня 2010

Редактировать: Этот ответ не совсем правильный.Смотрите внизу для опровержения.Я оставлю это пока в надежде, что кто-то может придумать вариант, который это исправляет.

Это можно сделать без отдельного расчета длины, что, как отмечали другие,требует увеличения числа до большой степени, и, как правило, мне кажется, что это грязное решение.

Доказать, что это правильно, немного сложно, и я не уверен, что доверяю своим объяснительным силам, чтобы прояснить это, но терпеть меня.В целях объяснения мы генерируем строки длиной не более n из алфавита a из |a| символов.

Во-первых, представьте, что у вас максимальная длина n,и вы уже решили, что генерируете строку по крайней мере n-1.Должно быть очевидно, что есть |a|+1 одинаково вероятные возможности: мы можем сгенерировать любой из |a| символов из алфавита, или мы можем выбрать для завершения n-1 символов.Чтобы решить, мы просто выбираем случайное число x между 0 и |a| (включительно);если x равно |a|, мы заканчиваем n-1 символами;в противном случае мы добавляем символ 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.Если наш рекурсивный вызов что-либо возвращает, мы знаем, что строка должна быть длиной не менее 1031 *, и мы генерируем собственный символ из алфавита и возвращаем его.Однако, если рекурсивный вызов ничего не возвращает, мы знаем, что строка не длиннее 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
0 голосов
/ 18 июня 2010

Матье: Ваша идея не работает, потому что строки с пробелами по-прежнему генерируются с большей вероятностью. В вашем случае, при n = 4, вы можете получить строку 'ab', сгенерированную как 'a' + 'b' + '' + '' или '' + 'a' + 'b' + '', или другие комбинации , Таким образом, не все строки имеют одинаковую вероятность появления.

0 голосов
/ 18 июня 2010

Моя идея по этому поводу выглядит так:

у вас есть строка длиной 1-n. Есть 26 возможных строк длиной 1, строки длиной 26 * 26 и так далее. Вы можете узнать процент каждой строки длины от всех возможных строк. Например, процент строки одинарной длины равен

((26 / (TOTAL_POSSIBLE_STRINGS_OF_ALL_LENGTH)) * 100).

аналогичным образом вы можете узнать процентную долю строк другой длины. Пометьте их в числовой строке от 1 до 100. Т.е. предположим, что процент строки одинарной длины равен 3, а строки двойной длины равен 6, тогда строка одинарной длины строки чисел находится в диапазоне 0-3, а строка двойной длины - 3-9 и т. Д. Теперь возьмите случайное число от 1 до 100. Определите диапазон, в котором лежит это число. Я имею в виду, например, что число, которое вы выбрали случайным образом, равно 2. Теперь это число лежит в диапазоне от 0 до 3, поэтому идите на 1 строку длины или, если случайное выбранное число равно 7, затем перейдите к строке двойной длины.

Таким образом, вы можете видеть, что длина каждой выбранной строки будет пропорциональна проценту от общего числа строк этой длины, внесенных во все возможные строки.

Надеюсь, мне ясно. Отказ от ответственности: я не рассматривал вышеупомянутое решение, кроме одного или двух. Так что, если оно совпадает с каким-то одним решением, это будет просто шанс. Также я буду рад всем советам и позитивной критике и поправлю меня, если я ошибаюсь.

Спасибо и уважение Mawia

0 голосов
/ 18 июня 2010

Лично я бы сделал это так:

Допустим, ваш алфавит содержит Z символов. Тогда количество возможных строк для каждой длины L равно:

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

... и т. Д.

Теперь допустим, что максимальная желаемая длина N. Тогда общее число возможных строк длиной от 1 до N, которое может сгенерировать ваша функция, будет суммой геометрической последовательности :

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

Давайте назовем это значение S. Тогда вероятность генерации строки любой длины L должна быть:

(Z ^ L) / S

ОК, хорошо. Это все хорошо и хорошо; но как мы генерируем случайное число при неравномерном распределении вероятностей?

Короткий ответ: нет. Получить библиотеку, чтобы сделать это для вас. Я разрабатываю в основном на .NET, поэтому я мог бы обратиться к Math.NET .

Тем не менее, это действительно не , поэтому трудно придумать элементарный подход к выполнению этого самостоятельно.

Вот один из способов: взять генератор, который дает вам случайное значение в пределах известного равномерного распределения, и назначить диапазоны в пределах этого распределения размеров в зависимости от вашего желаемого распределения. Затем интерпретируйте случайное значение, предоставленное генератором, определив, в какой диапазон оно попадает.

Вот пример на C # одного способа реализации этой идеи (прокрутите до конца, например, вывод):

RandomStringGenerator класс

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;
    }
}

Test

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%.
0 голосов
/ 18 июня 2010
// 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 намного лучше. Меньше кода тоже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...