Enumerating three variables in python list comprehension - python

I am trying to print all the possible enumerations of a list for three variables. For example if my input is:
x = 1
y = 1
z = 1
I want the output to be like:
[[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 1]]
If any of the x,y,z variables are higher than 1, it would enumerate all the integers from 0 to the variable value. For example, if x=3 then 0, 1, 2, or 3 would be possible in the first slot of the 3-element lists.
Right now I am creating the list comprehension like this:
output = [ [x,y,z] for x,y,z in range(x,y,z)]
I think something is wrong with the range function?

You could use the product() function from itertools as follows:
from itertools import product
answer = list(list(x) for x in product([0, 1], repeat=3))
print(answer)
Output
[[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]]

Complementary to the solutions using product, you could also use a triple list comprehension.
>>> x, y, z = 1, 2, 3
>>> [(a, b, c) for a in range(x+1) for b in range(y+1) for c in range(z+1)]
[(0, 0, 0),
(0, 0, 1),
(0, 0, 2),
(some more),
(1, 2, 2),
(1, 2, 3)]
The +1 is necessary since range does not include the upper bound.
If you want the output to be a list of lists, you can just do [[a, b, c] for ...].
Note, however, that this will obviously only work is you always have three variables (x, y, z), while product would work with an arbitrary number of lists/upper limits.

You can use range() function within a list comprehension and itertools.product function:
>>> x = 1
>>> y = 1
>>> z = 1
>>> from itertools import product
>>> list(product(*[range(i+1) for i in [x,y,z]]))
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]
This approach will work for different numbers too:
>>> x = 2
>>> y = 2
>>> z = 2
>>>
>>> list(product(*[range(i+1) for i in [x,y,z]]))
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (0, 2, 1), (0, 2, 2), (1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 2, 0), (1, 2, 1), (1, 2, 2), (2, 0, 0), (2, 0, 1), (2, 0, 2), (2, 1, 0), (2, 1, 1), (2, 1, 2), (2, 2, 0), (2, 2, 1), (2, 2, 2)]
>>>

If you need it in form of a list of lists (instead of list of tuples) you can use map over the output of the answer by Kasramvd, that is:
map(list,list(product(*[range(i+1) for i in [x,y,z]])))

Related

python permutations with more randomization

I'm trying to generate permutation from a list of indices, currently, I am using itertools.permutation. It's okay, except I need a really random nature of the indices as I will not be able to select all the permutations, but a very short subset of the total set (initial ones) for simulation.
For itertools.permutation:
The permutation tuples are emitted in lexicographic ordering according to the order of the input iterable. So, if the input iterable is sorted, the combination tuples will be produced in sorted order.
import itertools
for ind, idxs in enumerate(itertools.permutations(range(5))):
print(ind)
print(idxs)
print('--------')
0
(0, 1, 2, 3, 4)
--------
1
(0, 1, 2, 4, 3)
--------
2
(0, 1, 3, 2, 4)
--------
3
(0, 1, 3, 4, 2)
--------
4
(0, 1, 4, 2, 3)
--------
5
(0, 1, 4, 3, 2)
--------
6
(0, 2, 1, 3, 4)
--------
7
(0, 2, 1, 4, 3)
--------
8
(0, 2, 3, 1, 4)
--------
9
(0, 2, 3, 4, 1)
--------
10
(0, 2, 4, 1, 3)
--------
11
(0, 2, 4, 3, 1)
--------
12
(0, 3, 1, 2, 4)
--------
13
(0, 3, 1, 4, 2)
--------
One solution definitely comes to my mind is to shuffle the list each time to get a random order, but that makes the idea of permutation obsolete, which is not desired as there is a chance that the same sample will be generated more than once. The permutation should be generated iteratively, so I can not just do list(itertools.permutation..) as this will make a really unnecessary long list.
One way would be to shuffle before and/or after you generate the permutations.
For reference:
import itertools
import random
a = list(range(3))
print("original =",a)
random.shuffle(a)
print("shuffled =",a)
permutations = list(itertools.permutations(a))
print("permutations of shuffled array =",permutations)
random.shuffle(permutations)
print("shuffled permutations of shuffled array =",permutations)
original = [0, 1, 2]
shuffled = [1, 0, 2]
permutations of shuffled array = [(1, 0, 2), (1, 2, 0), (0, 1, 2), (0, 2, 1), (2, 1, 0), (2, 0, 1)]
shuffled permutations of shuffled array = [(0, 1, 2), (2, 0, 1), (2, 1, 0), (1, 0, 2), (1, 2, 0), (0, 2, 1)]
Generate random permutations : if you only use a small number k of them, the chance you take twice the same is k/n!.
Use random.sample:
permutations = list(itertools.permutations(range(5)))
permutation = random.sample(permutations, k=4)
# run 1
>>> random.sample(permutations, k=4)
[(0, 4, 1, 2, 3), (4, 0, 1, 3, 2), (3, 2, 0, 4, 1), (1, 2, 3, 4, 0)]
# run 2
>>> random.sample(permutations, k=4)
[(2, 1, 4, 0, 3), (0, 3, 4, 1, 2), (3, 1, 4, 0, 2), (0, 3, 4, 2, 1)]
# run 3
>>> random.sample(permutations, k=4)
[(3, 4, 1, 0, 2), (3, 0, 1, 2, 4), (0, 4, 1, 2, 3), (3, 4, 2, 0, 1)]
# and so on

