Ось підхід O (max (x) + len (x)), використовуючи scipy.sparse
:
import numpy as np
from scipy import sparse
x = np.array("1 2 2 0 0 1 3 5".split(),int)
x
# array([1, 2, 2, 0, 0, 1, 3, 5])
M,N = x.max()+1,x.size
sparse.csc_matrix((x,x,np.arange(N+1)),(M,N)).tolil().rows.tolist()
# [[3, 4], [0, 5], [1, 2], [6], [], [7]]
Це працює, створюючи розріджену матрицю із записами у позиціях (x [0], 0), (x [1], 1), ... Використовуючи CSC
формат (стислий розріджений стовпець), це досить просто. Потім матриця перетворюється у LIL
формат (пов'язаний список). Цей формат зберігає індекси стовпців для кожного рядка як список у своєму rows
атрибуті, тому все, що нам потрібно зробити, це взяти це та перетворити його у список.
Зауважте, що argsort
рішення на основі малих масивів , ймовірно, швидше, але у деяких не шалено великих розмірів це перетнеться.
Редагувати:
argsort
засноване numpy
-тільки рішення:
np.split(x.argsort(kind="stable"),np.bincount(x)[:-1].cumsum())
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]
Якщо порядок індексів у групах не має значення, ви можете також спробувати argpartition
(у цьому невеликому прикладі це не має значення, але це взагалі не гарантується):
bb = np.bincount(x)[:-1].cumsum()
np.split(x.argpartition(bb),bb)
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]
Редагувати:
@Divakar рекомендує не використовувати np.split
. Натомість цикл, ймовірно, швидший:
A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]
Або ви можете використовувати абсолютно новий (Python3.8 +) оператор моржів:
A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]
РЕДАКТУВАННЯ (редаговано)
(Не чистий numpy): як альтернатива numba (див. Пост @ senderle), ми також можемо використовувати pythran.
Компілювати з pythran -O3 <filename.py>
import numpy as np
#pythran export sort_to_bins(int[:],int)
def sort_to_bins(idx, mx):
if mx==-1:
mx = idx.max() + 1
cnts = np.zeros(mx + 2, int)
for i in range(idx.size):
cnts[idx[i] + 2] += 1
for i in range(3, cnts.size):
cnts[i] += cnts[i-1]
res = np.empty_like(idx)
for i in range(idx.size):
res[cnts[idx[i]+1]] = i
cnts[idx[i]+1] += 1
return [res[cnts[i]:cnts[i+1]] for i in range(mx)]
Тут numba
виграє виграш на основі виступу:
repeat(lambda:enum_bins_numba_buffer(x),number=10)
# [0.6235917090671137, 0.6071486569708213, 0.6096088469494134]
repeat(lambda:sort_to_bins(x,-1),number=10)
# [0.6235359431011602, 0.6264424560358748, 0.6217901279451326]
Старіші речі:
import numpy as np
#pythran export bincollect(int[:])
def bincollect(a):
o = [[] for _ in range(a.max()+1)]
for i,j in enumerate(a):
o[j].append(i)
return o
Таймінги проти numba (стара)
timeit(lambda:bincollect(x),number=10)
# 3.5732191529823467
timeit(lambda:enumerate_bins(x),number=10)
# 6.7462647299980745
np.argsort([1, 2, 2, 0, 0, 1, 3, 5])
даєarray([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)
. то можна просто порівняти наступні елементи.