Quite similar to Bitwise's answer :
def fn(a):
return lambda b: np.all(a==b, axis=1)
matches = np.apply_along_axis(fn(M), 1, L[:,:2])
result = L[np.any(matches, axis=1)]
What happens under the hood is something like this (I'll use Bitwise's example, which is easier to demonstrate) :
>>> M
array([[ 0, 1],
[ 2, 3],
[ 4, 5],
[ 6, 7],
[ 8, 9],
[10, 11]])
>>> M.shape+=(1,)
>>> M
array([[[ 0],
[ 1]],
[[ 2],
[ 3]],
[[ 4],
[ 5]],
[[ 6],
[ 7]],
[[ 8],
[ 9]],
[[10],
[11]]])
Here we have added another dimension to the M array, which is now (6,2,1).
>>> L2 = L[:,:-1].T
Then we get rid of the last column of 2, and transpose the array, so that the dimension is (2,4)
And here is the magic, M and L2 are now broadcastable to arrays of dimension (6,2,4).
As numpy's doc states :
A set of arrays is called “broadcastable” to the same shape if the
above rules produce a valid result, i.e., one of the following is
true:
The arrays all have exactly the same shape.
The arrays all have the same number of dimensions and the length of each dimensions is either a common length or 1.
The arrays that have too few dimensions can have their shapes prepended with a dimension of length 1 to satisfy property 2.
Example
If a.shape is (5,1), b.shape is (1,6), c.shape is (6,) and d.shape is
() so that d is a scalar, then a, b, c, and d are all broadcastable to
dimension (5,6); and
a acts like a (5,6) array where a[:,0] is broadcast to the other columns,
b acts like a (5,6) array where b[0,:] is broadcast to the other rows,
c acts like a (1,6) array and therefore like a (5,6) array where c[:] is broadcast to every row, and finally,
d acts like a (5,6) array where the single value is repeated.
M[:,:,0] will be repeated 4 times to fill the 3 dim, and L2 will be prepended a new dimension and be repeated 6 times to fill it.
>>> B = np.broadcast_arrays(L2,M)
>>> B
[array([[[ 0, 3, 6, 9],
[ 1, 4, 7, 10]],
[[ 0, 3, 6, 9],
[ 1, 4, 7, 10]],
[[ 0, 3, 6, 9],
[ 1, 4, 7, 10]],
[[ 0, 3, 6, 9],
[ 1, 4, 7, 10]],
[[ 0, 3, 6, 9],
[ 1, 4, 7, 10]],
[[ 0, 3, 6, 9],
[ 1, 4, 7, 10]]]),
array([[[ 0, 0, 0, 0],
[ 1, 1, 1, 1]],
[[ 2, 2, 2, 2],
[ 3, 3, 3, 3]],
[[ 4, 4, 4, 4],
[ 5, 5, 5, 5]],
[[ 6, 6, 6, 6],
[ 7, 7, 7, 7]],
[[ 8, 8, 8, 8],
[ 9, 9, 9, 9]],
[[10, 10, 10, 10],
[11, 11, 11, 11]]])]
We can now compare them element-wise :
>>> np.equal(*B)
array([[[ True, False, False, False],
[ True, False, False, False]],
[[False, False, False, False],
[False, False, False, False]],
[[False, False, False, False],
[False, False, False, False]],
[[False, False, True, False],
[False, False, True, False]],
[[False, False, False, False],
[False, False, False, False]],
[[False, False, False, False],
[False, False, False, False]]], dtype=bool)
Row to row (axis = 1):
>>> np.all(np.equal(*B), axis=1)
array([[ True, False, False, False],
[False, False, False, False],
[False, False, False, False],
[False, False, True, False],
[False, False, False, False],
[False, False, False, False]], dtype=bool)
Aggregate on L's :
>>> C = np.any(np.all(np.equal(*B), axis=1), axis=0)
>>> C
array([ True, False, True, False], dtype=bool)
And this gives you the boolean mask to apply to L.
>>> L[C]
array([[0, 1, 2],
[6, 7, 8]])
apply_along_axis will leverage the same feature, but reducing L's dimension instead of increasing M's (thus adding implicit loops).
Lis an added dimension based on some extra computationsortandin1dcalls. The idea is vague right now, though.