I have a list of tuples that have different length so I need to iterate over list

The tuple that length is equal to 6 is correct one while the tuples with shorter length are artefacts that should be joined to give length of 6.
For example:
I have a list of tuples as below:
foo = [(3, 1, 0, 1, 1, 1), (3, 1), (1, 1), (3, 1), (3, 1, 0, 1), (1, 2), (3, 3, 3, 1, 2, 2)]
len(foo[0]) = 6
len(foo[1]) = 2
len(foo[2]) = 2
len(foo[3]) = 2
len(foo[4]) = 4
len(foo[5]) = 2
len(foo[6]) = 6
So it means that I want to have a list with the following output:
foo_1 = [(3, 1, 0, 1, 1, 1), (3, 1, 1, 1, 3, 1), (3, 1, 0, 1, 1, 2), (3, 3, 3, 1, 2, 2)]
where:
foo_1[1] = foo[1] + foo[2] + foo[3],
foo_1[2] = foo[4] + foo[5]
Basically, I need to iterate over list of tuples and compare the length of each with 6. Then if the length of tuple is not equal to six I have to join tuples till their length will be 6.
You can create a function that flatten's the list of tuples, and then use generators and zip to group them into proper number of length.
>>> def flatten(lst):
for tup in lst:
yield from tup
>>> list(zip(*[flatten(foo)]*6))
[(3, 1, 0, 1, 1, 1),
(3, 1, 1, 1, 3, 1),
(3, 1, 0, 1, 1, 2),
(3, 3, 3, 1, 2, 2)]
You can find more about how zip(*[iter(iterable)]*n) works here.
Or you can use the itertools.chain.from_iterable function to accomplish the flattening part:
>>> flat = chain.from_iterable(foo)
>>> list(zip(*[flat]*6))
[(3, 1, 0, 1, 1, 1),
(3, 1, 1, 1, 3, 1),
(3, 1, 0, 1, 1, 2),
(3, 3, 3, 1, 2, 2)]
import itertools
foo=[(3, 1, 0, 1, 1, 1), (3, 1), (1, 1), (3, 1), (3, 1, 0, 1), (1, 2), (3, 3, 3, 1, 2, 2)]
foo1=[i for t in foo for i in t]
list(itertools.zip_longest(*[iter(foo1)]*6))
Output:
[(3, 1, 0, 1, 1, 1),
(3, 1, 1, 1, 3, 1),
(3, 1, 0, 1, 1, 2),
(3, 3, 3, 1, 2, 2)]
Or just iterate over and use slice
[foo1[i:i+6] for i in range(0,len(foo1),6)]
foo1 is list of all elements..and after that we can use slicing or zip_longest from itertools to get the desired result.
itertools.zip_longest
Make an iterator that aggregates elements from each of the iterables.
If the iterables are of uneven length, missing values are filled-in
with fillvalue. Iteration continues until the longest iterable is
exhausted. Roughly equivalent to:
If the total length is not in multiples of 6
foo=[(3, 1, 0, 1, 1, 1), (3, 1), (1, 1), (3, 1), (3, 1, 0, 1), (1, 2), (3, 3, 3, 1, 2, 2),(1,)
I've added an extra (1,)
list(itertools.zip_longest(*[iter(foo1)]*6))
(1, None, None, None, None, None)]
If we need some fill value instead of None then
list(itertools.zip_longest(*[iter(foo1)]*6,fillvalue=2))
(1, 2, 2, 2, 2, 2)
An easy way to get the results could use more_itertools.chunked
from more_itertools import chunked
from itertools import chain
foo = [(3, 1, 0, 1, 1, 1), (3, 1), (1, 1), (3, 1),
(3, 1, 0, 1), (1, 2), (3, 3, 3, 1, 2, 2)]
for chunk in chunked(chain.from_iterable(foo), 6):
print(chunk)
Prints:
[3, 1, 0, 1, 1, 1]
[3, 1, 1, 1, 3, 1]
[3, 1, 0, 1, 1, 2]
[3, 3, 3, 1, 2, 2]

