如何生成伪随机内卷?

时间:2016-08-17 11:28:36

标签: algorithm math random permutation inverse

为了生成伪随机排列,可以使用Knuth shuffles。对合是一种自逆置换,我想,我可以通过禁止多次触摸元素来调整shuffle。但是,我不确定我是否能够有效地做到这一点以及它是否会产生每个内卷等概率

我害怕,需要一个例子:在一组{0,1,2}上,有6个排列,其中4个是变形。我正在寻找一种算法,以相同的概率随机生成其中一个。

一个正确但非常低效的算法是:使用Knuth shuffle,如果它没有内卷,则重试。

2 个答案:

答案 0 :(得分:3)

我们这里使用public class UserLoginTask extends AsyncTask<Void, Void, Boolean> { private final String mEmail; private final String mPassword; UserLoginTask(String email, String password) { mEmail = email; mPassword = password; } @Override protected Boolean doInBackground(Void... params) { Retrofit restAdapter = new Retrofit.Builder() .baseUrl(Constants.ROOT_API_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); IConstructSecureAPI service = restAdapter.create(IConstructSecureAPI.class); Call<JsonElement> result = service.getToken(mEmail, mPassword, "password"); try { //using sync request and get response JsonElement element = result.execute().body(); if(element!=null) { //do whatever you want with your response } } catch (IOException e) { e.printStackTrace(); return false; } return true; } @Override protected void onPostExecute(final Boolean success) { mAuthTask = null; showProgress(false); if (success) { if(clientType != 2){ mPasswordView.setError(getString(R.string.error_incorrect_password)); mPasswordView.requestFocus(); clientType = 0; } else { Intent intent = new Intent(getApplicationContext(), MainActivity.class); startActivityForResult(intent, 1); } // finish(); } else { mPasswordView.setError(getString(R.string.error_incorrect_password)); mPasswordView.requestFocus(); } } @Override protected void onCancelled() { mAuthTask = null; showProgress(false); } } 作为一组大小a(n)as OEIS does)上的渐变数。对于给定的一组大小n和该集合中的给定元素,该集合上的总攻击数为n。该元素必须通过对合不变或与另一个元素交换。使我们的元素固定的invions数量是a(n),因为那些是其他元素的入侵。因此,在对象上的均匀分布必须具有a(n-1)保持该元素固定的概率。如果要修复它,只需留下该元素。否则,选择我们的算法尚未检查的另一个元素与我们的元素交换。我们刚刚决定了集合中的一个或两个元素会发生什么:继续并决定一次一个或两个元素会发生什么。

要做到这一点,我们需要一个每个a(n-1)/a(n)的入侵计数列表,但这可以通过递归公式轻松完成

i <= n

(请注意,来自OEIS的这个公式也来自我的算法:第一个术语计算第一个元素所在的第三个元素,第二个术语是与它交换的元素。)如果你正在使用它involutions,这可能非常重要,可以突破到另一个函数,预先计算一些较小的值,并缓存函数的结果以获得更快的速度,如下代码所示:

a(i) = a(i-1) + (i-1) * a(i-2)

我们还需要一种方法来跟踪尚未确定的元素,因此我们可以有效地选择具有统一概率的元素之一和/或标记元素。我们可以将它们保存在缩小的列表中,并在列表的当前末尾添加标记。当我们决定一个元素时,我们移动列表末尾的当前元素来替换已决定的元素,然后减少列表。有了这个效率,这个算法的复杂性是# Counts of involutions (self-inverse permutations) for each size _invo_cnts = [1, 1, 2, 4, 10, 26, 76, 232, 764, 2620, 9496, 35696, 140152] def invo_count(n): """Return the number of involutions of size n and cache the result.""" for i in range(len(_invo_cnts), n+1): _invo_cnts.append(_invo_cnts[i-1] + (i-1) * _invo_cnts[i-2]) return _invo_cnts[n] ,除了最后一个元素外,每个元素都有一个随机数计算。没有更好的订单复杂性。

这是Python 3.5.2中的代码。通过未定元素列表所涉及的间接,代码有点复杂。

O(n)

我做了几次测试。这是我用来检查有效性和统一分布的代码:

from random import randrange

def randinvolution(n):
    """Return a random (uniform) involution of size n."""

    # Set up main variables:
    # -- the result so far as a list
    involution = list(range(n))
    # -- the list of indices of unseen (not yet decided) elements.
    #    unseen[0:cntunseen] are unseen/undecided elements, in any order.
    unseen = list(range(n))
    cntunseen = n

    # Make an involution, progressing one or two elements at a time
    while cntunseen > 1:  # if only one element remains, it must be fixed
        # Decide whether current element (index cntunseen-1) is fixed
        if randrange(invo_count(cntunseen)) < invo_count(cntunseen - 1):
            # Leave the current element as fixed and mark it as seen
            cntunseen -= 1
        else:
            # In involution, swap current element with another not yet seen
            idxother = randrange(cntunseen - 1)
            other = unseen[idxother]
            current = unseen[cntunseen - 1]
            involution[current], involution[other] = (
                involution[other], involution[current])
            # Mark both elements as seen by removing from start of unseen[]
            unseen[idxother] = unseen[cntunseen - 2]
            cntunseen -= 2

    return involution

结果是

def isinvolution(p):
    """Flag if a permutation is an involution."""
    return all(p[p[i]] == i for i in range(len(p)))

# test the validity and uniformness of randinvolution()
n = 4
cnt = 10 ** 6
distr = {}
for j in range(cnt):
    inv = tuple(randinvolution(n))
    assert isinvolution(inv)
    distr[inv] = distr.get(inv, 0) + 1
print('In {} attempts, there were {} random involutions produced,'
    ' with the distribution...'.format(cnt, len(distr)))
for x in sorted(distr):
    print(x, str(distr[x]).rjust(2 + len(str(cnt))))

对我而言,这看起来非常均匀,我检查的其他结果也是如此。

答案 1 :(得分:0)

对合是一对一的映射,它是它自己的逆。任何密码都是一对一的映射;它必须是为了明确地解密cyphertext。

对于对合,您需要一个与其自身反转的密码。存在这样的密码,ROT13就是一个例子。其他人请参见Reciprocal Cipher

对于你的问题,我建议使用XOR密码。选择一个随机密钥至少与初始数据集中最长的数据一样长。如果您使用32位数字,则使用32位密钥。要置换,依次对每个数据的关键字进行异或。反向排列(相当于解密)与XOR操作完全相同,并将返回原始数据。

这将解决数学问题,但绝对不是加密安全的。反复使用相同的密钥将允许攻击者发现密钥。我认为除了需要具有均匀分布的随机看似对合之外,没有安全要求。

ETA:这是一个Java中的演示,我在第二篇评论中谈论的内容。作为Java,我使用索引0..12作为你的13个元素集。

public static void Demo() {

    final int key = 0b1001;

    System.out.println("key = " + key);
    System.out.println();

    for (int i = 0; i < 13; ++i) {

        System.out.print(i + " -> ");
        int ctext = i ^ key;

        while (ctext >= 13) {
            System.out.print(ctext + " -> ");
            ctext = ctext ^ key;
        }
        System.out.println(ctext);
    }

} // end Demo()

演示的输出是:

key = 9

0 -> 9
1 -> 8
2 -> 11
3 -> 10
4 -> 13 -> 4
5 -> 12
6 -> 15 -> 6
7 -> 14 -> 7
8 -> 1
9 -> 0
10 -> 3
11 -> 2
12 -> 5

如果变换后的键从数组的末尾掉落,它将再次变换,直到它落入数组内。我不确定while构造是否属于函数的严格数学定义。