Java 按排序顺序返回唯一项的随机数生成器

Java 按排序顺序返回唯一项的随机数生成器,java,algorithm,random,sequence,distribution,Java,Algorithm,Random,Sequence,Distribution,我需要一个用于许多(最多1万亿,10^12)唯一随机64位数字的生成器。 生成器需要按排序顺序返回数字(Long.MIN\u值到Long.MAX\u值)。问题是对$10^{12}$数字进行排序很慢。这个用例正在复制一个测试,该测试是为(4.5万亿个索引键)运行的 简单的解决方案是在内存中创建一个集合,使用大约一个巨大的位集合 以确保不返回重复项。 但这会占用太多内存或I/O。 我想使用最多几MB的内部状态 生成器应在内部使用java.util.Random。 它应该尽可能“公平”(具有与其他情况

我需要一个用于许多(最多1万亿,10^12)唯一随机64位数字的生成器。 生成器需要按排序顺序返回数字(Long.MIN\u值到Long.MAX\u值)。问题是对$10^{12}$数字进行排序很慢。这个用例正在复制一个测试,该测试是为(4.5万亿个索引键)运行的

简单的解决方案是在内存中创建一个集合,使用大约一个巨大的位集合 以确保不返回重复项。 但这会占用太多内存或I/O。 我想使用最多几MB的内部状态

生成器应在内部使用java.util.Random。 它应该尽可能“公平”(具有与其他情况相同的统计分布)。我还想有一个版本的128位数字(2长)

到目前为止,我拥有的是在内存中创建集合的代码(Java代码):

publicstaticvoidmain(字符串…参数){
for(长x:randomSet(10,0)){
系统输出println(x);
}
}
静态Iterable随机集(整数大小,整数种子){
随机r=新随机(种子);
树集=新树集();
while(set.size()
最简单(错误)的解决方案不是随机的,而是均匀地分布结果。 我认为“增加一个随机缺口”的解决方案行不通, 因为它很慢,10^12之后,这些差距的总和将不会到达它应该到达的位置(好吧,也许:记住还有多少数字,然后重新计算分布…)。我认为以下方法应该可行,但很复杂,不确定使用什么公式:对于每个位级别, 递归地计算可能出现的0/1数量 (不知何故,使用二项分布或近似值,即正态/高斯分布)。 在某个点停止(例如,100万条或更少条目的块), 使用上面的代码,以提高速度。 但也许有一个优雅的解决方案。 也许这与大都会黑斯廷斯算法有关,不确定。 我读过“顺序随机抽样的有效算法”, 但我认为它只适用于小n,我发现很难从中得到一个简单的算法

Java代码是最好的,但C是好的(无论如何,在某个时候我可能不得不将其转换为C/C++)。我不想使用太多的库,以简化移植。

以满足需求

  • 从整数区间i=[-(r+1),r],r>0生成随机数r_i序列,其统计分布如下 java.util.Random
  • 序列r_i必须严格递增(对于i>j,r_i>r_j)
  • 我们可以想出一个简单的算法

    A1:
     - draw a random number r_i from I via a library call
     - discard it, if it is less or equal the last draw, try another pick
    
    可能的投诉是,该算法可能给出的生成r_i的数目不正确,存在一个模糊要求,即N=10^12个期望总数

  • “需要一个用于多个(最多1万亿,10^12)唯一随机64位数字的生成器”
  • 解决办法是

    A2:
     - to generate N numbers and then 
     - sort them
    
    然而,还有另一个要求,即没有足够的可用内存

  • “我希望最多使用几MB的内部状态。”
  • 我的猜测是,不可能同时满足所有这些要求

    作为妥协,我建议

    A3:
     R=2^63 = 9 10^18  
     N=1 Trillion = 10^12
     - divide the range I=[-R,R-1] into N intervals of length (2R+1)/N each 
     - visit each of those intervals (visiting one interval after another)
     - draw a random number from that interval
    
    这将以递增的顺序给出N个随机数

    更新:

    在浏览了几遍之后,我的理解是:

    给定一个整数集I和一个子集S,其中N=| S |个元素,BBHash过程将计算一个函数f,该函数将S映射到{1,…,N}的某个置换(什么置换似乎由BBHash过程隐式决定),并将I中的所有其他元素映射到I中的一个特殊值Imax

    可能的测试:

    给定S和f,可以检查是否正确计算了I中任意元素在S中的成员资格

    也可以检查f(S)={1,…,N}

    我的猜测是,请求的算法是为了在内存紧张的情况下动态计算N=10^12的样本集S,需要随机数序列的唯一性,而不是单调性

    引用

    概率数据结构不能给你一个明确的答案, 相反,它们为您提供了答案的合理近似值 以及一种近似估计的方法。它们非常有用 用于大数据和流媒体应用程序,因为它们允许 大大减少所需的内存量(与 提供精确答案的数据结构)

    在大多数情况下,这些数据结构使用哈希函数 将项目随机化。因为它们忽略碰撞,所以保持大小不变 常数,但这也是他们不能给你准确答案的原因 价值观


    在BBHash的情况下,使用不同hash函数h_i的序列。一个应用不同的h_i,直到没有碰撞发生。这仅在输入唯一时有效。只有在实现中为特定的S存储了足够多的不同h_i时,它才会工作。

    10^12约为2^40,即序列值之间的平均步长为2^24

    所以,若目标是生成不可预测但有序的哈希序列,那个么这是不可能的,2^24对于bruteforce来说太容易了


    但若它不是目标,那个么为什么不将递增的2^40计数器的高位与2^24随机值的低位相加呢

    您需要大量的伪随机64位数字,它们都是唯一的。给定唯一的输入和相同的密钥,加密是唯一的——它必须是唯一的,因为它是可逆的。DES是一种64位的分组密码,因此加密数字0、1、2、3、4。。。10^12在带有ECB模式的DES中,将为您提供万亿个唯一的64位数字。
    A3:
     R=2^63 = 9 10^18  
     N=1 Trillion = 10^12
     - divide the range I=[-R,R-1] into N intervals of length (2R+1)/N each 
     - visit each of those intervals (visiting one interval after another)
     - draw a random number from that interval
    
    Loop over 7451 buckets (B):
        Generate 134,210,173 random values in the range (0..R),
        inserting them into the array as they are produced. Binary
        insertion should be reasonable (N*log(N), just like generating
        them all then sorting, but you can use the insertion to catch
        duplicates so you don't need extra memory or time for that).
    
        Output the bucket of values, adding (B*R) to each.
    
    public static void main(String... args) {
        Random r = new Random();
        Iterator<Long> it = randomSequence(r, 10, 32);
        while(it.hasNext()) {
            System.out.println(it.next());
        }
    }
    
    /**
     * Random sequence generator.
     *
     * @param r the random generator
     * @param size the number of entries to generate
     * @param shift the number of bits of the result
     * @return the iterator
     */
    static Iterator<Long> randomSequence(final Random r, final long size, final int shift) {
        if (size < 5) {
            // small lists are generated using a regular hash set
            TreeSet<Long> set = new TreeSet<Long>();
            while (set.size() < size) {
                set.add(r.nextLong() & ((2L << shift) - 1));
            }
            return set.iterator();
        }
        // large lists are created recursively
        return new Iterator<Long>() {
            long remaining = size, zeros = randomHalf(r, size);
            Iterator<Long> lowBits0 = randomSequence(r, zeros, shift - 1);
            Iterator<Long> lowBits1;
            @Override
            public boolean hasNext() {
                return remaining > 0;
            }
            @Override
            public Long next() {
                remaining--;
                if (lowBits0.hasNext()) {
                    return lowBits0.next();
                }
                if (lowBits1 == null) {
                    lowBits1 = randomSequence(r, size - zeros, shift - 1);
                }
                return (1L << shift) + lowBits1.next();
            }
        };
    }
    
    /**
     * Get the number of entries that are supposed to be below the half,
     * according to the probability theory. For example, for a number of coin
     * flips, how many are heads.
     *
     * @param r the random generator
     * @param samples the total number of entries
     * @return the number of entries that should be used for one half
     */
    static long randomHalf(Random r, long samples) {
        long low = 0, high = samples;
        double x = r.nextDouble();
        while (low + 1 < high) {
            long mid = (low + high) / 2;
            double p = probabilityBucketAtMost(samples, mid);
            if (x > p) {
                low = mid;
            } else {
                high = mid;
            }
        }
        return (low + high) / 2;
    }
    
    static double probabilityBucketAtMost(long flips, long heads) {
        // https://www.fourmilab.ch/rpkp/experiments/statistics.html
        long x = heads;
        long n = flips;
        double variance = Math.sqrt(n/4);
        // mean
        long mu = n / 2;
        // https://en.wikipedia.org/wiki/Normal_distribution
        // Numerical approximations for the normal CDF
        // the probability that the value of a standard normal random variable X is <= x
        return phi((x - mu) / variance);
    }
    
    static double phi(double x) {
        return 0.5 * (1 + Math.signum(x) * Math.sqrt(1 - Math.exp(-2 * x * x / Math.PI)));
    }