Let say I have a 2D matrix M of some arbitrary size (n, m). Now I want to do some vector operations of all rows in M with all other rows in M efficiently. In my case, I want to compute a bitwise xnor between every row.
What we do know is that there's no need to compute xnor(x, y) and xnor(y, x), it's enough to compute one of them. Also, in my case, there's no need to compute xnor(x, x). I know two solutions to this problem:
def xnor(p, q):
return abs(p-1) * abs(q-1) + (p * q)
def solution_one(M):
return xor(M[:, None], M).all(axis=2)
def solution_two(M):
n = M.shape[0]
results = np.zeros((n, n))
# Upper triangular indices of
ii, jj = np.triu_indices(n, 1)
for i, j in zip(ii,jj):
results[i,j] = xnor(M[i], M[j]).all(axis=0)
return results
Running the two with some arbitrary large matrices we get for solution one
# 100x100
%timeit solution_one(M)
# 3.55 ms ± 73.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# 500x500
%timeit solution_one(M)
# 1.36 s ± 59.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# 1000x1000
%timeit solution_one(M)
# 27.4 s ± 603 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
and for solution two
# 100x100
%timeit solution_two(M)
# 29.5 ms ± 535 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
# 500x500
%timeit solution_two(M)
# 1.06 s ± 41.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# 1000x1000
%timeit solution_two(M)
# 5.58 s ± 95.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
As one see, for smaller instances solution_one is faster than solution_two, but for bigger it is the opposite.
We can see that solution_one is doing n x n calculations whereas solution_two will do (as mentioned by Naphat Amundsen in the comments) ((n x n) - n) / 2. Is there better solutions to this? Can one somehow construct a new matrix of M such that solution one will do closer to ((n x n) - n) / 2 calculations?