我需要在0到10,000,000,000之间选择一个唯一的随机数,直到选中所有数字为止。基本上我需要的行为是一个预先构建的堆栈/队列,其中包含10亿个随机顺序的数字,无法将新项目推入其中。
我脑中不乏低效的方式。如,
除了存储所有可能的组合并使用随机之外,还有其他确定性方法吗?
如果没有,在相当小的空间内存储数字的最佳类型是什么?
如果此问题尚未解决。
***对不起,我很抱歉。最大可选数字是9,999,999,999,最小可选数字是1。
答案 0 :(得分:3)
你问:"除了存储所有可能的组合并使用随机之外,还有其他确定性方法吗?"
是的,有:加密。使用给定密钥进行加密可确保唯一输入的唯一结果,因为它是可逆的。每个键定义了可能输入的一对一排列。您需要在[1..10e9]范围内加密输入。处理那些需要34位数字的东西,最多可达17,179,869,183。
没有标准的34位加密。根据您需要多少安全性以及您需要多快的数字,您可以编写自己的简单,快速,不安全的四轮Feistel Cipher,或者使用Hasty Pudding cipher进行更慢,更安全的使用PEP274 34位模式。
使用任一解决方案,如果第一次加密产生的结果超出范围,则只需再次加密结果,直到新结果在您想要的范围内。一对一属性可确保加密链的最终结果是唯一的。
要生成一系列唯一的随机看似数字,只需按相同的密钥加密0,1,2,3,4 ......。加密可确保结果对于该密钥是唯一的。如果您记录了您的距离,那么您可以在以后生成更多唯一数字,最高可达100亿。
答案 1 :(得分:2)
如评论中的AChampion所述,您可以使用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的全部可能性!可能的组合,但它仍然是10 19 的数量级,这是相当多的。它确实有一些其他问题(例如交替偶数和奇数值)。您可以通过使用一小段数字来混合它,每次从序列中添加一个数字并随机抽出一个。
答案 2 :(得分:1)
与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]。
答案 3 :(得分:1)
范围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]
我已经针对此问题的上下文采用了几个快捷方式,因为assert
应该替换为处理代码,目前只有assert
s False
{{}} {}才会失败1}}
答案 4 :(得分:0)
我不知道任何真正随机的选择数字的方法而不存储已经选择的数字列表。你可以做一些线性哈希算法,然后通过它传递数字0到n(当哈希值返回10000000000以上时重复),但这不是真正随机的。
如果要存储数字,可以考虑通过位掩码进行存储。要快速选择位掩码,您可能会保留一个树,其中每个叶子将表示相应32个字节中的空闲位数,上面的分支将列出相应2K条目中的空闲位数,依此类推。然后你有O(log(n))时间来查找你的下一个条目,O(log(n))时间来声明一点(因为你必须更新树)。它还需要存储大约2n位的内容。
答案 5 :(得分:0)
如果不考虑可预测性,您可以使用XOR操作快速生成。假设您要生成一个随机的n位唯一数字序列(在您的情况下为34):
1-取n位的种子数。这个数字K
可以视为种子,每次运行新实验时都可以更改。
2-使用0向上的计数器
3-每次使用K
:next = counter xor K; counter++;
要将范围限制在100亿,这不是2的幂,你需要拒绝。
明显的缺点是可预测性。在步骤3中,您可以对计数器的字节进行事先转置,例如,按字节顺序反转(例如,当您从little-endian转换为big endian时)。这将对下一个数字的可预测性产生一些改进。
最后,我必须承认,这个答案可以被视为@rossum答案中提到的加密的特定实现,但它更具体,可能最快。
答案 6 :(得分:0)
你绝对不需要存储所有数字。
如果你想要一组完美的数字从1到10B每一次,我看到有两个选项:正如其他选项暗示的那样,使用34位LCG或Galois LFSR或XOR-shift生成从1到17B左右的数字序列,然后丢弃超过10B的数字。我不知道有任何具体的34位功能,但我确定有人。
选项2,如果可以节省1.25 GB的内存,就是创建一个仅存储已选择某个数字的信息的位图,然后使用Floyd的算法来获取数字,这将是快速的并为您提供更高质量的数字(事实上,它可以与硬件RNG一起使用)。
选项3,如果您可以忍受罕见但偶然的错误(重复或未选择的数字),请使用布隆过滤器替换位图并节省内存。
答案 7 :(得分:0)
速度慢但应该有效。完全随机
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;
}
}
}