Randomly generating 3-tuples with distinct elements in python - python

I am trying to generate 3-tuples (x,y,z) in python such that no two of x , y or z have the same value. Furthermore , the variables x , y and z can be defined over separate ranges (0,p) , (0,q) and (0,r). I would like to be able to generate n such tuples. One obvious way is to call random.random() for each variable and check every time whether x=y=z . Is there a more efficient way to do this ?

You can write a generator that yields desired elements, for example:
def product_no_repeats(*args):
for p in itertools.product(*args):
if len(set(p)) == len(p):
yield p
and apply reservoir sampling to it:
def reservoir(it, k):
ls = [next(it) for _ in range(k)]
for i, x in enumerate(it, k + 1):
j = random.randint(0, i)
if j < k:
ls[j] = x
return ls
xs = range(0, 3)
ys = range(0, 4)
zs = range(0, 5)
size = 4
print reservoir(product_no_repeats(xs, ys, zs), size)

Related

Python Nested Loops questions python3.0

I'm trying to write a nested loop that calculates (x+y)^2 for every value of y where the outer loop should print (x+y)^2. Here is my code so far:
x = [2,4,6,8,10,12,14,16,18]
y = [10,8.25,7.5,7,6.5,7,7.5,8.25,10]
for i in range(0 , len(x)):
# nested
#for j in range(0, len(y)):
for j in range(0, len(y)):
print( (x[i] + y[j])**2)
My output keeps printing repeated values like so:
144
105.0625
90.25
81
72.25
81
90.25
105.0625
144
Maybe you could try this:
NB - it works by zipping through two lists (same size) and do the math. If the size is unequal, then you should try zip_longest with some other default number (for this shorter list).
outs = []
for a, b in zip(x, y):
outs.append(pow((a+b), 2))
Example 2: one list is longer -
x = [2,4,6,8,10,12,14,16,18, 20] # adding 20 as extra number
y = [10,8.25,7.5,7,6.5,7,7.5,8.25,10]
from itertools import zip_longest #(a, b, fillvalue=0)
outs = []
for a, b in zip_longest(x, y, fillvalue=0):
outs.append(pow((a+b), 2))
Without using any library:
print([((x[i] if i < len(x) else 0) + (y[i] if i < len(y) else 0))**2 for i in range(max(len(x), len(y)))])
OUTPUT
[144, 150.0625, 182.25, 225, 272.25, 361, 462.25, 588.0625, 784]

Is there a pythonic way to sample N consecutive elements from a list or numpy array

Is there a pythonic way to select N consecutive elements from a list or numpy array.
So Suppose:
Choice = [1,2,3,4,5,6]
I would like to create a new list of length N by randomly selecting element X in Choice along with the N-1 consecutive elements following choice.
So if:
X = 4
N = 4
The resulting list would be:
Selection = [5,6,1,2]
I think something similar to the following would work.
S = []
for i in range(X,X+N):
S.append(Selection[i%6])
But I was wondering if there is a python or numpy function that can select the elements at once that was more efficient.
Use itertools, specifically islice and cycle.
start = random.randint(0, len(Choice) - 1)
list(islice(cycle(Choice), start, start + n))
cycle(Choice) is an infinite sequence that repeats your original list, so that the slice start:start + n will wrap if necessary.
You could use a list comprehension, using modulo operations on the index to keep it in range of the list:
Choice = [1,2,3,4,5,6]
X = 4
N = 4
L = len(Choice)
Selection = [Choice[i % L] for i in range(X, X+N)]
print(Selection)
Output
[5, 6, 1, 2]
Note that if N is less than or equal to len(Choice), you can greatly simplify the code:
Choice = [1,2,3,4,5,6]
X = 4
N = 4
L = len(Choice)
Selection = Choice[X:X+N] if X+N <= L else Choice[X:] + Choice[:X+N-L]
print(Selection)
Since you are asking for the most efficient way I created a little benchmark to test the solutions proposed in this thread.
I rewrote your current solution as:
def op(choice, x):
n = len(choice)
selection = []
for i in range(x, x + n):
selection.append(choice[i % n])
return selection
Where choice is the input list and x is the random index.
These are the results if choice contains 1_000_000 random numbers:
chepner: 0.10840400000000017 s
nick: 0.2066781999999998 s
op: 0.25887470000000024 s
fountainhead: 0.3679908000000003 s
Full code
import random
from itertools import cycle, islice
from time import perf_counter as pc
import numpy as np
def op(choice, x):
n = len(choice)
selection = []
for i in range(x, x + n):
selection.append(choice[i % n])
return selection
def nick(choice, x):
n = len(choice)
return [choice[i % n] for i in range(x, x + n)]
def fountainhead(choice, x):
n = len(choice)
return np.take(choice, range(x, x + n), mode='wrap')
def chepner(choice, x):
n = len(choice)
return list(islice(cycle(choice), x, x + n))
results = []
n = 1_000_000
choice = random.sample(range(n), n)
x = random.randint(0, n - 1)
# Correctness
assert op(choice, x) == nick(choice,x) == chepner(choice,x) == list(fountainhead(choice,x))
# Benchmark
for f in op, nick, chepner, fountainhead:
t0 = pc()
f(choice, x)
t1 = pc()
results.append((t1 - t0, f))
for t, f in sorted(results):
print(f'{f.__name__}: {t} s')
If using a numpy array as the source, we could of course use numpy "fancy indexing".
So, if ChoiceArray is the numpy array equivalent of the list Choice, and if L is len(Choice) or len(ChoiceArray):
Selection = ChoiceArray [np.arange(X, N+X) % L]
Here's a numpy approach:
import numpy as np
Selection = np.take(Choice, range(X,N+X), mode='wrap')
Works even if Choice is a Python list rather than a numpy array.

