Generate the n-th random number with Python - python

I am trying to generate random numbers that are used to generate a part of a world (I am working on world generation for a game). I could create these with something like [random.randint(0, 100) for n in range(1000)] to generate 1000 random numbers from 0 to 100, but I don't know how many numbers in a list I need. What I want is to be able to say something like random.nth_randint(0, 100, 5) which would generate the 5th random number from 0 to 100. (The same number every time as long as you use the same seed) How would I go about doing this? And if there is no way to do this, how else could I get the same behavior?

Python's random module produces deterministic pseudo random values.
In simpler words, it behaves as if it generated a list of predetermined values when a seed is provided (or when default seed is taken from OS), and those values will always be the same for a given seed.
Which is basically what we want here.
So to get nth random value you need to either remember its state for each generated value (probably just keeping track of the values would be less memory hungry) or you need to reset (reseed) the generator each time and produce N random numbers each time to get yours.
def randgen(a, b, n, seed=4):
# our default seed is random in itself as evidenced by https://xkcd.com/221/
random.seed(seed)
for i in range(n-1):
x = random.random()
return random.randint(a, b)

If I understood well your question you want every time the same n-th number. You may create a class where you keep track of the generated numbers (if you use the same seed).
The main idea is that, when you ask for then nth-number it will generate all the previous in order to be always the same for all the run of the program.
import random
class myRandom():
def __init__(self):
self.generated = []
#your instance of random.Random()
self.rand = random.Random(99)
def generate(self, nth):
if nth < len(self.generated) + 1:
return self.generated[nth - 1]
else:
for _ in range(len(self.generated), nth):
self.generated.append(self.rand.randint(1,100))
return self.generated[nth - 1]
r = myRandom()
print(r.generate(1))
print(r.generate(5))
print(r.generate(10))

Using a defaultdict, you can have a structure that generates a new number on the first access of each key.
from collections import defaultdict
from random import randint
random_numbers = defaultdict(lambda: randint(0, 100))
random_number[5] # 42
random_number[5] # 42
random_number[0] # 63
Numbers are thus lazily generated on access.
Since you are working on a game, it is likely you will then need to preserve random_numbers through interruptions of your program. You can use pickle to save your data.
import pickle
random_numbers[0] # 24
# Save the current state
with open('random', 'wb') as f:
pickle.dump(dict(random_numbers), f)
# Load the last saved state
with open('random', 'rb') as f:
opened_random_numbers = defaultdict(lambda: randint(0, 100), pickle.load(f))
opened_random_numbers[0] # 24

Numpy's new random BitGenerator interface provides a method advance(delta) some of the BitGenerator implementations (including the default BitGenerator used). This function allows you to seed and then advance to get the n-th random number.
From the docs:
Advance the underlying RNG as-if delta draws have occurred.
https://numpy.org/doc/stable/reference/random/bit_generators/generated/numpy.random.PCG64.advance.html#numpy.random.PCG64.advance

Related

Index of element in random permutation for very large range

I am working with a very large range of values (0 to approx. 10^6128) and I need a way in Python to perform two-way lookups with a random permutation of the range.
Example with a smaller dataset:
import random
values = list(range(10)) # the actual range is too large to do this
random.shuffle(values)
def map_value(n):
return values[n]
def unmap_value(n):
return values.index(n)
I need a way to implement the map_value and unmap_value methods with values in the very large range above.
Creating a fixed permutation of 10**6128 values is costly - memory wise.
You can create values from your range on the fly and store them in one / two dictionaries.
If you only draw comparativly few values one dict might be enough, if you have lots of values you might need 2 for faster lookup.
Essentially you
lookup a value, if not present generate an index, store it and return it
lookup an index, if not present, generate a value, store it and return it
Using a fixed random seed should lead to same sequences:
import random
class big_range():
random.seed(42)
pos_value = {}
value_pos = {}
def map_value(self, n):
p = big_range.value_pos.get(n)
while p is None:
p = random.randrange(10**6128) # works, can't use random.choice(range(10**6128))
if p in big_range.pos_value:
p = None
else:
big_range.pos_value[p]=n
big_range.value_pos[n]=p
return p
def unmap_value(self, n):
p = big_range.pos_value.get(n)
while p is None:
p = random.randrange(10**6128) # works, can't use random.choice(range(10**6128))
if p in big_range.pos_value:
p = None
else:
big_range.pos_value[n]=p
big_range.value_pos[p]=n
return p
br = big_range()
for i in range(10):
print(br.map_value(i))
print(big_range.pos_value)
print(big_range.value_pos)
Output:
Gibberisch humongeous number ... but it works.
You store each number twice (once as pos:number, once as number:pos) for lookup reasons. You might want to check how many numbers you can generate before your memory goes out.
You can use one dict only, but looking up the value to an index is not O(1) but O(n) in that case because you need to traverse dict.items() to find the value and return the index.
The repeatability breaks if you do other random things in between because you alter the "state" of random - you might need to do some more capsulating and maybe statekeeping inside your class using random.getstate() / random.setstate() to store the last state after generation of a new random as well...
If you look for most of your values it will take longer and longer to produce a "not present one" if you simple keep looping indexes from 0 to 10**6128...
random.getstate()
random.setstate()
random.randrange()
This is kindof fragile and more of a thought experiment - I have no clue whatfor one needs a 10**6128 range of numbers...

