寻找一种算法以(伪)随机顺序吐出一系列数字

时间:2009-04-09 03:46:39

标签: algorithm language-agnostic math sequence

假设我有一系列数字: {n,n + 1,n + 2,... n + m}

如果没有提前存储数字我想创建一个函数f(),给定序列{1,2,3,... m}将随机吐出原始集合(或至少伪随机)订单。

例如假设我的序列是{10,11,12,13,14,15,16,17}

   f(1) could yield 14
   f(2) could yield 17
   f(3) could yield 13
   f(4) could yield 10
   f(5) could yield 16
   f(6) could yield 15
   f(7) could yield 11
   f(8) could yield 12

在过去的某个时刻,一位同事向我展示了一种能够做到这一点的数学算法,但是我已经忘记了除了存在之外几乎所有关于它的事情。我记得你必须事先得到序列,并从函数中使用的序列中生成一些常量。对于那些想知道的人,我遗憾地失去了与那位同事的联系。

这个question's答案看起来与我想要的很接近,但我不确定答案是否允许我提前将输出约束到特定序列。


修改

为了澄清一点,我不想存储原始序列或改组序列。我想从原始序列生成一个函数f()。

令人沮丧的是,我已经看到了这一点,我只是记不起来,谷歌再次找到它。

Fisher-Yates算法非常适合置换或改组卡片,但它不是我想要的。

10 个答案:

答案 0 :(得分:6)

有一个简单的函数可以为给定的[0..m-1]生成m的排列。只需选择一个k的数字,相对于m的素数,然后f(i)=(k*i) mod m。这总是会产生一个排列(0<=i<m上没有重复)。如果k大于m,则效果会更好。

例如,m = 20,设k = 137(Python代码,%表示模数):

 >>> [(137*i) % 20 for i in range(20)]
 [0, 17, 14, 11, 8, 5, 2, 19, 16, 13, 10, 7, 4, 1, 18, 15, 12, 9, 6, 3]

这是一个非常简单的PRNG,不保证其统计属性。

答案 1 :(得分:3)

你的问题有点令人困惑,因为它听起来你想要恢复所有原始序列,但是你有4和8映射到10,没有映射到12。

如果你真的认为这是一个1:1的映射,那么你要找的是原始集的随机排列。有或没有先收集集合的方法(但你需要生成它的东西,或跟踪你的位置)。

另外,请注意n并不重要。您可以随时使用0,1,2,... m,然后在需要时将n添加到所有内容中。

假设我已经正确地解释了这一点并且您实际上正在寻找一种随机排列算法(即随机排列,通过类比称为shuffle来改组一副牌),请查看Fisher-Yates

[编辑] 好的,根据您的更新,您遇到的问题是:您不希望显式编码排列,但您必须以某种方式对其进行编码以构造f。最简单的方法就是将置换索引实际存储在数组中,但如果您不想因某种原因(例如太大)而这样做,则可以通过各种方式对其进行编码。虽然没有免费的午餐,因为这有多么简单的信息理论限制。无论如何,你可以从查找“编码排列”的工作中获得一些想法,比如this paper

答案 2 :(得分:3)

这个问题类似于改组一副(m + 1)张牌,编号为[n,...,n + m]。请注意,编号(因而n)并不重要;重要的是,我们可以将卡分开。 (如果需要,您可以稍后再添加n。)

为了做你想做的事,你可以执行Fisher-Yates shufflejust keep track of which indices have been selected进行改组。这将允许您根据要求避免存储值本身的另一个副本。

答案 3 :(得分:0)

这是我自己的一种伪造语言的伪代码:

function f(array a)
    array newArray
    while a.size() == 0
        int position = randomNumber(1 to a.size())
        int removedNumber = a[position]
        a.remove(position)
        newArray.insertAtEnd(removedNumber)
    end while
    return newArray

答案 4 :(得分:0)

将初始值添加到列表中。
然后,使用随机数在列表的当前大小范围内选择一个新的索引值。
使用该索引选择然后从列表中删除该数字。

有人已经指出,这类似于拥有一副牌,然后一次随机删除一张牌。

答案 5 :(得分:0)

如果您想要1:1的映射,请按照其他答案中的提及与Fisher-Yates一起使用。

如果您不关心1:1映射,并且您只需要所有结果值来自给定序列(可能重复),那么您可以使用具有指定范围的随机函数。

例如,在C ++中,您可以通过以下方式使用rand() -

result = rand() % (m+1) + n

所以对你的例子来说,

result = rand() % 8 + 10

会产生10到17之间的整数。

答案 6 :(得分:0)

您可以fit a polynomial到所选序列;我想这就是你的同事给你看的。但是,与仅仅记住排列相比,它不会节省空间。

答案 7 :(得分:0)

根据my previous answer,您可以使用分组密码和xor折叠生成前n个整数的排列。

答案 8 :(得分:0)

您的输入由f(x) => x + 9描述,或者更为一般f(x) => n - 1 + xx从1开始。

您链接到another question,其中描述了将x映射到随机值r(x)的函数0 <= r(x) <= m

因此f(r(x) + 1)(r(x) + n)应该为您提供所需的价值。

对于小m,您还应该能够通过跟踪和错误找到标准随机数生成器的种子,如果您不采用mod m+1,则会生成m+1个不同的值我想编码你自己的发电机。

答案 9 :(得分:0)

如果不在某处存储原始函数的结果,则无法返回值。推理:

您的随机数生成器会告诉您从原始序列返回这些值:5,11th,3rd。

所以你跳过前四个值,返回第5个,跳过另外5个,返回第11个......现在你如何在没有保存的情况下返回第3个?

你可以逃脱的最接近的事情是创建一个列表并附加你跳过的所有值,但这听起来很尴尬,可能不值得努力。此外,在洗牌算法返回非常大且非常小的值的情况下(在这种情况下,您将有效地将大多数值复制到列表中,首先,您要避免),这将非常慢。

我休息一下。