Find every way to fill a 3 element list given 2 numbers

Given 2 numbers, say 1 and 0, how do I find every possible combination that makes a 3 element list
For example, I want to input 1 and 0 and return:
(1, 1, 1)
...
(0, 0, 0)
Also, (1, 0, 1) and (1, 1, 0) are counted as different and I need to return both of these
itertools.product([0,1],repeat=3)
# [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]
should give you what you want i think
Here you go:
from itertools import product
print([x for x in product([0, 1], repeat=3)])
Output:
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]

Recursively find adjacent coordinates for n-dimensional array

For any tuple of coordinates I am given (ex. (2,2) or (1,2,3) or (4,5,6,7,8), etc.), how can I get a list of all the adjacent coordinates.
So that,
find_adjacents((2,2))
>>[[1,1],[1,2],[1,3],[2,1],[2,3],[3,1],[3,2],[3,3]]
and
find_adjacents((2,2,2,2))
would return something with 3^4-1 elements
You can ignore edge cases for this (imagine indices range from -inf to inf)
You can use itertools.product for this. The Cartesian product of the ranges will include the input itself, so we need to remove it afterwards. list.pop works for this, because the product is generated in order, such that the input itself will always be exactly the middle element.
from itertools import product
def neighbours(t):
ranges = [(x-1, x, x+1) for x in t]
result = list(product(*ranges))
result.pop(len(result) // 2)
return result
Examples (formatted for readability):
>>> neighbours( (1, 2) )
[(0, 1), (0, 2), (0, 3),
(1, 1), (1, 3),
(2, 1), (2, 2), (2, 3)]
>>> neighbours( (1, 1, 1) )
[(0, 0, 0), (0, 0, 1), (0, 0, 2),
(0, 1, 0), (0, 1, 1), (0, 1, 2),
(0, 2, 0), (0, 2, 1), (0, 2, 2),
(1, 0, 0), (1, 0, 1), (1, 0, 2),
(1, 1, 0), (1, 1, 2),
(1, 2, 0), (1, 2, 1), (1, 2, 2),
(2, 0, 0), (2, 0, 1), (2, 0, 2),
(2, 1, 0), (2, 1, 1), (2, 1, 2),
(2, 2, 0), (2, 2, 1), (2, 2, 2)]
You can use recursion:
def combos(d, c = []):
if not d:
yield c
else:
yield from [i for b in range(d[0]-1, d[0]+2) for i in combos(d[1:], c+[b])]
vals = (2,2)
print(list(combos(vals)))
print(list(combos((1, 1, 1))))
Output:
[[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]]
[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 2, 0], [0, 2, 1], [0, 2, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 2, 0], [1, 2, 1], [1, 2, 2], [2, 0, 0], [2, 0, 1], [2, 0, 2], [2, 1, 0], [2, 1, 1], [2, 1, 2], [2, 2, 0], [2, 2, 1], [2, 2, 2]]

