如何进行一次接受一个值的置换函数?

时间:2013-05-07 09:56:45

标签: math

我正在寻找一个从0,1 .... N区间接受1个数字的函数,并从同一个区间返回一个置换值。

0,1,2,3,4,5和f(x)的例子是:

f(0)=5;
f(1)=1;
f(2)=0;
f(3)=4;
f(4)=2;
f(5)=3;

从我的研究/理解,这是一个循环群,其中f(x)是它的全部基础。 如果已经找到函数f(x)= 911 * x%N作为我想要的示例,则使用它时会出现模式。 911是一个大的素数,通过改变它,我得到另一种排列,但仍然,模式出现。我希望结果是随机的。

我知道我可以使用类似[0,1,2 ... N] .shuffle()之类的东西预生成排列,但在我的情况下我不能做这样的事情。

我有一个服务,它输入一个数值并返回置换值/位置。 N设置为服务器端,我正在寻找的功能也是如此。

3 个答案:

答案 0 :(得分:4)

请记住,我在这里描述的算法基于列表[1,2,... N-1](长度为N-1)。如果您坚持使用列表[0,1,...,N](长度为N + 1),请应用所需的微小修改。此外,为了简洁起见,我使用%操作数的方式与大多数编程语言略有不同:a%b取值介于1和b之间,而不是介于0和b-1之间,但当然后面的主要思想没有改变,所以a%b的值是1到b之间的整数,它与a,模b一致。

如果您仔细阅读,那么显而易见的是,生成的随机播放根本不是随机的。但是,通过精心选择的参数,模式不易识别(我的模幂运算的基本思想来自密码学,其中重要的是具有不可识别的模式和不可恢复的函数)。

与实际的编程解决方案相比,这更像是语言无关的算法描述。我不会详细介绍您可能遇到的有效实施和陷阱。我希望它仍然有帮助。我还在python中编写了部分内容,因此我可以提供进一步的帮助,甚至可以在需要时共享我的代码,但这需要一些完成和重构。

使用取幂而不是乘法来摆脱模式

你对f(x)= t * x%N(你选择t为911)的初步试验显示了一些模式,因为乘法保持线性(在模块的“模数”中)。< / p>

如果使用取幂而不是乘法,则可以给出更多随机的感觉。像f(x)= t ^ x%N这样的东西。但是,必须明智地选择t(就像在乘法情况下那样,是N的互质),并且这个公式给出的输出赢了。仅在N为素数的情况下,为不同的x值提供不同的数字。

大学水平的数学即将到来,请耐心等待,我会尽量保持简单。

我们需要使用原始根。相关的Wikipedia article可能会有很大的帮助,但基本的想法是,精心挑选的基地的剩余权力取得1和N-1之间的所有值。例如

3^1 = 3
3^2 = 9 = 2 (mod 7)
3^3 = 27 = 6 (mod 7)
3^4 = 81 = 4 (mod 7)
3^5 = 243 = 5 (mod 7)
3^6 = 729 = 1 (mod 7)

都是不同的(从这一点来看,值从头开始重复:3 ^ 7 = 3 ^ 1(mod 7),3 ^ 8 = 3 ^ 2(mod 7),依此类推)。

所以,如果你的N是7,那么3可以正常工作。您可以使用f(x)=(3 ^ x)%7表示1到6之间的x值。

这导致以下f:

f(1) = 3
f(2) = 2
f(3) = 6
f(4) = 4
f(5) = 5
f(6) = 1

引入班次,提供一些额外的随机效果

如果你正在玩这一点,你可以注意到,N-1总是被改组为1.如果你想避免这种情况,我们可以更进一步,并选择一个任意数字k来添加取幂。也就是说,使用g(x)=(f(x)+ k)%(N-1)=((t ^ x)%N + k)%(N-1),在我们的例子中,令k为2,导致排列:

g(1) = 5
g(2) = 4
g(3) = 2
g(4) = 6
g(5) = 1
g(6) = 3

如何选择基础

现在你得到基本的感觉。但是当N不是7时,如何使用这个呢?

问题的关键是选择参数t,在我们的示例中为3。

不幸的是,这通常是一个难题(数学家称之为finding a primitive root),并且我不知道任何易于理解,开箱即用的解决方案的。

