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?
(current-pseudo-random-generator)])
exact-nonnegative-integer?]
[(random [seq sequence?]
[rand-gen pseudo-random-generator?
(current-pseudo-random-generator)])
any/c]
[(random [rand-gen pseudo-random-generator?
(current-pseudo-random-generator)])
(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
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
@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
internal state for generating numbers. The random number generator
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)))])
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
provides an interface to randomness from the underlying operating
system. Use @racket[crypto-random-bytes]
instead of @racket[random] wherever security is a concern.}
@defmodule[racket/random]{}
@defproc[(crypto-random-bytes [n exact-positive-integer?])
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
obtained from @filepath{/dev/urandom}, while Windows uses
the @tt{RtlGenRand} system function.
@ -964,6 +960,34 @@ the @tt{RtlGenRand} system function.
@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}

View File

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

View File

@ -78,9 +78,6 @@
sequence-generate
sequence-generate*
prop:sequence
sequence-length
-sequence-ref
define-sequence-syntax
make-do-sequence
@ -2032,29 +2029,4 @@
#true
[(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
[() (random)] ; no args, random float
[(x)
;; one arg, either random float with prng, or random integer, or random
;; sequence element
;; one arg, either random float with prng, or random integer
(cond [(exact-positive-integer? x)
(enforce-random-int-range x)
(random x)]
[(pseudo-random-generator? x)
(random x)]
[(sequence? x)
(-sequence-ref x (random (sequence-length x)))]
[else
(raise-argument-error
'random
"(or/c (integer-in 1 4294967087) sequence? pseudo-random-generator?)"
"(or/c (integer-in 1 4294967087) pseudo-random-generator?)"
x)])]
[(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
(enforce-random-int-range x)
(enforce-random-int-range y)
(enforce-greater x y)
(+ x (random (- y x)))]
[(pseudo-random-generator? y)
(cond [(exact-positive-integer? x) ; int and prng case
(enforce-random-int-range x)
(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)])]
[(pseudo-random-generator? y) ; int and prng case
(enforce-random-int-range x)
(random x y)]
[else
(raise-argument-error
'random

View File

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

View File

@ -1,7 +1,13 @@
#lang racket/base
(require "private/unix-rand.rkt" "private/windows-rand.rkt" racket/contract/base)
(provide (contract-out [crypto-random-bytes (-> exact-nonnegative-integer? bytes?)]))
(require "private/unix-rand.rkt" "private/windows-rand.rkt"
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))
; returns n random bytes from the os.
@ -12,3 +18,25 @@
[else (raise (make-exn:fail:unsupported
"not supported on the current platform"
(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)
(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)
(unless (sequence? seq) (raise-argument-error 'sequence-tail "sequence?" seq))
(unless (exact-nonnegative-integer? i)