Count occurences of elements of a matrix fast

Let M and n be d x d- and d-dimensonal numpy arrays of integers, respectively. I want to count the number of triples of the form (n(i), n(j), M(i,j)). As a result I want a numpy array such that each entry counts the number of occurences of such a triple.
Edit: M is symmetric and I don't want to count triples with i=j.
I'm currently using itertools.product (for loop over all pairs) and numpy.bincount to do this, but it is too slow. Is there a smarter way doing this, probably using numpy?
Since the arrays contain integers, you can consider each triplet as an linearly index-able element. Here's an approach with that philosophy in mind and thus avoids loops, like so -
# Form n(i) x n(j) array and then append with "columnar" M(i,j) array
nn_arr = n[np.array(list(itertools.product(range(5), repeat=2)))]
nn_M_arr = np.concatenate((nn_arr,M.reshape(-1,1)),axis=1)
# Get linear indices version
dims = nn_M_arr.max(0)+1
lidx = np.ravel_multi_index(nn_M_arr.T,dims)
# Get unique indices and the counts
_, idx, counts = np.unique(lidx,return_index=True,return_counts=True)
# Get corresponding unique triplets using unique indices and zip with counts
out = zip(map(tuple,nn_M_arr[idx]),counts)
Sample run -
In [206]: M
Out[206]:
array([[1, 0, 0, 2, 0],
[1, 1, 2, 0, 2],
[0, 0, 2, 0, 1],
[2, 1, 2, 0, 2],
[1, 1, 1, 1, 0]])
In [207]: n
Out[207]: array([0, 1, 1, 1, 2])
In [208]: out
Out[208]:
[((0, 0, 1), 1),
((0, 1, 0), 2),
((0, 1, 2), 1),
((0, 2, 0), 1),
((1, 0, 0), 1),
((1, 0, 1), 1),
((1, 0, 2), 1),
((1, 1, 0), 4),
((1, 1, 1), 2),
((1, 1, 2), 3),
((1, 2, 1), 1),
((1, 2, 2), 2),
((2, 0, 1), 1),
((2, 1, 1), 3),
((2, 2, 0), 1)]
Let :
M=np.random.randint(0,3,(10,10))
n=np.random.randint(0,3,10)
Making triples and drop i=j :
x,y=np.meshgrid(n,n)
a=np.dstack((x,y,M)).reshape(-1,3)
au=a[a [:,0]!=a[:,1]] # i<>j
The problem with unique is that it use only 1D array. a solution is to convert rows in strings : this ensure lazy comparisons and is generally fast.
c=np.frombuffer(au,dtype='S12') # 12 is 3*n.itemsize
_,indices,counts=np.unique(c,return_index=True,return_counts=True)
result=np.vstack((counts,au[indices].T)) # count first.
##
array([[1, 2, 5, 3, 4, 1, 4, 4, 3, 4, 9, 1, 3, 4, 9, 3, 4],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2],
[1, 1, 1, 2, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 1, 1, 1],
[0, 1, 2, 0, 1, 2, 0, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]], dtype=int64)
If integers are small like here (<4), you can present the results so that res[n(i),n(j),M(i,j)]give the count :
res=np.zeros((3,3,3),int)
res[list(zip(*au[indices]))]=counts

Categories