How to use random.seed to create multiple different initial pseudo random numbers in Python?

I am using random.seed to generate pseudo-random numbers over a certain number of iterations. However, with this method, for the same number of iterations, it generates the same initial value each time. I am wondering if there is a way to write the code to generate 4 different random initial values that are in different locations in the parameter range? For example my code looks like this:
import random
N=10
random.seed(N)
vx1 = [random.uniform(-3,3) for i in range(N)]
And each time this will generate the starting vx1[0] = 0.428. Is there a way to write the code for it to generate four different initial values of vx1? So the initial value for vx1 could equal 0.428 or 3 other values. Then each initial value would also have the following 9 random numbers in the range.
I think you have a fundamental misunderstanding as to what random.seed does. "Random" number generators are actually deterministic systems that generate pseudo-random numbers. The seed is a label for a reproducible initial state. The whole point of it is that the same sequence of numbers will be generated for the same seed.
If you want to create a reproducible sequence of 1,000,000 numbers, use a seed:
s = 10
N = 1000000
random.seed(s)
vx1 = [random.uniform(-3, 3) for i in range(N)]
If you want to generate a different sequence every time, use a different seed every time. The simplest way to do this is to just not call seed:
N = 1000000
vx1 = [random.uniform(-3, 3) for i in range(N)]

Use Random.random [30,35] to print random numbers between 30,35. Seed(70)

Probably a simple answer, not sure what I am missing. For a homework assignment I have to use random.random() to generate numbers between 30 and 35. The seed has to be set to 70 to match the pseudo-random numbers with the grader. This wasn't in my lecture so I am a little stumped as to what to do.
I have:
import random
def problem2_4():
print(random.random(30,35))
But this is clearly wrong.
The assignment says the output should look like (note: for the problem i use def problem2_4() just for the assignment grading system)
problem2_4()
[34.54884618961936, 31.470395203793395, 32.297169396656095, 30.681793552717807,
34.97530360173135, 30.773219981037737, 33.36969776732032, 32.990127772708405,
33.57311858494461, 32.052629620057274]
The output [blah, blah, blah] indicates that it is a list of numbers rather than a series of numbers printed one-by-one.
In addition, if you want random floating point values, you'll need to transform the numbers from random.random (which are zero to one) into that range.
That means you'll probably need something like:
import random # Need this module.
def problem2_4():
random.seed(70) # Set initial seed.
nums = [] # Start with empty list.
for _ in range(10): # Will add ten values.
nums += [random.random() * 5 + 30] # Add one value in desired range.
print(nums) # Print resultant list.
Of course, the Pythonic way to do this would be:
import random
random.seed(70)
print([random.random() * 5 + 30 for _ in range(10)])
Bit that might be a bit ahead of where your educator is working. Still, it's good to learn this stuff as early as possile since you'll never be a Pythonista until you do :-)
The function that you are looking for is randint, which returns an integer (whole number) between the specified minimum and maximum values.
So the solution would be
random.randint(30, 35)
Python Docs

How to write code which will only repeat a random number once?

This is what I have so far:
import random
for x in range(10):
tickets = [random.sample(range(0,59), 6)]
print (tickets)
But I need to make it so that all the numbers generated are different except for two numbers which are the same.
So that's my problem and would appreciate help before Friday! This is the question I was asked for reference: "My new year's resolution is to win the lottery. To do this I will buy 10 tickets each week. I shall choose 6 numbers at random for each ticket. The numbers range from 1 to 59. All the numbers can only be used once except for one which will need to be duplicated. Write a program in python to simulate this."
You can avoid repeated numbers by keeping track of those that have been used and disallowing them in later samples:
import random
def non_repeating_random_sample(population, k, seen):
while True:
sample = random.sample(population, k)
if not any(number in seen for number in sample):
seen.union(sample)
return sample
seen = set()
for _ in range(10):
tickets = [non_repeating_random_sample(range(0, 59), 6, seen)]
print(tickets)
When doing something like this, it may be important to understand that the samples returned—except for the first one—aren't really random because of the additional restraint.
Regardless, it would be simpler and faster to just shuffle the entire population, and then extract groups of of the desired size from it:
import random
number_of_samples = 10
number_per_sample = 6
samples = list(range(number_of_samples*number_per_sample))
random.shuffle(samples)
generator = zip(*[iter(samples)]*number_per_sample)
for _ in range(number_of_samples):
print(list(next(generator)))

