4

I'm wondering if there's a way to generate decreasing numbers within a certain range? I want to program to keep outputting until it reaches 0, and the highest number in the range must be positive.

For example, if the range is (0, 100), this could be a possible output: 96 57 43 23 9 0

Sorry for the confusion from my original post

Fiona Kwok
  • 87
  • 2
  • 8
  • What are your inputs? Do you need to be able to specify or guarantee how many numbers are output? Should all the numbers generated be unique? – Silas Ray Jan 10 '13 at 15:09
  • 1
    Also, do you want integers? Floating point values? Can the range include 0 or negative values? – Silas Ray Jan 10 '13 at 15:17
  • 4
    The semantics in this question seem to be producing a variety of answers. Is it the range which is decreasing or just the numbers? Are you looking for a generator or are you happy with just producing a list of numbers. – Lewis Norton Jan 10 '13 at 15:23
  • @Fiona, do you know *random variables* and *probability distributions*? If not, find a maths book with a title like 'introduction to probability' and read the first chapter. Then you'll have the language to state your problem precisely, and—if you read further—the methods to solve it. – Colonel Panic Jan 10 '13 at 16:27
  • I have to run a loop that keeps sending numbers until it reaches 0. So the range can go from 0 to a very large number. Here is a short list as an example: 105, 97, 43, 35, 29, 18, 3, 0. – Fiona Kwok Jan 11 '13 at 21:54

8 Answers8

18

I would generate a list of n random numbers then sort them highest to lowest.

Lewis Norton
  • 6,911
  • 1
  • 19
  • 29
  • 1
    That wouldn't be as random as generating a new number every time in a narrower range though. – jd. Jan 10 '13 at 15:16
  • 4
    "as random" is quite a vague term. I would say it would be a different type of randomness. I think this would be uniform. – Andy Hayden Jan 10 '13 at 15:18
  • @hayden Yeah, you would get a more statistically uniform distribution across the initial range over time with this method over clamping to the last generated value, I think. It does sacrifice efficiency though. – Silas Ray Jan 10 '13 at 15:20
  • Exactly. This solution is uniform, while the other is definetly not. Also it is quite easy to optimize it - instead of sorting you can just add new elements in order. – freakish Jan 10 '13 at 15:21
  • You are still creating a bunch of values in memory instead of generating them one at a time, there's really no way around that without fundamentally reworking the algorithm. – Silas Ray Jan 10 '13 at 15:22
  • 1
    The question is ambiguous as to whether Fiona wants a generator or numbers generating. This is my answer given the idea she just wants them generating on the spot. – Lewis Norton Jan 10 '13 at 15:24
  • Well, really the question is too vague to provide a meaningful answer to as currently written. ;) – Silas Ray Jan 10 '13 at 15:25
  • @hayden: by "not as random" I mean given a number, the next one is more easily predictable with this solution. – jd. Jan 10 '13 at 15:27
  • @jd I'm not so sure I agree with you, maybe early ones but otherwise results heavily (exponentially?) skew towards 0. Certainly the OP is too vague on the requirements! – Andy Hayden Jan 10 '13 at 15:30
  • @hayden we certainly hit the limits of the hardware quickly (< 800 iterations). I agree that some context is required to decide the best solution. – jd. Jan 10 '13 at 15:33
  • @jd I'm not sure what "more easily predictable" means. Anyway I've posted my answer with efficient, without hardware limits and uniformly distributed solution. – freakish Jan 11 '13 at 00:10
6

Few thing have to be noted. The algorithm which starts in X > 0 and in each step takes a random number from (0,X) and replaces X with it is no good. Why? Because ( assuming random behaves properly ) expected value in each step is in the middle of interval (0,X). This implies that the sequence of these numbers is expected to converge to 0 as fast as (1/2)^N. And indeed it can be easily seen that majority of numbers are near 0, even for enormous inital value. This means that the distribution of these numbers is not uniform, which is a desired property most of the time.

This is a major drawback, even though the complexity of generating Nth number is O(N) and ( what is more important ) memory usage is O(1).

The other solution is to just take N random numbers and sort them. This is not bad, although complexity of this algorithm is O(N log(N)) ( or rather the same as the complexity of underlying sorting algorithm ), which can be reduced to O(N) if we put elements in order instead of sorting, but memory usage is O(N) - we have to remember all elements. However these numbers will be uniformly distributed, which is a great advantage!

Following the idea in the paper "Generating sorted lists of random numbers" by Jon Louis Bentley here's the algorithm which probably is the most optimal one ( at least known to me ) and produces uniformly distributed numbers:

import math
import random

def generate( min = 0, max = 10, number = 100 ):
    start = 0
    for i in xrange( number, 0, -1 ):
        start = start + math.log( random.random( ) ) / i
        next = math.exp( start ) * ( max - min ) + min
        yield next

for number in generate( ):
    print number

Note that complexity of this algorithm is still O(N) ( which I doubt can get lower ) but memory usage is O(1) and these numbers are uniformly distributed in interval (min,max), which is not that obvious, but true. The only drawback is that we have to know how many numbers we want to generate before starting.

