【问题标题】:Select one number at a time between 0 & 10 billion in random order以随机顺序在 0 到 100 亿之间一次选择一个数字
【发布时间】:2017-03-16 20:45:36
【问题描述】:

问题

我需要一次在 0 到 10,000,000,000 之间选择一个唯一的随机数,并一直这样做直到选择了所有数字。本质上,我需要的行为是一个预先构建的堆栈/队列,其中包含 100 亿个随机数字,无法将新项目推入其中。

不太好的解决方法:

我的大脑中不乏低效的方法。比如,

  • 持久化生成的数字并检查新生成的随机数是否已被使用,在某些时候这会让我们无限期地等待生成可用的数字。
  • 将所有可能的数字保留在一个表中并弹出一个随机行并为下一次选择等保持新的行数。不确定这是好是坏。

问题:

  1. 除了存储所有可能的组合并使用随机数之外,还有其他确定性方法吗?

    • 喜欢维护可用数字的窗口并首先随机选择一个窗口并在该窗口内随机选择一个数字等。例如:如this
  2. 如果不是,在相当小的空间中存储数字的最佳类型是什么?

    • 50+% 的数字不适合 32 位 (int),64 位 (long) 是浪费。 Cos 最大数字适合 34 位,每个数字浪费 30 位(总共 >37GB)。

如果这个问题还没有解决。

  1. 什么是存储和选择随机点并快速调整结构以便下一个选择更快的良好数据结构?

***抱歉,含糊不清。最大可选数为 9,999,999,999,最小可选数为 1。

【问题讨论】:

  • 随机性必须有多完美?您是否需要所有 100 亿个因子排列的可能性相同?
  • 您可能想查看Linear Congruential Generator
  • 您想要 1 到 100 亿还是 0 到 100 亿之间的数字?标题说明一件事,问题说明另一件事。
  • 您需要 lg((10^10)!) 位来存储 10b 数字的所有可能排列,因此... 4,584,883,940 位 -> 547MB。因此,对于完全随机性,这是您的 最小 #2 空间。
  • @ruakh 随机性不必完全完美。

标签: algorithm search memory-management random


【解决方案1】:

您问:“除了存储所有可能的组合并使用随机数之外,还有其他确定性方法吗?”

是的,有:加密。使用给定密钥的加密保证了唯一输入的唯一结果,因为它是可逆的。每个键定义了可能输入的一对一排列。您需要对 [1..10e9] 范围内的输入进行加密。要处理这么大的事情,您需要 34 位数字,最高可达 17,179,869,183。

没有标准的 34 位加密。根据您需要多少安全性以及需要多快的数字,您可以编写自己的简单、快速、不安全的四轮 Feistel Cipher,或者在 34 位模式下使用 Hasty Pudding cipher 进行更慢和更安全的操作。

对于任一解决方案,如果第一次加密给出的结果超出范围,只需再次加密结果,直到新结果在您想要的范围内。一对一属性确保加密链的最终结果是唯一的。

要生成一个看似随机的唯一数字序列,只需使用相同的密钥按顺序加密 0、1、2、3、4...。加密保证该密钥的结果是唯一的。如果你记录你已经走了多远,那么你以后可以生成更多的唯一数字,最高可达 100 亿个限制。

【讨论】:

  • 我看不出这有什么帮助
  • 加密 0 以获得一个“随机”数字。加密 1 以获得有保证的不同“随机”数。加密 3 以获得第三个保证不同的“随机”数字。重复直到所有可能的数字都被选中,或者当你有足够的数字时停止。您始终可以按顺序加密下一个数字来重新启动。无需存储所有数字,只需存储密钥以及您使用过的范围内的最低和最高。您可以随时根据需要重新计算。
  • 看到有人在 Stack Overflow 上提到 Hasty Pudding Cipher,我真的很惊讶!然后我看到它是rossum!好吧,你是少数知道它的人之一! :-)
  • +1。请注意,使用这种方法,并非所有的 100 亿阶乘排列实际上都是可能的(因为远没有接近 100 亿阶不同的可能加密密钥);但是由于上面的 OP cmets 认为“随机性不一定是完全完美的”,这似乎很可能是好的。
  • 我不知道任何非自定义加密可以采用 0-10b 之间的数字并产生保证的不同数字 0-10b。每个标准加密都会为您提供大于 10b 的数字。如果你使用模数,你就会失去保证的不同。在这种情况下,数字会重复,并且具有不同的可能性。
【解决方案2】:

正如 AChampion 在 cmets 中提到的,您可以使用 Linear Congruential generator

您的模 (m) 值为 100 亿。为了获得完整的周期(范围内的所有值出现在系列重复之前),您需要选择 a 和 c 常数以满足某些标准。 m 和 c 需要互质,并且 a - 1 需要被 m 的质因数(只有 2 和 5)和 4 整除(因为 100 亿可以被 4 整除)。

