写点什么

生成大边界不重复随机整数

作者:waitmoon
  • 2024-03-20
    上海
  • 本文字数:4929 字

    阅读完需:约 16 分钟

好久没写了...写点东西证明没送外卖,没跑滴滴,没转行,还活着。


题目

如何生成 M 个 1~N 的不重复的随机整数?其中 N>1 万亿,1≤M≤N。

初解析

初看这就是一道简单的算法题,用洗牌算法就可以,但 N>1 万亿,即使用 bit 也要消耗掉 116G+的存储,Swap 消耗也很大,如果每次随机然后做记录,冲突的就再随机一次?但 M 是可以接近 N 的,接近 N 时冲突变大,历史生成的随机数存储也会越来越大。怎么办?

岔开话题

想另外一个问题,如果有一天可以星际航行,你想带走地球上的所有数据,你会怎么带?带走所有数据存储的磁盘?

如果可以,把所有的数据都转换成十进制,然后在这个天文数字前加个"0.",于是它就变成了一个<1 的小数,如果我能制作出两根短/长刚好是这个小数的棍子,是不是我带着这两根棍子就带走了整个数据。

如果有这个能力,是不是 2001 太空漫游的黑石碑我们也能造了!!如果没有这个能力呢?我们能不能通过一些简单的几个数字的运算得到这个值?也许是 π/6 在 2^19937−1 小数位的截断?如果是这样,那是不是只需要在一张纸上写下这个规则即可。

当然,找到这个答案的消耗可能比带走磁盘成本大的多...

回归正题

能不能通过几个简单数字通过一定规则输出这些随机整数?

如果 N 是个质数,把 1~N 首尾相连成环。

因为质数的性质,只能被 1 和本身分解,因此随机一个[1,N]之间的数做为初始值,再随机一个[1,N)之间的数作为步长,是不是就能够稳定输出一个看似随机的数,并且不重复。

如图,N=7,start=3,step=2 就会得到一个 3572461 的随机序列,但是这显然不够随机,可以根据 35 即可推断出后续的随机数,这时候再引入一个时针的概念,每次取数时随机一个时针方向和这个时针下的取数长度。


如图,中途的随机转向可以提供一个较好的随机性,但依然不够随机,因为在初始位置和初始步长决定后,无法再获得更好的随机性,比如,不会出现 354 这样的序列。

关于随机性,可以增加嵌套环的方式,用多组初始值和步长来叠加,但依旧不够随机(如何把有序的东西在不引入相对无序下变成无序?又挖一个坑,哈哈哈)

如果 N 是个合数呢?

目前想到的是两种方案

一种是剔除 N 的所有质因数,比如遇到一个因数后随机向前或向后跳跃,直到遇到下一个非因数。但这个过程对一些大数在因数密集区要找很久。

另一种方案是,找到下一个质数,然后忽略掉之间的假数。

我们暂时采用找到下一个质数的方案简单实现一下:

Code

Talk is cheap, show me the code...

Java

package com.waitmoon.wm.rand;
import java.math.BigInteger;import java.util.concurrent.ThreadLocalRandom;
public final class WmRandom { private final long l; private final long r; private long cw; private long ccw; private long step; private ThreadLocalRandom random; private long fr; private long t; private final long s; private long ms; private boolean d; private long ns;
/** * @param r [0,r] */ public WmRandom(long r) { this(0, r, 200); }
/** * @param l left inclusive * @param r right inclusive */ public WmRandom(long l, long r) { this(l, r, 200); }
public void reset() { synchronized (this) { random = ThreadLocalRandom.current(); this.ns = fr - l; this.step = random.nextLong(1L, (ns >> 1) + 1L); while (step != 1L && (ns + 1L) % step == 0) { this.fr = BigInteger.valueOf(ns + 1L).nextProbablePrime().longValue() + l - 1L; this.ns = fr - l; } if (fr <= l) { throw new IllegalArgumentException("fillBound illegal"); } this.cw = random.nextLong(l, fr); long i = cw - step; this.ccw = i < l ? ns + i + 1L : i; this.t = 0; this.ms = random.nextLong(l, r); this.d = random.nextBoolean(); } }
/** * @param l left inclusive * @param r right inclusive * @param certainty certainty of prime */ public WmRandom(long l, long r, int certainty) { if (l >= r) { throw new IllegalArgumentException("origin must be non-negative and bound must be positive and origin must be less than bound"); } this.s = r - l + 1L; this.l = l; this.r = r; if (BigInteger.valueOf(s).isProbablePrime(certainty)) { this.fr = r; } else { this.fr = BigInteger.valueOf(s).nextProbablePrime().longValue() + l - 1L; } random = ThreadLocalRandom.current(); this.ns = fr - l; this.step = random.nextLong(1L, (ns >> 1) + 1L); while (step != 1 && (ns + 1L) % step == 0) { this.fr = BigInteger.valueOf(ns + 1L).nextProbablePrime().longValue() + l - 1L; this.ns = fr - l; } if (fr <= l) { throw new IllegalArgumentException("fillBound illegal"); } this.cw = random.nextLong(l, fr); long i = cw - step; this.ccw = i < l ? ns + i + 1L : i; this.ms = random.nextLong(l, r); this.d = random.nextBoolean(); }
/** * @return next value */ public long next() { long v; long ms; boolean d; synchronized (this) { if (t >= s) { throw new IllegalStateException("exhausted all possible values"); } d = this.d; ms = this.ms; long i; if (random.nextBoolean()) { v = cw; if (cw > r) { long g = fr - cw; cw = cw + g - g % step; i = cw + step; cw = i > fr ? i - ns - 1L : i; v = cw; } i = cw + step; cw = i > fr ? i - ns - 1L : i; } else { v = ccw; if (ccw > r) { long g = ccw - r - 1L; ccw = ccw - g + g % step; i = ccw - step; ccw = i < l ? ns + i + 1L : i; v = ccw; } i = ccw - step; ccw = i < l ? ns + i + 1L : i; } t++; } if (d) { if (v <= ms) { return v; } long f = ((r - ms) & 1) == 0 ? 1L : 0L; long m = ms + ((r - ms) >> 1) + 1L; return m - v - f + m; } if (v > ms) { return v; } long f = ((ms - l + 1L) & 1) == 0 ? 1L : 0L; long m = l + ((ms - l) >> 1) + f; return m - v - f + m; }}
复制代码

