开发者

Produce 10 (or less) uniformly distributed random floats using a list comprehension by filtering based on random value

开发者 https://www.devze.com 2023-03-24 00:09 出处:网络
For some strange reason, let\'s say that I want use List Comprehensions -- and List Comprehensions only -- to generate 10 random numbers larger than 0.5. For the \"10 random numbers\" part of the prob

For some strange reason, let's say that I want use List Comprehensions -- and List Comprehensions only -- to generate 10 random numbers larger than 0.5. For the "10 random numbers" part of the problem we w开发者_开发问答ould use:

samples = [ random.random() for x in range(10) ]

Now, for the "larger than 0.5", how one would implement using LC?

samples = [ random.random() for x in range(10) if ??? ]


What are you describing is called rejection sampling, and is a bad idea. For example if you wanted only numbers >0.999, it would take 1000 times longer to generate them.


Best way

The correct way to do this is to use another sampling technique, such as using the inverse of the CDF (cumulative density function), and doing [inverseCDF(random()) for _ in range(10)]. In your case, this would be, like GaretJax suggests, [0.5+random()/2 for _ in range(10)]


Intent

I think your intent was being able to further filter based on the entire expression of a list comprehension. In that case, you would put your iterable (here with parentheses rathern than [...] for laziness, though it doesn't matter) inside another comprehension:

[r for r in (random.random() for x in range(10)) if r>0.5]

For a better solution along these lines that can handle rejection, see the "Solution with list comprehensions only" section or eryksun's answer.


Solution with generators

(see below for a list-comprehension only solution)

You can do this for any linear range. If your rejection function is arbitrarily complex however, a general way to do rejection sampling would be as follows. (Again, a very bad idea unless you know what you're doing and efficiency doesn't matter.)

from random import random
from itertools import *

def randoms():
    while True:
        yield random()

def rejectionSample(pred, n):
    return islice(filter(pred, randoms()), n)

Example:

>>> print( list(rejectionSample(lambda x:x>0.5)) )
[0.6656564857979361, 0.9850389778418555, 0.9607471536139308, 0.9191328900300356, 0.810783093197139]

You could also do something like:

def rejectionSample(pred, n):
    count = 0
    while count<n:
        r = random()
        if pred(r):
            yield r
            count += 1 

Solution with list comprehensions only

However since you want to use list comprehensions only, this means the expression-part of your comprehension cannot fail, so you have to somehow embed a while loop in the comprehension. This is impossible to do with a single lambda function alone, but we can pull it off as long as we have some recursive/loopy primitive, for example...

[next(filter(pred,randoms())) for _ in range(10)]

(If you really wanted a one-liner list comprehension, randoms() can be rewritten as (random() for _ in count()).) Again, this is unnecessary if you easily find the analytic inverse cumulative distribution function for your particular distribution.


edit: I take that back... it... is possible... with just lambdas...

DEAR GOD HAVE MERCY WHAT HORRORS HAVE I UNLEASHED UPON THE WORLD NOOOOOO

[
 (lambda f:f(f,random()))(lambda self,r:r if r>0.5 else self(self,random()))
 for _ in range(10)
]


The random specific problem can easily be solved like this:

samples = [ random.random()/2 + .5 for x in range(10) ]

But I think you're question is aimed to address the specific problem where you want to execute the test on a generated item. To do this you can use multiple list comprehensions, or even better, use a generator and a list comprehension to limit the number of iterations:

samples = (random.random() for x in range(10))
samples = [x for x in samples if x > .5]

Note that you can insert other generator-based filters between the first generator and the last list comprehension. This will let you do whatever you want in one single pass. Consider these interesting presentations on the topic: http://www.dabeaz.com/generators/ and http://www.dabeaz.com/coroutines/ (in the second one the author finishes up by implementing a simple operating system by using coroutines and generators).

Now, this code does not address the fact that you want exactly 10 items. Let me try to find and code up an example and edit this answer (edit: just saw your comment about "10 or less", so this already replies to your question... I may anyway try to find a solution which gives you 10 items, just for fun :) ).

Edit:

I came up with the following solution. It is not purely LC/generator based as it uses the itertools module, but I though it will be interesting to post:

import itertools
import random

samples = (random.random() for _ in itertools.count())
samples = (x for x in samples if x > .5)
samples = [next(samples) for _ in range(10)]

Though, you can easily replace the itertools.count function with the following code:

def iiter():
    while True:
        yield None

All this does not make much sense, you can get to the desired result without all this thinking by simply constructing you own generator, but list comprehension and generators are two of my favorite python features!


The best I can do, as others have shown, is to use a sample generator that filters the function. But rather than use itertools, I'll use the built-in iter function:

>>> import random; random.seed(42)

>>> sample = (x for x in iter(lambda:random.random(), 1) if x > 0.5)
>>> samples = [next(sample) for _ in range(10)]

>>> print(', '.join(format(s, '0.3f') for s in samples))
0.639, 0.736, 0.677, 0.892, 0.505, 0.650, 0.545, 0.589, 0.809, 0.806


I think you mean

samples = [ x for x in range(10) if random.random() > 0.5 ]

or

samples = [ (lambda r: r > 0.5 and r or 0)(random.random()) for x in range(10) ]

The second one might not be syntatically correct as i don't have python-able machine with me atm.

EDIT:- I know this has an accepted answer and I know I have two downvotes but still:

samples = [(0.5 + (random.random() * 0.5)) for x in range(10)]

gives you 10 random numbers between 0.5 and 1

0

精彩评论

暂无评论...
验证码 换一张
取 消