İşte O (max (x) + len (x)) yaklaşımını kullanarak 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]]
Bu, (x [0], 0), (x [1], 1), ... konumlarındaki girişleri içeren seyrek bir matris oluşturarak çalışır. CSC
(Sıkıştırılmış seyrek sütun) formatını kullanmak oldukça basittir. Matris daha sonra LIL
(bağlantılı liste) formatına dönüştürülür. Bu biçim, her satırın sütun indekslerini rows
özniteliğinde bir liste olarak saklar , bu yüzden tek yapmamız gereken bunu almak ve listeye dönüştürmektir.
Küçük dizilere argsort
dayalı çözümlerin muhtemelen daha hızlı olduğunu, ancak bazı büyük olmayan boyutlarda bunun geçeceğini unutmayın.
DÜZENLE:
argsort
-bazlı numpy
-sadece çözüm:
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])]
Gruplardaki endekslerin sırası önemli değilse, deneyebilirsiniz argpartition
(bu küçük örnekte fark yaratmaz, ancak bu genel olarak garanti edilmez):
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])]
DÜZENLE:
@Divakar kullanılmamasını önerir np.split
. Bunun yerine, bir döngü muhtemelen daha hızlıdır:
A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]
Veya yeni (Python3.8 +) mors operatörünü kullanabilirsiniz:
A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]
DÜZENLEME (düzenlendi):
(Saf numpy değil): Numba'ya bir alternatif olarak (bkz. @ Senderle'ın gönderisine) pythran da kullanabiliriz.
Şununla derleyin: 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)]
İşte numba
performans açısından bir bıyık ile kazanır:
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]
Daha eski şeyler:
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
Zamanlamalar - numba (eski)
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])
verirarray([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)
. yalnızca sonraki öğeleri karşılaştırabilirsiniz.