For entertainment's sake, here's a pure-numpy way of doing it:
>>> n = 2
>>> (np.arange(2**n) // ((1 << np.arange(n)[::-1,None]))) & 1
array([[0, 0, 1, 1],
[0, 1, 0, 1]])
>>> n = 4
>>> (np.arange(2**n) // ((1 << np.arange(n)[::-1,None]))) & 1
array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1],
[0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]])
Some explanation (note that it's very unlikely I'd ever write anything like the above in production code):
First, we get the numbers we need the bits of:
>>> np.arange(2**n)
array([0, 1, 2, 3])
Then the exponents of the powers of 2 we care about:
>>> np.arange(n)
array([0, 1])
Bitshift 1 by these to get the powers of two:
>>> 1 << np.arange(n)
array([1, 2])
Swap the order for aesthetic purposes:
>>> (1 << np.arange(n))[::-1]
array([2, 1])
Use None
to introduce an extra axis, so we can broadcast:
>>> (1 << np.arange(n))[::-1, None]
array([[2],
[1]])
Divide:
>>> np.arange(2**n) // (1 << np.arange(n))[::-1, None]
array([[0, 0, 1, 1],
[0, 1, 2, 3]])
Take only the first bit:
>>> (np.arange(2**n) // (1 << np.arange(n))[::-1, None]) & 1
array([[0, 0, 1, 1],
[0, 1, 0, 1]])