Combinations from range of values for given sizes

2020-03-25 15:06发布

问题:

I'm using the following code to create a list of indices for an array. However, I would like the index to run in Fortran order i.e. the inner loop being the faster varying loop. Is there a way to achieve this in python. At the moment, the output I get is in C order.

np.transpose(np.nonzero(np.ones([32,30])))

Output:

array([[ 0,  0],
       [ 0,  1],
       [ 0,  2],
       ..., 
       [31, 27],
       [31, 28],
       [31, 29]])

However, I need the ouptut in the form:

array([[ 0,  0],
       [ 1,  0],
       [ 2,  0],
       ..., 
       [29, 29],
       [30, 29],
       [31, 29]])

回答1:

A. Two param solution (two column output)

You could generate those indices with np.indices and then a transpose and a reshape does the job -

np.indices((32,30)).T.reshape(-1,2)

Sample output -

In [36]: np.indices((32,30)).T.reshape(-1,2)
Out[36]: 
array([[ 0,  0],
       [ 1,  0],
       [ 2,  0],
       ..., 
       [29, 29],
       [30, 29],
       [31, 29]])

Runtime test -

In [74]: points = [32,30]

# @218's soln
In [75]: %timeit np.transpose(np.nonzero(np.ones(points[::-1])))[:,::-1]
100000 loops, best of 3: 18.6 µs per loop

In [76]: %timeit np.indices((points)).T.reshape(-1,2)
100000 loops, best of 3: 16.1 µs per loop

In [77]: points = [320,300]

# @218's soln
In [78]: %timeit np.transpose(np.nonzero(np.ones(points[::-1])))[:,::-1]
100 loops, best of 3: 2.14 ms per loop

In [79]: %timeit np.indices((points)).T.reshape(-1,2)
1000 loops, best of 3: 1.26 ms per loop

Further performance boost

We can optimize it further by using flipped points with np.indices and then using np.column_stack to create the final 2 columns array. Let's time and verify it against the already proposed one. Listing those two approaches below -

def app1(points):
    return np.indices((points)).T.reshape(-1,2)

def app2(points):
    R,C = np.indices((points[::-1]))
    return np.column_stack((C.ravel(), R.ravel()))

Timings -

In [146]: points = [32,30]

In [147]: np.allclose(app1(points), app2(points))
Out[147]: True

In [148]: %timeit app1(points)
100000 loops, best of 3: 14.8 µs per loop

In [149]: %timeit app2(points)
100000 loops, best of 3: 17.4 µs per loop

In [150]: points = [320,300]

In [151]: %timeit app1(points)
1000 loops, best of 3: 1.1 ms per loop

In [152]: %timeit app2(points)
1000 loops, best of 3: 822 µs per loop

So, this one's better on bigger shapes.


B. Generic solution (Generic column output)

We will make it generic so that we could work with as many params as given, like so -

def get_combinations(params, order='right'):
    # params : tuple of input scalars that denotes sizes
    # The order arg is used for the LSB position. So, with order='right', the
    # rightmost column is the least significant, hence it will change the most
    # when going through the rows. For order='left', the leftmost column
    # would change the most.
    all_indices = np.indices(params)
    if order=='right':
        return np.moveaxis(all_indices,0,-1).reshape(-1,len(params))
    elif order=='left':
        return all_indices.T.reshape(-1,len(params))
    else:
        raise Exception('Wrong side value!')

Sample case runs -

In [189]: get_combinations((2,3), order='left')
Out[189]: 
array([[0, 0],
       [1, 0],
       [0, 1],
       [1, 1],
       [0, 2],
       [1, 2]])

In [191]: get_combinations((2,3,2), order='right')
Out[191]: 
array([[0, 0, 0],
       [0, 0, 1],
       [0, 1, 0],
       [0, 1, 1],
       [0, 2, 0],
       [0, 2, 1],
       [1, 0, 0],
       [1, 0, 1],
       [1, 1, 0],
       [1, 1, 1],
       [1, 2, 0],
       [1, 2, 1]])


回答2:

A general solution (that would work for higher dimensional indices e.g. 3D, 4D) etc. seems to be as suggested in the comments by Nils Werner:

points = [32,30]
np.transpose(np.nonzero(np.ones(points[::-1])))[:,::-1]

Output:

array([[ 0,  0],
       [ 1,  0],
       [ 2,  0],
       ..., 
       [29, 29],
       [30, 29],
       [31, 29]])


回答3:

Is this what you are after?

a = np.transpose(np.nonzero(np.ones([32,30])))  
a.reshape(32,30,2).transpose(1,0,2).reshape(-1,2)
Out[2197]: 
array([[ 0,  0],
       [ 1,  0],
       [ 2,  0],
       ..., 
       [29, 29],
       [30, 29],
       [31, 29]], dtype=int64)