Golang

package random
import ( "errors" "math/big" "math/rand" "time")
type WmIdRand struct { l int64 r int64 cw int64 ccw int64 step int64 fr int64 t int64 s int64 certainty int random *rand.Rand ms int64 d bool ns int64}
func New(l, r int64) (*WmIdRand, error) { return NewWithCertainty(l, r, 200)}
func NewWithCertainty(l, r int64, certainty int) (*WmIdRand, error) { random := rand.New(rand.NewSource(time.Now().UnixNano())) if l >= r { return nil, errors.New("origin must be non-negative and bound must be positive and origin must be less than bound") } s := r - l + 1 var fr int64 if big.NewInt(s).ProbablyPrime(certainty) { fr = r } else { fr = nextProbablePrime(big.NewInt(s), certainty).Int64() + l - 1 } ns := fr - l step := random.Int63n(ns>>1) + 1 for step != 1 && (ns+1)%step == 0 { fr = nextProbablePrime(big.NewInt(ns+1), certainty).Int64() + l - 1 ns = fr - l } if fr <= l { return nil, errors.New("no prime number in the range") } cw := random.Int63n(ns) + l var ccw int64 if cw-step < l { ccw = ns + cw - step + 1 } else { ccw = cw - step } ms := rand.Int63n(r-l) + l d := random.Intn(2) == 0 return &WmIdRand{ l: l, r: r, cw: cw, ccw: ccw, step: step, fr: fr, t: 0, s: s, certainty: certainty, random: random, ms: ms, d: d, ns: ns, }, nil}
func (r *WmIdRand) Next() (int64, error) { if r.t >= r.s { return 0, errors.New("exhausted all possible values") } var v int64 if r.random.Intn(2) == 0 { v = r.cw if r.cw > r.r { g := r.fr - r.cw r.cw = r.cw + (g - g%r.step) if r.cw+r.step > r.fr { r.cw = r.cw + r.step - r.ns - 1 } else { r.cw = r.cw + r.step } v = r.cw } if r.cw+r.step > r.fr { r.cw = r.cw + r.step - r.ns - 1 } else { r.cw = r.cw + r.step } } else { v = r.ccw if r.ccw > r.r { g := r.ccw - r.r - 1 r.ccw = r.ccw - (g - g%r.step) if r.ccw-r.step < r.l { r.ccw = r.ns + r.ccw - r.step + 1 } else { r.ccw = r.ccw - r.step } v = r.ccw } if r.ccw-r.step < r.l { r.ccw = r.ns + r.ccw - r.step + 1 } else { r.ccw = r.ccw - r.step } } r.t++ if r.d { if v <= r.ms { return v, nil } m := r.ms + ((r.r - r.ms) >> 1) + 1 f := int64(0) if ((r.r - r.ms) & 1) == 0 { f = 1 } return m - v - f + m, nil } if v > r.ms { return v, nil } f := int64(0) if ((r.ms - r.l + 1) & 1) == 0 { f = 1 } m := r.l + ((r.ms - r.l) >> 1) + f return m - v - f + m, nil}
func (r *WmIdRand) Reset() error { r.random = rand.New(rand.NewSource(time.Now().UnixNano())) r.step = r.random.Int63n(r.ns>>1) + 1 if r.step != 1 && (r.ns+1)%r.step == 0 { r.fr = nextProbablePrime(big.NewInt(r.ns+1), r.certainty).Int64() + r.l - 1 r.ns = r.fr - r.l } if r.fr <= r.l { return errors.New("fillBound illegal") } r.cw = r.random.Int63n(r.ns) + r.l
if r.cw-r.step < r.l { r.ccw = r.ns + r.cw - r.step + 1 } else { r.ccw = r.cw - r.step } r.t = 0 r.ms = r.random.Int63n(r.r-r.l) + r.l r.d = r.random.Intn(2) == 0 return nil}
func nextProbablePrime(n *big.Int, certainty int) *big.Int { next := new(big.Int).Set(n) if next.Bit(0) == 0 { next.Add(next, big.NewInt(1)) } else { next.Add(next, big.NewInt(2)) } for { if next.ProbablyPrime(certainty) { return next } next.Add(next, big.NewInt(2)) }}
复制代码

可能有小伙伴好奇,为什么环不用 mod N 的形式,这样写起来也简单,因为 mod 的性能不如简单加减运算~


目前只想到了这种随机性不太够的方案,有新的方案欢迎探讨交流~

发布于: 刚刚阅读数: 4
用户头像

waitmoon

关注

致力于解决灵活繁复的硬编码问题~ 2020-03-30 加入

来都来了,坐下喝茶~看看ice~~

评论

发布
暂无评论
生成大边界不重复随机整数_伪随机函数_waitmoon_InfoQ写作社区