但这只是问题的一部分。更可悲的是,如果N是复合数,原始根甚至不会起作用。例如,如果N = 6,则没有任何数字t,表达式t ^ x modulo 6取所有1到5之间的值。

但要解决这个问题并不难。

如何处理复合N

如果N是复合的,我们应该找到一个几乎不大的素数P,并建立在基于那个的算法的基础上,通过用它们的后洗牌值替换越界数字(和迭代,如果需要的话。)

例如,如果N是6,我们可以选择P为7并使用我们之前构造的g(x)。

g(1) = 5 ok (5<=N-1 holds)                          h(1) = 5
g(2) = 4 ok                                         h(2) = 4
g(3) = 2 ok                                    =>   h(3) = 2
g(4) = 6 too large, using g(g(4)) = g(6) = 3        h(4) = 3
g(5) = 1 ok                                         h(5) = 1

为了安全起见,我给出了N = 4的另一个例子,我们使用之前计算的P = 7的解。

g(1) = 5, g(5) = 1                                  h(1) = 1
g(2) = 4, g(4) = 6, g(6) = 3                   =>   h(2) = 3
g(3) = 2                                            h(3) = 2

现在应该清楚了。选择接近N的P是明智的,因此g的越界值不需要太多的重新计算。

返回发现

所以我们唯一的问题就是找到可以用作求幂基础的原始根。

如果我之前链接的页面上的数学引起了一些内心的厌恶,我有一些好消息:在区间[2,N-1]中可能的t值很高,所以即使是随机猜测也可能有所帮助。

有一些细节如何有效地验证在链接页面上随机选择的t对我们是否真的有用,但除非您使用的是非常大的数字,否则您可以执行取幂并检查数字1是否更早出现比t的(N-1)次幂(也许你还记得我注意到t ^ x = 1(mod N)总是保持在x = N-1的情况下,所以早期出现的1会破坏唯一性)。

我建议在N / 2周围寻找合适的t(意味着数量级 - 对于P = 91367,t = 54949完美地工作)。如果你选择t太低(例如t = 2),你可以很容易地发现由一些相邻x值上的取幂引起的模式(2 + k,4 + k,8 + k,...将出现在旁边彼此)。如果t太接近N,如果在相同奇偶校验的连续x值中查看f(x),则可能会出现类似的现象。 t的一个很好的选择应该涵盖这些模式,并以足够随意的结果结束。

<强>摘要

再一次,这是算法的步骤

(N给定)

找到比N

略大的P素数

选择介于1和P-1之间的任意数字

找到t是P

的原始根

(对于给定的x输出shuffle h(x)是)

计算

f(x) = (t ^ x) % P

计算

g(x) = (f(x) + k) % (P-1)

计算

h(x) = g(x)                                       if g(x)<=N-1,
       iterate the calculations with x = g(x)     otherwise

答案 1 :(得分:2)

这个非常密切相关的问题(How to generate a predictable shuffling of a sequence without generating the whole sequence in advance?)及其接受的答案(以及其中链接的博客文章及其参考文章)可能也很有趣,应该可以解决您的问题。

答案 2 :(得分:0)

根据您的评论:

Function accepts 1 number =x, math happens, 1 number is returned which is a random position AND UNIQUE within the same interval where x comes from

我可能仍然不明白这个问题,但我再试一次。我希望无论如何它会给你一些线索/帮助!

<?php
$control = array();

$f = array(5,1,0,4,2,3); //Example

$x = 5; //Function accepts 1 number =x


for($n=1;$n<41;$n++) {
    echo doTheMath($control, $f, $x); //Echo just for example
}

function doTheMath(Array &$control, Array &$f, $x) {
    $n = count($f); //Interval count

    //1 number is returned 
    //AND
    //UNIQUE within the same interval where x comes from.

    while (!in_array($random = array_rand($f, 1), $control)) { //Get another random value from array-element if it's not in control-array

        if (!in_array($random, $control)) {
            $control[] = $random;

            //The are no more unique values, stop executing php
            if (count($control) == $n) {
                exit;
            }

            $result = 911 * $random * $n;
            return $result . '<hr />';
        }
    }


    return null;

}
?>

示例输出:

10932
0
21864
27330
16398