Also have a look at this thread:

Generating sorted random ints without the sort? O(n)

Might be useful.

Community
  • 1
  • 1
freakish
  • 54,167
  • 9
  • 132
  • 169
  • Thank you, your code works very well, but is there any way to generate integers only or do they have to be floats to avoid repetition? – Fiona Kwok Jan 11 '13 at 21:59
  • @FionaKwok Well, you can always yield integer part of `next` and do that only when it is different then the previous value of `next` yielded. However you lose control over how many integeres you will get, but since the distribution will stilll be uniform then you will get some definetly. So if you want to have control over everything, then probably generating random values and putting them in order is the way to do it. – freakish Jan 11 '13 at 22:28
2

like:

from random import random
min=0
max=10
oldval=1.

while True:
  oldval=oldval*random()
  randval=min+(max-min)*oldval
umläute
  • 28,885
  • 9
  • 68
  • 122
  • This is pretty solid, as long as you are trying to generate floating point values, or you don't mind repetition in ints, and you don't have interest in controlling the number of outputs. – Silas Ray Jan 10 '13 at 15:14
  • The same issue with this solution: it converges to 0 extremely fast, the distribution is not uniform at all. – freakish Jan 10 '13 at 15:57
  • definitely; but itdidn't say anyhting about uniform distributions being required – umläute Jan 10 '13 at 16:00
2

Here are a few alternatives. This should produce close to a chi-square distribution of values, with later values selected from a smaller range than earlier values:

import random
random_range = range(10) 
numbers = [random.choice(random_range[:i]) for i in range(10, 0, -1)]

This can also be done with floats:

import random
max = 10.0
min = 0.0
desired = 100
step = (max - min) / desired
numbers = [random.random() * (max - (i * step)) for i in range(desired)]

Alternatively, selecting random values from a decreasing sliding window can provide a uniform distribution.

import random, numpy
max = 10.0
min = 0.0
desired = 100
step = float(min - max) / desired
window = 1.0
numbers = [x + (random.random() * window) - (window / 2.0) for x in numpy.arange(max, min, step)]

If a monotonically-decreasing list of numbers is required, then setting window <= step will provide one. Good luck!

brentlance
  • 2,189
  • 1
  • 19
  • 25
1

Building on @brentlance's idea, this will work for any integer range, positive, negative, or both:

import random

random_decreasing_integers_from_range = (i for i in xrange(max, min - 1, -1) if random.random() > .5)

If you want to be able to specify the number of outputs, here's an attempt at it that at least attempts to keep the distribution across the range somewhat uniform:

import random

def random_decreasing_integers_from_range(min, max, num_outputs):
    range_size = abs(max - min)
    if range_size < num_outputs:
        raise ValueError('Distance from min to max must be equal to or greater than num_outputs.')
    output_count = 0
    for iteration, value in enumerate(xrange(max, min - 1, -1)):
        # if we only have enough values left to satisfy the number requested,
        # yield value
        if num_outputs - output_count == range_size - iteration + 1:
            output_count += 1
            yield value
        # else yield value randomly, weighted by how far in to the range we are
        # and how many values we have left to yield of the total requested
        else:
            ratio_consumed = float(iteration + 1) / range_size
            ratio_yielded = float(output_count) / num_outputs
            if random.random() < (1 - ratio_yielded) * ratio_consumed:
                output_count += 1
                yield value
        # if we've yielded the requested number of values, stop
        if output_count == num_outputs:
            break

This works reasonably well, but it seems to break down when num_outputs isn't between about 10% and 25% of range_size. At the lower bound, favoring the middle of the range really shows through, while at the upper bound, the short circuit condition starts to cause the results to really favor the lower end of the range.

Silas Ray
  • 25,682
  • 5
  • 48
  • 63
  • I had been working on a more verbose version that used itertools, but I simplified it to this and forgot to remove the import. Removed now. – Silas Ray Jan 10 '13 at 16:18
1

Thanks, everybody for your answers. But I found a solution to my own problem which I thought was quite simple, and I would like to share that with all of you.

import random
i = 1000000  

while i > 0:
    i = random.randint(0, i)
    print i
Fiona Kwok
  • 87
  • 2
  • 8
0

This will give you decreasing random numbers in the range 0..1.

import random
def generate():
    n = 1.0
    while n:
        n = random.random() * n
        yield n
iterator = generate()
iterator.next()

Note that the function stops yielding numbers after a while as the numbers inevitably reach 0 given the limited precision of floating point numbers.

jd.
  • 10,678
  • 3
  • 46
  • 55
0

Not an expert in python...but here's the basic idea I have:

a=10000
for i in range(1,50):
   b=random.randint(1,a)
   print(b)
   a=b
Aadith Ramia
  • 10,005
  • 19
  • 67
  • 86
  • Note a big weakness of this solution: since in each stop you shorten the range, then it converges to `1` ( and it does that very fast, exponentialy actually ). So the distribution is not uniform at all and for large enough range ( 1000? ) most of elements will be simply 1. – freakish Jan 10 '13 at 15:50