How to make list comprehensions more readable?

I have this bit of code below, and i think it's kinda hard to understand if you are new to python.
How would i go about making it more readable to a group of newcomers to python (students)
def right_inwrongplace(userGuess, number):
correct_places = [True if v == number[i] else False for i, v in enumerate(userGuess)]
g = [v for i, v in enumerate(userGuess) if not correct_places[i]]
n = [v for i, v in enumerate(number) if not correct_places[i]]
return len([i for i in g if i in n])
Here are a few improvements:
True if x else False is simply bool(x) or, as you are doing a comparison already, just that expression, i.e. v == number[i].
Since you are accessing the number by positional index you can just zip the two sequences.
So for the first you'd get:
correct_places = [x == y for x, y in zip(userGuess, number)]
The same argument with zip applies to the following two comprehensions (you can again iterate over the original string):
g = [x for x, y in zip(userGuess, number) if x != y]
n = [y for x, y in zip(userGuess, number) if x != y]
Given that this is basically the same comprehension two times, and that we don't need correct_places anymore we can instead do the following:
g, n = zip(*[(x, y) for x, y in zip(userGuess, number) if x != y])
Then you can sum instead of len:
return sum(x in n for x in g)
So basically you can use the following code:
g, n = zip(*(xy for xy in zip(userGuess, num) if xy[0] != xy[1])
return sum(x in n for x in g)

How to probabilistically populate a list in python?

I want to use a basic for loop to populate a list of values in Python but I would like the values to be calculate probabilistically such that p% of the time the values are calculated in (toy) equation 1 and 100-p% of the time the values are calculated in equation 2.
Here's what I've got so far:
# generate list of random probabilities
p_list = np.random.uniform(low=0.0, high=1.0, size=(500,))
my_list = []
# loop through but where to put 'p'? append() should probably only appear once
for p in p_list:
calc1 = x*y # equation 1
calc2 = (x-y) # equation 2
my_list.append(calc1)
my_list.append(calc2)
You've already generated a list of probabilities - p_list - that correspond to each value in my_list you want to generate. The pythonic way to do so is via a a ternary operator and a list comprehension:
import random
my_list = [(x*y if random() < p else x-y) for p in p_list]
If we were to expand this into a proper for loop:
my_list = []
for p in p_list:
if random() < p:
my_list.append(x*y)
else:
my_list.append(x-y)
If we wanted to be even more pythonic, regarding calc1 and calc2, we could make them into lambdas:
calc1 = lambda x,y: x*y
calc2 = lambda x,y: x-y
...
my_list = [calc1(x,y) if random() < p else calc2(x,y) for p in p_list]
or, depending on how x and y vary for your function (assuming they're not static), you could even do the comprehension in two steps:
calc_list = [calc1 if random() < p else calc2 for p in p_list]
my_list = [calc(x,y) for calc in calc_list]
I took approach of minimal changes to the original code and easy to understand syntax:
import numpy as np
p_list = np.random.uniform(low=0.0, high=1.0, size=(500,))
my_list = []
# uncomment below 2 lines to make this code syntactially correct
#x = 1
#y = 2
for p in p_list:
# randoms are uniformly distributed over the half-open interval [low, high)
# so check if p is in [0, 0.5) for equation 1 or [0.5, 1) for equation 2
if p < 0.5:
calc1 = x*y # equation 1
my_list.append(calc1)
else:
calc2 = (x-y) # equation 2
my_list.append(calc2)
The other answers seem to assume you want to keep the calculated chances around. If all you are after is a list of results for which equation 1 was used p% of the time and equation 2 100-p% of the time, this is all you need:
from random import random, seed
inputs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# change the seed to see different 'random' outcomes
seed(1)
results = [x * x if random() > 0.5 else 2 * x for x in inputs]
print(results)
If you are ok to use numpy worth trying the choice method.
https://docs.scipy.org/doc/numpy-1.14.1/reference/generated/numpy.random.choice.html

For loop on two arrays of points

I have two arrays of points: list1 with list1.shape = [N, 3] and list2 with list2.shape = [M, 3]. Within N, M: the total number of point, (x, y, z) are 3 coordinates in 3D.
Now I want to check if each point of list1 is within a distance r with each point of list2. A nature way to do this is a for loop:
for i in range(N):
for j in range(M):
if (list1[i, 0] - list2[j, 0])**2 + (list1[i, 1] - list2[j, 1])**2 + (list1[i, 2] - list2[j, 2])**2 < r**2:
''' Return 1 if list1[i] is within list2[j] '''
return True
else:
''' Return 0 if list1[i] is not within list2[j] '''
return False
But it's horribly slow. Could I do the more efficient way?
You can use the outer operations to calculate the distance matrix without the for loops:
s = np.subtract.outer
d_matrix = s(list1[:,0], list2[:,0])**2
d_matrix += s(list1[:,1], list2[:,1])**2
d_matrix += s(list1[:,2], list2[:,2])**2
Where each line is the distance of point i about all points. To find out if point i is close to any point using your criterion:
a = np.zeros_like(list1[:,0])
a[np.any(d_matrix < r**2, axis=1)] = 1

Categories

Resources