Подход № 1
Вот один, основанный на маскировании -
def similar_vectors_masking(a):
n = len(a)
m = n*2+1
ar = np.repeat(a[None],len(a)*2,0)
ar.ravel()[::m] -= 1
ar.ravel()[n::m] += 1
mask = np.ones(len(ar),dtype=bool)
mask[::2] = a!=0
out = ar[mask]
return out
Примеры прогонов -
In [142]: similar_vectors_masking(np.array([0,0,0]))
Out[142]:
array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
In [143]: similar_vectors_masking(np.array([1,0,2,3,0,0,1]))
Out[143]:
array([[0, 0, 2, 3, 0, 0, 1],
[2, 0, 2, 3, 0, 0, 1],
[1, 1, 2, 3, 0, 0, 1],
[1, 0, 1, 3, 0, 0, 1],
[1, 0, 3, 3, 0, 0, 1],
[1, 0, 2, 2, 0, 0, 1],
[1, 0, 2, 4, 0, 0, 1],
[1, 0, 2, 3, 1, 0, 1],
[1, 0, 2, 3, 0, 1, 1],
[1, 0, 2, 3, 0, 0, 0],
[1, 0, 2, 3, 0, 0, 2]])
Подход #2
Для массива, заполненного нулями, нам может быть лучше с репликацией до точного размера вывода, используя np.repeat
и снова использовать немного маскирования для увеличения и уменьшения 1s
, какпоэтому -
def similar_vectors_repeat(a):
mask = a!=0
ra = np.arange(len(a))
r = mask+1
n = r.sum()
ar = np.repeat(a[None],n,axis=0)
add_idx = r.cumsum()-1
ar[add_idx,ra] += 1
ar[(add_idx-1)[mask],ra[mask]] -= 1
return ar
Сравнительный анализ
Временные интервалы со всеми опубликованными подходами в большом массиве -
In [414]: # Setup input array with ~80% zeros
...: np.random.seed(0)
...: a = np.random.randint(1,5,(5000))
...: a[np.random.choice(range(len(a)),int(len(a)*0.8),replace=0)] = 0
In [415]: %timeit similar_vectors(a) # Original soln
...: %timeit similar_vectors_flatnonzero_concat(a) # @yatu's soln
...: %timeit similar_vectors_v2(a) # @Brenlla's soln
...: %timeit similar_vectors_masking(a)
...: %timeit similar_vectors_repeat(a)
1 loop, best of 3: 195 ms per loop
1 loop, best of 3: 234 ms per loop
1 loop, best of 3: 231 ms per loop
1 loop, best of 3: 238 ms per loop
10 loops, best of 3: 82.8 ms per loop