Create a random number excluding one number

I am writing a Monty Hall simulator and found it necessary to generate a number within a range excluding one number.

It seemed easy, so I naively wrote:

(The g/... functions are part of my personal library. Their use should be clear enough):

 (defn random-int-excluding "Generates a random number between min-n and max-n; excluding excluding-n. min-n is inclusive, while max-n is exclusive." [min-n max-n excluding-n rand-gen] (let [rand-n (g/random-int min-n max-n rand-gen) rand-n' (if (= rand-n excluding-n) (inc rand-n) rand-n)] (g/wrap rand-n' min-n (inc max-n)))) 

This generates a random number within the range and, if it is equal to the excluded number, adds it; if necessary. Of course, this led to the fact that after the excluded quantity doubled, it was chosen, since it was chosen if it was selected or excluded. Samples of output frequencies for the range from 0 to 10 (max. Exception), with the exception of 2:

 ([0 0.099882] [1 0.100355] [3 0.200025] [4 0.099912] [5 0.099672] [6 0.099976] [7 0.099539] [8 0.100222] [9 0.100417]) 

Then I read this answer , which seemed much simpler and based on it, wrote:

 (defn random-int-excluding "Generates a random number between min-n and max-n; excluding excluding-n. min-n is inclusive, while max-n is exclusive." [min-n max-n excluding-n rand-gen] (let [r1 (g/random-int min-n excluding-n rand-gen) r2 (g/random-int (inc excluding-n) max-n rand-gen)] (if (g/random-boolean rand-gen) r1 r2))) 

In principle, it divides the range into 2 smaller ranges: from minus to the number excluded, and from the excluded number + 1 to max. It generates a random number from these ranges, then randomly selects one of them. Unfortunately, although, as I noted in the answer, this gives distorted results if both sections do not have the same size. Samples of output frequencies; same conditions as above:

 ([0 0.2499497] [1 0.2500795] [3 0.0715849] [4 0.071297] [5 0.0714366] [6 0.0714362] [7 0.0712715] [8 0.0715285] [9 0.0714161]) 

Please note that the number of parts of a smaller range before the excluded number is much more likely. To fix this, I would have to skew it to select numbers from a larger range more often, and indeed, I am not good enough at maths in general to understand how to do this.

I looked at the accepted answer from a related question, but for me it seems like a version of my first attempt, which allows to exclude more than one number. I would expect, compared to what the answering machine claimed, the numbers at the end of the exclusion range would be preferable, because if you select a number that is within the excluded range, it simply increases the number outside the range.

Since this will be one of the most named functions in the simulation, I would really like to avoid the “brute force” method of the cycle, while the generated number is excluded, since the range will contain only 3 numbers, so there is a 1/3 chance that You will need to try every attempt.

Does anyone know a simple algorithm for choosing a random number from a continuous range, but excludes one number?

+5
source share
5 answers

Just create a lazy sequence and filter out the elements you don't need:

 (let [ignore #{4 2}] (frequencies (take 2000 (remove ignore (repeatedly #(rand-int 5)))))) 

The advantage of a different matching approach to various new values: this function will also work with various discrete distributions of random numbers .

+1
source

To generate a number in the range [a, b] excluding c , simply generate a number in the range [a, b-1] , and if the result is c , then print b .

+6
source

Just to show the implementation I wrote, here is what worked for me:

 (defn random-int-excluding "Generates a random number between min-n and max-n; excluding excluding-n. min-n is inclusive, while max-n is exclusive." [min-n max-n excluding-n rand-gen] (let [rand-n (g/random-int min-n (dec max-n) rand-gen)] (if (= rand-n excluding-n) (dec max-n) rand-n))) 

Which gives a good uniform distribution:

 ([0 0.111502] [1 0.110738] [3 0.111266] [4 0.110976] [5 0.111162] [6 0.111266] [7 0.111093] [8 0.110815] [9 0.111182]) 
+1
source

If the collection size of acceptable answers is small, just put all the values ​​in a vector and use rand-nth :

http://clojuredocs.org/clojure.core/rand-nth

 (def primes [ 2 3 5 7 11 13 17 19] ) (println (rand-nth primes)) (println (rand-nth primes)) (println (rand-nth primes)) ~/clj > lein run 19 13 11 

Update

If some of the values ​​should contain more than others, just put them in an array of values ​​more than once. The number of events of each value determines its relative weight:

 (def samples [ 1 2 2 3 3 3 4 4 4 4 ] ) (def weighted-samples (repeatedly #(rand-nth samples))) (println (take 22 weighted-samples)) ;=> (3 4 2 4 3 2 2 1 4 4 3 3 3 2 3 4 4 4 2 4 4 4) 

If we need any number from 1 to 5, but never 3, just do the following:

 (def samples [ 1 2 4 5 ] ) (def weighted-samples (repeatedly #(rand-nth samples))) (println (take 22 weighted-samples)) (1 5 5 5 5 2 2 4 2 5 4 4 5 2 4 4 4 2 1 2 4 1) 
+1
source

Just to answer Alan Malloy explicitly:

 (defn rand-int-range-excluding [from to without] (let [n (+ from (rand-int (dec (- to from))))] (if (= n without) (dec to) n))) (->> #(rand-int-range-excluding 5 10 8) repeatedly (take 100) frequencies) ;{6 28, 9 22, 5 29, 7 21} 

No votes. :).

+1
source

Source: https://habr.com/ru/post/1259484/


All Articles