random: move sequence support to random-ref, in racket/random.

And add `random-sample`.
This commit is contained in:
Vincent St-Amour 2016-01-02 15:49:28 -06:00
parent 6af2f711b7
commit 707f9bffa6
7 changed files with 113 additions and 72 deletions

View File

@ -847,10 +847,6 @@ both in binary and as integers.
[rand-gen pseudo-random-generator? [rand-gen pseudo-random-generator?
(current-pseudo-random-generator)]) (current-pseudo-random-generator)])
exact-nonnegative-integer?] exact-nonnegative-integer?]
[(random [seq sequence?]
[rand-gen pseudo-random-generator?
(current-pseudo-random-generator)])
any/c]
[(random [rand-gen pseudo-random-generator? [(random [rand-gen pseudo-random-generator?
(current-pseudo-random-generator)]) (current-pseudo-random-generator)])
(and/c real? inexact? (>/c 0) (</c 1))])]{ (and/c real? inexact? (>/c 0) (</c 1))])]{
@ -861,10 +857,6 @@ exact integer in the range @racket[0] to @math{@racket[k]-1}.
When called with two integer arguments @racket[min] and @racket[max], returns a When called with two integer arguments @racket[min] and @racket[max], returns a
random exact integer in the range @racket[min] to @math{@racket[max]-1}. random exact integer in the range @racket[min] to @math{@racket[max]-1}.
When called with a sequence argument @racket[seq], returns a random element of
the sequence. Like @racket[sequence-length], does not terminate on infinite
sequences, and extracts elements up to the returned element.
When called with zero arguments, returns a random inexact number between When called with zero arguments, returns a random inexact number between
@racket[0] and @racket[1], exclusive. @racket[0] and @racket[1], exclusive.
@ -873,7 +865,10 @@ generator (which defaults to the current one, as produced by
@racket[current-pseudo-random-generator]). The generator maintains an @racket[current-pseudo-random-generator]). The generator maintains an
internal state for generating numbers. The random number generator internal state for generating numbers. The random number generator
uses a 54-bit version of L'Ecuyer's MRG32k3a algorithm uses a 54-bit version of L'Ecuyer's MRG32k3a algorithm
@cite["L'Ecuyer02"].} @cite["L'Ecuyer02"].
@history[#:changed "6.4"]{Added support for ranges.}}
@defproc[(random-seed [k (integer-in 1 (sub1 (expt 2 31)))]) @defproc[(random-seed [k (integer-in 1 (sub1 (expt 2 31)))])
void?]{ void?]{
@ -944,16 +939,17 @@ three integers is non-zero. Otherwise, the result is @racket[#f].}
@; ------------------------------------------------------------------------ @; ------------------------------------------------------------------------
@subsection{System-Provided Randomness} @subsection{Other Randomness Utilities}
@defmodule[racket/random]{The @racketmodname[racket/random] module @defmodule[racket/random]{}
provides an interface to randomness from the underlying operating
system. Use @racket[crypto-random-bytes]
instead of @racket[random] wherever security is a concern.}
@defproc[(crypto-random-bytes [n exact-positive-integer?]) @defproc[(crypto-random-bytes [n exact-positive-integer?])
bytes?]{ bytes?]{
Provides an interface to randomness from the underlying operating system. Use
@racket[crypto-random-bytes] instead of @racket[random] wherever security is a
concern.
Returns @racket[n] random bytes. On Unix systems, the bytes are Returns @racket[n] random bytes. On Unix systems, the bytes are
obtained from @filepath{/dev/urandom}, while Windows uses obtained from @filepath{/dev/urandom}, while Windows uses
the @tt{RtlGenRand} system function. the @tt{RtlGenRand} system function.
@ -964,6 +960,34 @@ the @tt{RtlGenRand} system function.
@history[#:added "6.3"]} @history[#:added "6.3"]}
@defproc[(random-ref [seq sequence?]
[rand-gen pseudo-random-generator?
(current-pseudo-random-generator)])
any/c]{
Returns a random element of the sequence. Like @racket[sequence-length], does
not terminate on infinite sequences, and extracts elements up to the returned
element.
@history[#:added "6.4"]}
@defproc[(random-sample [seq sequence?]
[n exact-positive-integer?]
[rand-gen pseudo-random-generator?
(current-pseudo-random-generator)]
[#:replacement? replacement? any/c #t])
(listof any/c)]{
Returns a list of @racket[n] elements of @racket[seq], picked at random.
If @racket[replacement?] is non-false, elements are drawn with replacement,
which allows for duplicates.
Like @racket[sequence-length], does not terminate on infinite sequences, and
extracts elements up to the returned element.
@history[#:added "6.4"]}
@; ------------------------------------------------------------------------ @; ------------------------------------------------------------------------
@subsection{Number--String Conversions} @subsection{Number--String Conversions}

View File

@ -3,7 +3,7 @@
(Section 'numbers) (Section 'numbers)
(require racket/extflonum) (require racket/extflonum racket/random)
(test #f number? 'a) (test #f number? 'a)
(test #f complex? 'a) (test #f complex? 'a)
@ -2452,12 +2452,12 @@
(test (begin (random-seed 23) (list (random 10 20) (random 20 30) (random 30 40))) (test (begin (random-seed 23) (list (random 10 20) (random 20 30) (random 30 40)))
'random-seed-same2 'random-seed-same2
(begin (random-seed 23) (list (random 10 20) (random 20 30) (random 30 40)))) (begin (random-seed 23) (list (random 10 20) (random 20 30) (random 30 40))))
(test (begin (random-seed 23) (list (random '(1 2 3)) (random '(4 5 6)) (random '(7 8 9)))) (test (begin (random-seed 23) (list (random-ref '(1 2 3)) (random-ref '(4 5 6)) (random-ref '(7 8 9))))
'random-seed-same3 'random-seed-same3
(begin (random-seed 23) (list (random '#(1 2 3)) (random '#(4 5 6)) (random '#(7 8 9))))) (begin (random-seed 23) (list (random-ref '#(1 2 3)) (random-ref '#(4 5 6)) (random-ref '#(7 8 9)))))
(test (begin (random-seed 23) (list (random '#(1 2 3)) (random '#(4 5 6)) (random '#(7 8 9)))) (test (begin (random-seed 23) (list (random-ref "123") (random-ref "123") (random-ref "123")))
'random-seed-same4 'random-seed-same4
(begin (random-seed 23) (list (random '#(1 2 3)) (random '#(4 5 6)) (random '#(7 8 9))))) (begin (random-seed 23) (list (random-ref "123") (random-ref "123") (random-ref "123"))))
(arity-test random-seed 1 1) (arity-test random-seed 1 1)
(arity-test random 0 3) (arity-test random 0 3)
(err/rt-test (random-seed "apple")) (err/rt-test (random-seed "apple"))
@ -2514,9 +2514,16 @@
(parameterize ([current-pseudo-random-generator (parameterize ([current-pseudo-random-generator
(vector->pseudo-random-generator (vector->pseudo-random-generator
#(3620087466 1904163406 3177592043 1406334318 257151704 3090455638))]) #(3620087466 1904163406 3177592043 1406334318 257151704 3090455638))])
(test 3 random '(1 2 3 4 5)) (test 3 random-ref '(1 2 3 4 5))
(test 10 random '#(7 6 8 9 10)) (test 10 random-ref '#(7 6 8 9 10))
(test #\e random "abcde")) (test #\e random-ref "abcde"))
(parameterize ([current-pseudo-random-generator
(vector->pseudo-random-generator
#(3620087466 1904163406 3177592043 1406334318 257151704 3090455638))])
(test '(3) random-sample '(1 2 3 4 5) 1)
(test '(5 5 5) random-sample '(1 2 3 4 5) 3)
(test '(2 4 5) random-sample '(1 2 3 4 5) 3 #:replacement? #f))
(test #t = 0 0) (test #t = 0 0)
(test #f = 0 (expt 2 32)) (test #f = 0 (expt 2 32))

View File

@ -78,9 +78,6 @@
sequence-generate sequence-generate
sequence-generate* sequence-generate*
prop:sequence prop:sequence
sequence-length
-sequence-ref
define-sequence-syntax define-sequence-syntax
make-do-sequence make-do-sequence
@ -2032,29 +2029,4 @@
#true #true
[(next-body l d init-dir use-dir?)])]]))) [(next-body l d init-dir use-dir?)])]])))
(define-values (sequence-length)
(λ (s)
(unless (sequence? s) (raise-argument-error 'sequence-length "sequence?" s))
(for/fold ([c 0]) ([i (in-values*-sequence s)])
(add1 c))))
(define-values (-sequence-ref)
(λ (s i)
(unless (sequence? s) (raise-argument-error 'sequence-ref "sequence?" s))
(unless (exact-nonnegative-integer? i)
(raise-argument-error 'sequence-ref "exact-nonnegative-integer?" i))
(let-values ([(v) (for/fold ([c #f]) ([v (in-values*-sequence s)]
[j (in-range (add1 i))]
#:unless (j . < . i))
(or v '(#f)))])
(cond
[(not v)
(raise-arguments-error
'sequence-ref
"sequence ended before index"
"index" i
"sequence" s)]
[(list? v) (apply values v)]
[else v]))))
) )

View File

@ -113,38 +113,27 @@
(case-lambda (case-lambda
[() (random)] ; no args, random float [() (random)] ; no args, random float
[(x) [(x)
;; one arg, either random float with prng, or random integer, or random ;; one arg, either random float with prng, or random integer
;; sequence element
(cond [(exact-positive-integer? x) (cond [(exact-positive-integer? x)
(enforce-random-int-range x) (enforce-random-int-range x)
(random x)] (random x)]
[(pseudo-random-generator? x) [(pseudo-random-generator? x)
(random x)] (random x)]
[(sequence? x)
(-sequence-ref x (random (sequence-length x)))]
[else [else
(raise-argument-error (raise-argument-error
'random 'random
"(or/c (integer-in 1 4294967087) sequence? pseudo-random-generator?)" "(or/c (integer-in 1 4294967087) pseudo-random-generator?)"
x)])] x)])]
[(x y) [(x y)
;; two args, either min and prng, or min and max, or sequence and prng ;; two args, either min and prng, or min and max
(cond [(exact-positive-integer? y) ; min and max case (cond [(exact-positive-integer? y) ; min and max case
(enforce-random-int-range x) (enforce-random-int-range x)
(enforce-random-int-range y) (enforce-random-int-range y)
(enforce-greater x y) (enforce-greater x y)
(+ x (random (- y x)))] (+ x (random (- y x)))]
[(pseudo-random-generator? y) [(pseudo-random-generator? y) ; int and prng case
(cond [(exact-positive-integer? x) ; int and prng case
(enforce-random-int-range x) (enforce-random-int-range x)
(random x y)] (random x y)]
[(sequence? x) ; sequence and prng case
(-sequence-ref x (random (sequence-length x) y))]
[else
(raise-argument-error
'random
"(or/c (integer-in 1 4294967087) sequence?)"
x)])]
[else [else
(raise-argument-error (raise-argument-error
'random 'random

View File

@ -9,9 +9,7 @@
sequence-ormap sequence-ormap
sequence-for-each sequence-for-each
sequence-fold sequence-fold
sequence-count sequence-count)
sequence-length
(rename-out [-sequence-ref sequence-ref]))
(define (sequence-andmap f s) (define (sequence-andmap f s)
(unless (procedure? f) (raise-argument-error 'sequence-andmap "procedure?" f)) (unless (procedure? f) (raise-argument-error 'sequence-andmap "procedure?" f))

View File

@ -1,7 +1,13 @@
#lang racket/base #lang racket/base
(require "private/unix-rand.rkt" "private/windows-rand.rkt" racket/contract/base) (require "private/unix-rand.rkt" "private/windows-rand.rkt"
(provide (contract-out [crypto-random-bytes (-> exact-nonnegative-integer? bytes?)])) racket/contract/base racket/sequence racket/set)
(provide (contract-out [crypto-random-bytes (-> exact-nonnegative-integer? bytes?)]
[random-ref (->* (sequence?) (pseudo-random-generator?) any/c)]
[random-sample (->* (sequence? exact-nonnegative-integer?)
(pseudo-random-generator?
#:replacement? any/c)
(listof any/c))]))
; (: crypto-random-bytes (-> Positive-Integer Bytes)) ; (: crypto-random-bytes (-> Positive-Integer Bytes))
; returns n random bytes from the os. ; returns n random bytes from the os.
@ -12,3 +18,25 @@
[else (raise (make-exn:fail:unsupported [else (raise (make-exn:fail:unsupported
"not supported on the current platform" "not supported on the current platform"
(current-continuation-marks)))])) (current-continuation-marks)))]))
(define (random-ref seq [prng (current-pseudo-random-generator)])
(sequence-ref seq (random (sequence-length seq))))
(define (random-sample seq n [prng (current-pseudo-random-generator)]
#:replacement? [replacement? #t])
(cond [replacement?
(for/list ([i (in-range n)])
(random-ref seq prng))]
[else
(unless (>= (sequence-length seq) n)
(raise-argument-error 'random-sample
"integer less than sequence length"
n))
(define l (sequence-length seq))
;; sequences don't necessarily support removal, so instead sample
;; indices without replacement, then index into the sequence
(let loop ([res-idx (set)])
(cond [(= (set-count res-idx) n) ; we have all we need, we're done
(for/list ([i (in-set res-idx)]) (sequence-ref seq i))]
[else
(loop (set-add res-idx (random l)))]))]))

View File

@ -39,6 +39,29 @@
(define (sequence->list s) (define (sequence->list s)
(for/list ([v s]) v)) (for/list ([v s]) v))
(define (sequence-length s)
(unless (sequence? s) (raise-argument-error 'sequence-length "sequence?" s))
(for/fold ([c 0]) ([i (in-values*-sequence s)])
(add1 c)))
(define (sequence-ref s i)
(unless (sequence? s) (raise-argument-error 'sequence-ref "sequence?" s))
(unless (exact-nonnegative-integer? i)
(raise-argument-error 'sequence-ref "exact-nonnegative-integer?" i))
(let ([v (for/fold ([c #f]) ([v (in-values*-sequence s)]
[j (in-range (add1 i))]
#:unless (j . < . i))
(or v '(#f)))])
(cond
[(not v)
(raise-arguments-error
'sequence-ref
"sequence ended before index"
"index" i
"sequence" s)]
[(list? v) (apply values v)]
[else v])))
(define (sequence-tail seq i) (define (sequence-tail seq i)
(unless (sequence? seq) (raise-argument-error 'sequence-tail "sequence?" seq)) (unless (sequence? seq) (raise-argument-error 'sequence-tail "sequence?" seq))
(unless (exact-nonnegative-integer? i) (unless (exact-nonnegative-integer? i)