200新新新新200新200新新旗新新200新新200新新200新新200新新新200新新200新新新新新新200 [英] Generate unique random numbers in a loop

查看:114
本文介绍了200新新新新200新200新新旗新新200新新200新新200新新200新新新200新新200新新新新新新200的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

OK,经过几个小时的疯狂调试,我终于有了:

 (defmacro assoc-bind 
(let *((i(gensym))
(exp(gensym))
(abindings
(let((cursor bindings)result)
(while cursor
(push(caar cursor)result)
(push(cdar cursor)result)
(setq cursor(cdr cursor)))
(setq result(nreverse结果))
(cons(list i`(quote,result))
(cons(list exp expression)result)))))
`(let(,@ abindings)
(while,i
(set(car,i)(caar,exp))
(setq,i(cdr,i))
(set(car,i) ,exp))
(setq,i(cdr,i),exp(cdr,exp)))
,@ body)))

(let((i 0 )(限制100)(test(make-string 100? - ))
bag bag-iter next-random last)
(while(< i limit)
;; bag i是((分钟)最大)...
(setq bag-iter bag next-random(random limit))
(messageoriginal-random:%dnext-random)
(如果bag- iter
(catch't
(setq last nil)
(而bag-iter
;;这里不能使用`destructuring-bind'
;;它的错误如果没有足够的利益
(assoc-bind
((lower-a。upper-a)(lower-b。upper-b))
bag-iter
(cond
;; CASE 0:============没有更多的conses
((和(null lower-b)(> = next-random upper-a))
(cond
((= next-random upper-a)
(if(<(1+ next-random)limit)
(setcdr(car bag-iter)下一个随机))
(setcar(car bag-iter)(incf next-random))
(when(和last(= 1( - (cdar last)next-random)))
(setcdr(car last)a)
(setcdr last nil))))
;;增加右
((=( - next-random upper-a)1)
(setcdr(car bag-iter)next-random))
;;添加新的cons
(t(setcdr bag-iter
(list(cons next-random next-random)))))
(消息case 0)
't nil))
;;案例1:第一个
前的============((< next-random lower-a)
(if(=(1+ next-random) a)
(setcar(car bag-iter)next-random)
(如果最后
(setcdr last
(cons(cons next-random next-random))
)b $ b(set(包含下一个随机的随机)))))
(消息case 1)
(throw't nil) )
;;案例2:第一个范围中的============
((< next-random upper-a)
(if(或(和(&((>( - -random lower-a)
( - upper-a next-random))
(<(1+ upper-a)limit))
(= lower-a 0))
;;修改右
(progn
(setq next-random(1+ upper-a))
(setcdr(car bag-iter)next-random)
(和(低)b(=( - lower-b next-random)1))
;;收敛右
(setcdr(car bag-iter)upper-b)
setcdr bag-iter(cddr bag-iter))))
;;修改left
(setq next-random(1- lower-a))
(setcar(car bag-iter) next-random)
(when(and last(=( - next-random(cdar last))1))
;;收敛左
(setcdr(car last)upper-a)
(setcdr last(cdr bag-iter))))
(消息case 2)
(throw' t nil))
;;案例3:中间的$ {$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b((= next-random upper-a)
(setq next-random(1+ next-random))
(setcdr(car bag-iter)next-random)
(=( - lower-b next-random)1)
;;收敛左,如果需要
(setcdr(car bag-iter)upper-b)
(setcdr bag-iter cddr bag-iter)))
;;收敛右
((=( - lower-b upper-a)1)
(setcdr(car bag-iter)upper-b)
(setcdr bag-iter(cddr bag-iter)))
;;增加left
((=( - next-random 1)upper-a)
(setcdr汽车袋迭代)next-random)
(when(= next-random(1- lower-b))
(setcdr(car bag -iter)upper-b)
(setcdr bag-iter(cddr bag-iter))))
;;减去右
((=( - lower-b next-random)1)
(setcar(cadr bag-iter)next-random))
;;我们有一个新的利益空间
(t(setcdr bag-iter
(cons(cons next-random next-random))
(cdr bag-iter))))
(消息case 3)
(throw't nil)))
(setq last bag-iter bag-iter(cdr bag-iter))))
(setq bag (list(cons next-random next-random))))
(messagenext-random:%dnext-random)
(messagebag:%sbag)
(char-equal(aref test next-random)?x)
(throw nil nil))
(aset test next-random?x)
(incf i))
(消息测试))

它的作品,但它是超级丑陋。当我开始工作时,我想到,该函数不应该占用多达十几行代码。很高兴希望我的初步假设不是那么遥远,我要求您尽量帮助整理。



如果阅读我的代码让你头痛(我绝对可以理解!)以下是对上述内容的描述:



在给定的间隔内生成随机数(为了简单起见,零开始,最多为限制)。每次迭代确保新生成的数字是唯一的,通过对已经生成的预先记录的数字范围进行验证。这些范围以 alist ,即((min-0。max-0)(min-1。max-1)的形式存储。 ..(min-N,max-N))。检查新生成的随机数不在任何范围内时,使用该数字,并使用生成的数字更新范围。否则,该号码将被这样的数字所代替,该数字距离它所在的范围的最小或最大值更接近,但不能超过限制或为负数。



更新范围的规则:



给定N =新的随机数,两个范围((a。b)(c。d))
可能会发生以下更改:

 如果N < a  -  1:((N。N)(a。b)(c。d))
如果N < a +(b-a)/ 2:(((a-a).b)(c。d))
如果N < b和(c-b)> 2:((a。(1+ b))(c。d))
如果N <如果N = c-1,则(b)((c-b))= 2:((a。d))
< c:((a。b)(N。N)(c。d))

我覆盖了所有的案例。



如果您有方法来描述算法的时间/空间复杂性,那么奖励积分:)另外,如果你能想到另一种方法问题,或者您可以肯定地说,在这种情况下,分配统一性有问题,请告诉!



编辑:



现在太累了测试,但这里是另一个想法,以防万一:

 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ 

(除非是字节分隔符(setq byte-separator,))
(let((result
(with-output-to-string
(princ[ )
(++(对于我跨数组)
(如果bigendian
(++(对于j从0到(每位字节))
(princ( logand 1(lsh ij))))
(++(对于j(从每位字节)到0)
(princ(logand 1(lsh ij)))))
(princ byte-separator)))))
(if(> (长度结果)1)
(aset结果(1-(长度结果))?\])
(setq result(concat result])))
result))

(defun random-in-range(限制和可选位)
(除非位(setq位31))
(let((i 0)字符串限制 - ))
(缓存(make-vector(ceiling limit bits)0))
下一个随机搜索
left-shift right-shift
(while
(setq next-random(random limit))
(let *((除数(floor next-random bits))
(提醒(lsh 1( - 随机(*除数位))))
(if(=(log((isf缓存除数)提醒)0)
;;我们有一个很好的随机
(aset缓存除数(isf缓存除数)提醒))
;;将搜索最接近的未设置位
(setq left-shift(1- next-random)
right-shift(1+ next-random)
搜索t)
(我ssage有碰撞%snext-random)
(同时搜索
;;步骤左边再试一次
(当(>左移0)
(setq除数(floor left-shift bits)
reminder(lsh 1( - left-shift(* divisor bits )))
(if(=(log((isf cache divisor)))0)
(setf next-random left-shift
search nil
(isf cache divisor)
(logior(aref cache divisor)reminder))
(decf left-shift)))
;;一步一步
(when(and searching(< right-shift limit))
(setq除数(floor right-shift bits)
reminder(lsh 1( - right-shift (*除数位)))
(if(=(log((isf cache divisor)))0)
(setf next-random right-shift
search nil
isf cache divisor)
(logior(aref cache divisor)reminder))
(incf right-shift))))))
(incf i)
(messagecache: %s(pprint-bytearray cache t 31))
(when(char-equal(aref test next-random)?x)
(throw nil next-random))
(aset test next-random?x)
(messagenext-random:%dnext-random))))

(随机范围100)
哪个应该将内存使用量减少到31(可能是32,我不知道多少的位一个int可以安全地在eLisp中使用,ints似乎是平台依赖的)。



I.e。我们可以分组31个数字中的自然数,并且在每个这样的组内,可以将其所有成员(或其组合)存储为单个int(每个数字只需要一个位来显示其存在)。这使得搜索最近的未使用的邻居有点复杂,但是31次内存减少(而不需要动态分配)的好处看起来像是一个很好的视角。



EDIT2:



好的,我终于弄清楚了如何用位掩码。更新了上面的代码。这可以节省高达64倍(我认为是)的内存,您可以随机生成内存。

解决方案

为了更简单的方法,只需要在所需间隔内生成一个数字序列,然后将它们洗牌。然后,当你需要一个随机数字的时候,只需把下一个列表取下来。



这样可以确保所需的间隔内的所有数字都有一次,并且获得的每个随机数是唯一的,如果您通过它,整个间隔将被耗尽。



这些符合您的要求,因为我了解它们。

OK, after few hours of insane debugging, I finally have this:

(defmacro assoc-bind (bindings expression &rest body)
  (let* ((i (gensym))
         (exp (gensym))
         (abindings
          (let ((cursor bindings) result)
            (while cursor
              (push (caar cursor) result)
              (push (cdar cursor) result)
              (setq cursor (cdr cursor)))
            (setq result (nreverse result))
            (cons (list i `(quote ,result))
                  (cons (list exp expression) result)))))
    `(let (,@abindings)
       (while ,i
         (set (car ,i) (caar ,exp))
         (setq ,i (cdr ,i))
         (set (car ,i) (cdar ,exp))
         (setq ,i (cdr ,i) ,exp (cdr ,exp)))
       ,@body)))

(let ((i 0) (limit 100) (test (make-string 100 ?-))
      bag bag-iter next-random last)
  (while (< i limit)
    ;; bag is an alist of a format of ((min . max) ...)
    (setq bag-iter bag next-random (random limit))
    (message "original-random: %d" next-random)
    (if bag-iter
        (catch 't
          (setq last nil)
          (while bag-iter
            ;; cannot use `destructuring-bind' here,
            ;; it errors if not enough conses
            (assoc-bind
                ((lower-a . upper-a) (lower-b . upper-b))
                bag-iter
              (cond
               ;; CASE 0: ============ no more conses
               ((and (null lower-b) (>= next-random upper-a))
                (cond
                 ((= next-random upper-a)
                  (if (< (1+ next-random) limit)
                      (setcdr (car bag-iter) (incf next-random))
                    (setcar (car bag-iter) (incf next-random))
                    (when (and last (= 1 (- (cdar last) next-random)))
                      (setcdr (car last) upper-a)
                      (setcdr last nil))))
                 ;; increase right
                 ((= (- next-random upper-a) 1)
                    (setcdr (car bag-iter) next-random))
                  ;; add new cons
                  (t (setcdr bag-iter
                             (list (cons next-random next-random)))))
                (message "case 0")
                (throw 't nil))
               ;; CASE 1: ============ before the first
               ((< next-random lower-a)
                (if (= (1+ next-random) lower-a)
                    (setcar (car bag-iter) next-random)
                  (if last
                      (setcdr last
                              (cons (cons next-random next-random)
                                    bag-iter))
                    (setq bag (cons (cons next-random next-random) bag))))
                (message "case 1")
                (throw 't nil))
               ;; CASE 2: ============ in the first range
               ((< next-random upper-a)
                (if (or (and (> (- next-random lower-a)
                                (- upper-a next-random))
                             (< (1+ upper-a) limit))
                        (= lower-a 0))
                    ;; modify right
                    (progn
                      (setq next-random (1+ upper-a))
                      (setcdr (car bag-iter) next-random)
                      (when (and lower-b (= (- lower-b next-random) 1))
                        ;; converge right
                        (setcdr (car bag-iter) upper-b)
                        (setcdr bag-iter (cddr bag-iter))))
                  ;; modify left
                  (setq next-random (1- lower-a))
                  (setcar (car bag-iter) next-random)
                  (when (and last (= (- next-random (cdar last)) 1))
                    ;; converge left
                    (setcdr (car last) upper-a)
                    (setcdr last (cdr bag-iter))))
                (message "case 2")
                (throw 't nil))
               ;; CASE 3: ============ in the middle
               ((< next-random lower-b)
                (cond
                 ;; increase previous
                 ((= next-random upper-a)
                  (setq next-random (1+ next-random))
                  (setcdr (car bag-iter) next-random)
                  (when (= (- lower-b next-random) 1)
                    ;; converge left, if needed
                    (setcdr (car bag-iter) upper-b)
                    (setcdr bag-iter (cddr bag-iter))))
                 ;; converge right
                 ((= (- lower-b upper-a) 1)
                  (setcdr (car bag-iter) upper-b)
                  (setcdr bag-iter (cddr bag-iter)))
                 ;; increase left
                 ((= (- next-random 1) upper-a)
                  (setcdr (car bag-iter) next-random)
                  (when (= next-random (1- lower-b))
                    (setcdr (car bag-iter) upper-b)
                    (setcdr bag-iter (cddr bag-iter))))
                 ;; decrease right
                 ((= (- lower-b next-random) 1)
                  (setcar (cadr bag-iter) next-random))
                 ;; we have room for a new cons
                 (t (setcdr bag-iter
                            (cons (cons next-random next-random)
                                  (cdr bag-iter)))))
                (message "case 3")
                (throw 't nil)))
              (setq last bag-iter bag-iter (cdr bag-iter)))))
      (setq bag (list (cons next-random next-random))))
    (message "next-random: %d" next-random)
    (message "bag: %s" bag)
    (when (char-equal (aref test next-random) ?x)
      (throw nil nil))
    (aset test next-random ?x)
    (incf i))
  (message test))

It works, but it is super ugly. When I started working on this I imagined that the function should not take more then some dozen lines of code. In good hope that my initial assumption was not so far off, I'm asking you to try to help to tidy this up.

If reading my code gives you a headache (I can absolutely understand it!) here's a description of what the above does:

Generates random numbers within given interval (starting with zero for simplicity and up to limit). Each iteration ensures that the newly generated number is unique by verifying it against the pre-recorded ranges of numbers that have already been generated. These ranges are stored in a form of alist, i.e. ((min-0 . max-0) (min-1 . max-1) ... (min-N . max-N)). After checking that the new generated random number is not within any range, that number is used, and the range is updated with the generated number. Otherwise, the number is replaced by such number, which is closer to it from either min or max of the range it is in, but it cannot exceed the limit or be negative.

Rules for updating ranges:

Given N = new random number, and the two ranges ((a . b) (c . d)) it is possible that the following changes will happen:

if N < a - 1: ((N . N) (a . b) (c . d))
if N < a + (b - a) / 2: (((1- a) . b) (c . d))
if N < b and (c - b) > 2: ((a . (1+ b)) (c . d))
if N < b and (c - b) = 2: ((a . d))
if N = c - 1: ((a . b) ((1- c) . d))
if N < c: ((a . b) (N . N) (c . d))

I hope I covered all cases.

Bonus points if you have a way to describe the time/space complexity of the algo :) Also, if you can think of another approach to the problem, or you can certainly tell that there's something wrong with uniformity of distribution in this case, do tell!

EDIT:

Too tired to test it at the moment, but here was another idea I had, just in case:

(defun pprint-bytearray
  (array &optional bigendian bits-per-byte byte-separator)
  (unless bits-per-byte (setq bits-per-byte 32))
  (unless byte-separator (setq byte-separator ","))
  (let ((result
         (with-output-to-string
           (princ "[")
           (++ (for i across array)
             (if bigendian
                 (++ (for j from 0 downto (- bits-per-byte))
                   (princ (logand 1 (lsh i j))))
               (++ (for j from (- bits-per-byte) to 0)
                 (princ (logand 1 (lsh i j)))))
             (princ byte-separator)))))
    (if (> (length result) 1)
        (aset result (1- (length result)) ?\])
      (setq result (concat result "]")))
    result))

(defun random-in-range (limit &optional bits)
  (unless bits (setq bits 31))
  (let ((i 0) (test (make-string limit ?-))
        (cache (make-vector (ceiling limit bits) 0))
        next-random searching
        left-shift right-shift)
    (while (< i limit)
      (setq next-random (random limit))
      (let* ((divisor (floor next-random bits))
             (reminder (lsh 1 (- next-random (* divisor bits)))))
        (if (= (logand (aref cache divisor) reminder) 0)
            ;; we have a good random
            (aset cache divisor (logior (aref cache divisor) reminder))
          ;; will search for closest unset bit
          (setq left-shift (1- next-random)
                right-shift (1+ next-random)
                searching t)
          (message "have collision %s" next-random)
          (while searching
            ;; step left and try again
            (when (> left-shift 0)
              (setq divisor (floor left-shift bits)
                    reminder (lsh 1 (- left-shift (* divisor bits))))
              (if (= (logand (aref cache divisor) reminder) 0)
                  (setf next-random left-shift
                        searching nil
                        (aref cache divisor)
                        (logior (aref cache divisor) reminder))
                (decf left-shift)))
            ;; step right and try again
            (when (and searching (< right-shift limit))
              (setq divisor (floor right-shift bits)
                    reminder (lsh 1 (- right-shift (* divisor bits))))
              (if (= (logand (aref cache divisor) reminder) 0)
                  (setf next-random right-shift
                        searching nil
                        (aref cache divisor)
                        (logior (aref cache divisor) reminder))
                (incf right-shift))))))
      (incf i)
      (message "cache: %s" (pprint-bytearray cache t 31 ""))
      (when (char-equal (aref test next-random) ?x)
        (throw nil next-random))
      (aset test next-random ?x)
      (message "next-random: %d" next-random))))

(random-in-range 100)

Which should reduce the memory usage by the factor of 31 (maybe it can be 32, I don't know how many bits of an int it is safe to use in eLisp, ints seem to be platform dependant).

I.e. we can divide the natural numbers in groups 31 numbers each, and within each such group there would be possible to store all of its members (or their combinations) as a single int (each one of the numbers needs only one bit to show its presence). This makes searching for the nearest unused neighbour somewhat more convoluted, but the benefit of 31 times memory reduction (and no need of dynamic allocation) looks like a good perspective...

EDIT2:

OK, I finally figured out how to do that with bit mask. Updated the code above. This can save up memory up to by the factor of 64 (I think so...) of the range, where you generate random.

解决方案

For a simpler approach, just generate a sequence of numbers in the desired interval, then shuffle them. Then, when you need a random number, just take the next one off that list.

This ensures that all the numbers in the desired interval are there once and only once, and that each random number acquired is unique and that the entire interval will be exhausted if you go through it.

These satisfy your requirements as I understand them.

这篇关于200新新新新200新200新新旗新新200新新200新新200新新200新新新200新新200新新新新新新200的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