Generate big random sequence of unique numbers [duplicate]

This question already has answers here:
Unique (non-repeating) random numbers in O(1)?
(22 answers)
Closed 9 years ago.
I need to fill a file with a lot of records identified by a number (test data). The number of records is very big, and the ids should be unique and the order of records should be random (or pseudo-random).
I tried this:
# coding: utf-8
import random
COUNT = 100000000
random.seed(0)
file_1 = open('file1', 'w')
for i in random.sample(xrange(COUNT), COUNT):
file_1.write('ID{0},A{0}\n'.format(i))
file_1.close()
But it's eating all of my memory.
Is there a way to generate a big shuffled sequence of consecutive (not necessarily but it would be nice, otherwise unique) integer numbers? Using a generator and not keeping all the sequence in RAM?
If you have 100 million numbers like in the question, then this is actually manageable in-memory (it takes about 0.5 GB).
As DSM pointed out, this can be done with the standard modules in an efficient way:
>>> import array
>>> a = array.array('I', xrange(10**8)) # a.itemsize indicates 4 bytes per element => about 0.5 GB
>>> import random
>>> random.shuffle(a)
It is also possible to use the third-party NumPy package, which is the standard Python tool for managing arrays in an efficient way:
>>> import numpy
>>> ids = numpy.arange(100000000, dtype='uint32') # 32 bits is enough for numbers up to about 4 billion
>>> numpy.random.shuffle(ids)
(this is only useful if your program already uses NumPy, as the standard module approach is about as efficient).
Both method take about the same amount of time on my machine (maybe 1 minute for the shuffling), but the 0.5 GB they use is not too big for current computers.
PS: There are too many elements for the shuffling to be really random because there are way too many permutations possible, compared to the period of the random generators used. In other words, there are fewer Python shuffles than the number of possible shuffles!
Maybe something like (won't be consecutive, but will be unique):
from uuid import uuid4
def unique_nums(): # Not strictly unique, but *practically* unique
while True:
yield int(uuid4().hex, 16)
# alternative yield uuid4().int
unique_num = unique_nums()
next(unique_num)
next(unique_num) # etc...
You can fetch random int easily from reading (on linux) /dev/urandom or using os.urandom() and struct.unpack():
Return a string of n random bytes suitable for cryptographic use.
This function returns random bytes from an OS-specific randomness source. The returned data should be unpredictable enough for cryptographic applications, though its exact quality depends on the OS implementation. On a UNIX-like system this will query /dev/urandom, and on Windows it will use CryptGenRandom. If a randomness source is not found, NotImplementedError will be raised.
>>> for i in range(4): print( hex( struct.unpack('<L', os.urandom(4))[0]))
...
0xbd7b6def
0xd3ecf2e6
0xf570b955
0xe30babb6
While on the other hand random package:
However, being completely deterministic, it is not suitable for all purposes, and is completely unsuitable for cryptographic purposes.
If you really need unique records you should go with this or answer provided by EOL.
But assuming really random source, with possibly repeated characters you'll have 1/N (where N = 2 ** sizeof(int)*8 = 2 ** 32) chance of hitting item at first guess, thus you can get (2**32) ** length possible outputs.
On the other hand when using just unique results you'll have max:
product from i = 0 to length {2*32 - i}
= n! / (n-length)!
= (2**32)! / (2**32-length)!
Where ! is factorial, not logical negation. So you'll just decrease randomness of result.
This one will keep your memory OK but will probably kill your disk :)
It generates a file with the sequence of the numbers from 0 to 100000000 and then it randomly pick positions in it and writes to another file. The numbers have to be re-organized in the first file to "delete" the numbers that have been chosen already.
import random
COUNT = 100000000
# Feed the file
with open('file1','w') as f:
i = 0
while i <= COUNT:
f.write("{0:08d}".format(i))
i += 1
with open('file1','r+') as f1:
i = COUNT
with open('file2','w') as f2:
while i >= 0:
f1.seek(i*8)
# Read the last val
last_val = f1.read(8)
random_pos = random.randint(0, i)
# Read random pos
f1.seek(random_pos*8)
random_val = f1.read(8)
f2.write('ID{0},A{0}\n'.format(random_val))
# Write the last value to this position
f1.seek(random_pos*8)
f1.write(last_val)
i -= 1
print "Done"

Categories

Resources