如何创建n个随机长度的字符串,其总和等于给定的数量?

时间:2017-06-05 17:03:34

标签: arrays algorithm random

我正在尝试创建一个算法来创建随机长度的n个字符串,其总和等于给定的数量。

一个让它更清晰的例子:

total = 20;
n = 7;

strings = ['aaaa', 'a', 'aaaaaaa', 'aa', 'aaa', 'aa', 'a'];

所以我有7个随机长度的字符串,它们各自长度的总和是(除非我计算错误)20。

直到现在我想出了这个递归函数:

gaps = [];
function createGapsArray(total, n) {
    if (n == 1) {
        var gapLength = total;
    } else {
        var gapLength = getRandomInt(1, total / 2);
    }

  var gap = "";
  for (var i = 0; i < gapLength; i++) {
    gap += "a";
  }
  gaps.push(gap);

  if (n > 1 && total > 0) {
    createGapsArray(total - gapLength, --n);
  }
}

哪个不起作用。通常它会在生成我想要的所有n个段之前完成。通过我所做的少数测试,似乎将总数除以4而不是2,就可以完成工作。像:

var gapLength = getRandomInt(1, total / 4);

但是这种约束的选择只是随意的。我想知道是否有更好的方法。

另外,我知道通过我的方法,算法可能会在最初生成更长的段,而在结束时生成更小的段。我不介意均匀分布,但这并不是什么大问题,因为对于我需要的东西,我可以在完成后简单地将数组洗牌。

3 个答案:

答案 0 :(得分:3)

这是一个类似的问题,即“找到总数为N的k个数的随机集合”。这个答案的原始版本使用了一个简单的解决方案,如果数字是连续的(即浮点数),则是无偏的:在[0, N]范围内生成k-1个数字,对它们进行排序,在开头放0,在结束,然后找到连续元素之间的差异。但由于没有小数字符,我们需要数字为整数,并且上述算法偏向于包含0的集合(在连续情况下有一个无穷小的概率为0,但在离散情况下它是重要的)。

用于生成非空整数解的无偏解决方案是在包含范围[1,N-1]中找到整数的随机(k-1)组合。要找到随机组合,请使用范围随机shuffle的第一个k-1元素(使用Fisher-Yates shuffle)。然后对组合进行分类(如果需要)并预先加0;这些值是每个字符串的起始位置(以便下一个值是结束位置。)

这不会产生空的子串,因为每个子串都有一个唯一的起点。要包含空子串,请使用上面的N + k而不是N,然后将每个范围缩小1:如果索引已排序,则可以通过从 i中减去 i 来实现 th index。

在Python中:

from random import sample
def random_split(str, k):
    v = [0] + sorted(sample(range(1, len(str)), k-1)) + [len(str)]
    return [str[v[i]:v[i+1]] for i in range(k)]

def random_split_allow_empty(str, k):
    v = [0] + sorted(sample(range(1, len(str)+k), k-1)) + [len(str)+k]
        return [str[v[i]-i:v[i+1]-i-1] for i in range(k)]

在Javascript中:

function shuffle(vec, k) {
  for (let i = 0; i < k; ++i) {
    let r = i + Math.floor(Math.random() * (vec.length - i));
    let t = vec[r];
    vec[r] = vec[i];
    vec[i] = t;
  }
  return vec;
}

function random_partition(N, k) {
  let v = [];
  for (let i = 1; i < N; ++i) v[i-1] = i;
  shuffle(v, k - 1);
  v[k-1] = 0;
  return v.slice(0, k).sort((a,b)=>a-b);
}

function random_split(s, k) {
  return random_partition(s.length, k).map(
    (v, i, a) => s.slice(v, a[i+1]));
}

function random_split_allow_empty(s, k) {
  return random_partition(s.length + k, k).map((v,i)=>v-i).map(
    (v, i, a) => s.slice(v, a[i+1]));
}

答案 1 :(得分:2)

严格地说,你不能这样做,因为你至少在最后一个违反所需随机属性的“字符串”中添加了约束。当然,你需要多严格地解释随机性的要求在很大程度上取决于你在更大的问题领域所做的事情。

您可以做的是,从所需长度的初始字符串开始,然后迭代生成随机分割点,直到您拥有所需数量的片段。要真正随机,你必须允许这些片段中的一些是空的(例如,如果你随机选择一个单字符片段进行拆分会发生什么?),但这可能会在没有做太多暴力的情况下解决整体结果的随机性......

答案 2 :(得分:0)

想到它的方法是拥有长度等于总数的字符串。我将以20为例:

string: aaaaaaaaaaaaaaaaaaaa
index:  01234567890123456789

然后,您在0total之间生成N-1个随机数,这些随机数对应于您应该剪切字符串以生成N个不同字符串的位置。

让我们说数字是5,8,13,15,1,3。这些将是剪切字符串的指数:

string: a aa aa aaa aaaaa aa aaaaa
index:  0|12|34|567|89012|34|56789

这与生成N-1个随机数,排序它们,在列表的开头添加0和在结尾添加total并获取差异相同:

var numbers = [0];
for (var i = 0; i < N-1; i++) numbers.push(getRandomInt(0, total));
numbers.push(total);
numbers.sort();

var strings = [];
for (var i = 0; i < N; i++)
  strings.push(new Array(numbers[i + 1] - numbers[i]).join('a'));

这将给出统一的分布。

请注意,如果您只想要非空字符串,则不应存在重复的随机值。