如果您只是想出一组常量,那么您将只有一个可能的系列,并且数字将始终以相同的顺序出现。但是,您可以轻松地随机生成满足条件的常量。要测试 c 和 m 的相对素数,只需测试 c 是否可被 2 和 5 整除,因为这些是 m 的唯一素数(请参阅互素性测试的第一个条件here

Python 中的简单草图:

import random

m = 10000000000
a = 0
c = 0
r = 0

def setupLCG():
    global a, c, r
    # choose value of c that is 0 < c < m and relatively prime to m
    c = 5
    while ((c % 5 == 0) or (c % 2 == 0)):
        c = random.randint(1, m - 1)
    # choose value of a that is 0 < a <= m and a - 1 is divisible by
    # prime factors of m, and 4
    a = 4
    while ((((a - 1) % 4) != 0) or (((a - 1) % 5) != 0)):
        a = random.randint(1, m)
    r = random.randint(0, m - 1)

def rand():
    global m, a, c, r
    r = (a*r + c) % m
    return r

random.seed()
setupLCG()
for i in range(1000):    
    print rand() + 1

这种方法不会给出 10000000000 的全部可能性!可能的组合,但仍将是 1019 的数量级,这是相当多的。它确实有一些其他问题(例如交替偶数和奇数值)。你可以通过有一个小的数字池来混合它,每次从序列中添加一个数字并随机抽取一个。

【讨论】:

  • @pjs。那时候我想过那个。问题说 0-100 亿,但标题说 1-100 亿。我现在假设是后者,因为 OP 还说他们想要 100 亿个数字。
  • 是的,我赞成指出歧义的评论。希望OP会回应。
  • 注意:10000000001 的质因数是11^2 * 23 * 4093 * 8779
  • 如果范围是 1 到 9999999999 包括在内,那么你遇到它时可以丢弃 100 亿。或者针对 9999999999 的主要因子调整算法。但是第一个选项更容易,因为 a 的有效值会更多。
  • 感谢您对实现的帮助,它帮助我比阅读文献更容易理解这个概念。
【解决方案3】:

与 rossum 的建议类似,您可以使用 invertible integer hash function,它将 [0,2^k) 中的一个整数唯一地映射到同一范围内的另一个整数。对于您的特定问题,您选择 k=34 (2^34=160 亿) 并拒绝任何超过 100 亿的数字。这是一个完整的实现:

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

uint64_t hash_64(uint64_t key, uint64_t mask)
{
    key = (~key + (key << 21)) & mask; // key = (key << 21) - key - 1;
    key = key ^ key >> 24;
    key = ((key + (key << 3)) + (key << 8)) & mask; // key * 265
    key = key ^ key >> 14;
    key = ((key + (key << 2)) + (key << 4)) & mask; // key * 21
    key = key ^ key >> 28;
    key = (key + (key << 31)) & mask;
    return key;
}

int main(int argc, char *argv[])
{
    uint64_t i, shift, mask, max = 10000ULL;
    char *dummy;
    if (argc > 1) max = strtol(argv[1], &dummy, 10);
    for (shift = 0; 1ULL<<shift <= max; ++shift) {}
    mask = (1ULL<<shift) - 1;
    for (i = 0; i <= mask; ++i) {
        uint64_t x = hash_64(i, mask);
        x = hash_64(x, mask);
        x = hash_64(x, mask); // apply multiple times to increase randomness
        if (x > max || x == 0) continue;
        printf("%llu\n", x);
    }
    return 0;
}

这应该以随机顺序为您提供数字 [0,10000000000]。

【讨论】:

  • 感谢您为我节省了实施解决方案的时间。感谢您的帮助。
【解决方案4】:

对于范围 1-999,999,999,999 等价于 0-999,999,999,998(只需添加 1)。给定LCG 的定义,那么你可以实现这个:

import functools as ft
import itertools as it
import operator as op
from sympy import primefactors, nextprime

def LCG(m, seed=0):
    factors = set(primefactors(m))
    a = ft.reduce(op.mul, factors)+1
    assert(m%4 != 0 or (m%4 == 0 and (a-1)%m == 0))
    c = nextprime(max(factors)+1)
    assert(c < m)
    x = seed
    while True:
        x = (a * x + c) % m
        yield x

# Check the first 10,000,000 for duplicates
>>> x = list(it.islice(LCG(999999999999), 10000000))
>>> len(x) == len(set(x))
True
# Last 10 numbers
>>> x[-10:]
[99069910838, 876847698522, 765736597318, 99069940559, 210181061577,
 432403293706, 99069970280, 543514424631, 99069990094, 99070000001]

我为这个问题的上下文采取了一些捷径,因为asserts 应该被替换为处理代码,目前如果那些asserts 是False,它只会失败

【讨论】:

  • 感谢您向我介绍 LCG。虽然这更完美,但我最终还是使用了 rossum 的解决方案。我非常感谢对这个(对我而言)新概念的介绍。
【解决方案5】:

我不知道有任何真正随机的方法来选择数字而不存储已经选择的数字列表。你可以做某种线性散列算法,然后通过它传递数字 0 到 n(当你的散列返回值大于 10000000000 时重复),但这不会是真正的随机。

如果您要存储数字,您可以考虑通过位掩码进行。为了在位掩码中快速选择,您可能会保留一棵树,其中每个叶子代表相应 32 个字节中的空闲位数,上面的分支将列出相应 2K 条目中的空闲位数,依此类推.然后你有 O(log(n)) 时间来找到你的下一个条目,并且 O(log(n)) 时间来声明一点(因为你必须更新树)。它也需要存储大约 2n 位的东西。

【讨论】:

    【解决方案6】:

    您绝对不需要存储所有数字。

    如果您想要一组从 1 到 10B 的完美数字,我看到了两个选项:正如其他人所暗示的,使用 34 位 LCG 或 Galois LFSR 或 XOR-shift 生成从 1 到 17B 左右的数字序列,然后丢弃超过 10B 的数字。我不知道有任何专门的 34 位函数,但我确定有人知道。

    选项 2,如果您可以腾出 1.25 GB 的内存,则创建一个位图,仅存储已选择某个数字的信息,然后使用弗洛伊德算法获取数字,这将是快速的并且给您很多质量更好的数字(事实上,它可以很好地用于硬件 RNG)。

    选项 3,如果您可以忍受罕见但偶尔出现的错误(重复或从未选择过的数字),请将位图替换为 Bloom 过滤器并节省内存。

    【讨论】:

      【解决方案7】:

      如果不考虑可预测性,您可以使用 XOR 运算快速生成。假设您要生成具有 n 位(在您的情况下为 34)的唯一数字的随机序列:

      1- 在 n 位上取一个种子数。这个数字 K 可以被视为一个种子,您可以在每次运行新实验时更改它。

      2- 使用从 0 向上的计数器

      3- 每次将计数器与K 异或:next = counter xor K; counter++;

      要将范围限制为 100 亿(不是 2 的幂),您需要进行拒绝。

      明显的缺点是可预测性。在第 3 步中,您可以对计数器的字节进行预先转置,例如反转字节的顺序(例如从小端转换为大端时)。这将对下一个数字的可预测性产生一些改进。

      最后我不得不承认,这个答案可以被认为是@rossum 的答案中提到的加密的特定实现,但它更具体,可能是最快的。

      【讨论】:

        【解决方案8】:

        非常慢,但它应该可以工作。完全随机

        using System;
        using System.Diagnostics;
        using System.IO;
        using System.Runtime.InteropServices;
        
        namespace ConsoleApplication1
        {
            class Program
            {
                static Random random = new Random();
                static void Main()
                {
                    const long start = 1;
                    const long NumData = 10000000000;
                    const long RandomNess = NumData;
                    var sz = Marshal.SizeOf(typeof(long));
                    var numBytes = NumData * sz;
        
                    var filePath = Path.GetTempFileName();
                    using (var stream = new FileStream(filePath, FileMode.Create))
                    {
                        // create file with numbers in order
                        stream.Seek(0, SeekOrigin.Begin);
                        for (var index = start; index < NumData; index++)
                        {
                            var bytes = BitConverter.GetBytes(index);
                            stream.Write(bytes, 0, sz);
                        }
        
                        for (var iteration = 0L; iteration < RandomNess; iteration++)
                        {
        
                            // get 2 random longs
                            var item1Index = LongRandom(0, NumData - 1, random);
                            var item2Index = LongRandom(0, NumData - 1, random);
        
        
                            // allocate room for data
                            var data1ByteArray = new byte[sz];
                            var data2ByteArray = new byte[sz];
        
                            // read the first value
                            stream.Seek(item1Index * sz, SeekOrigin.Begin);
                            stream.Read(data1ByteArray, 0, sz);
        
                            // read the second value
                            stream.Seek(item2Index * sz, SeekOrigin.Begin);
                            stream.Read(data2ByteArray, 0, sz);
        
                            var item1 = BitConverter.ToInt64(data1ByteArray, 0);
                            var item2 = BitConverter.ToInt64(data2ByteArray, 0);
        
                            Debug.Assert(item1 < NumData);
                            Debug.Assert(item2 < NumData);
        
                            // swap the values
                            stream.Seek(item1Index * sz, SeekOrigin.Begin);
                            stream.Write(data2ByteArray, 0, sz);
        
                            stream.Seek(item2Index * sz, SeekOrigin.Begin);
                            stream.Write(data1ByteArray, 0, sz);
                        }
        
                    }
                    File.Delete(filePath);
        
                    Console.WriteLine($"{numBytes}");
        
                }
        
                static long LongRandom(long min, long max, Random rand)
                {
                    long result = rand.Next((int)(min >> 32), (int)(max >> 32));
                    result = (result << 32);
                    result = result | rand.Next((int)min, (int)max);
                    return result;
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2014-01-30
          • 1970-01-01
          • 2015-09-10
          • 2017-05-31
          • 2012-03-27
          • 2011-01-23
          • 2018-04-12
          相关资源
          最近更新 更多