Ecco un approccio O (max (x) + len (x)) usando 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]]
Funziona creando una matrice sparsa con voci nelle posizioni (x [0], 0), (x [1], 1), ... Usando il formato CSC
(colonna sparsa compressa) questo è piuttosto semplice. La matrice viene quindi convertita nel formato LIL
(elenco collegato). Questo formato memorizza gli indici di colonna per ogni riga come elenco nel suo rows
attributo, quindi tutto ciò che dobbiamo fare è prenderlo e convertirlo in elenco.
Si noti che per le matrici di piccole dimensioni le argsort
soluzioni sono probabilmente più veloci, ma in alcuni casi di dimensioni non follemente grandi, questo andrà oltre.
MODIFICARE:
argsort
basata su numpy
soluzione -Solo:
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])]
Se l'ordine degli indici all'interno dei gruppi non ha importanza, puoi anche provare argpartition
(succede che non fa alcuna differenza in questo piccolo esempio, ma questo non è garantito in generale):
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])]
MODIFICARE:
@Divakar sconsiglia l'uso di np.split
. Invece, un ciclo è probabilmente più veloce:
A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]
Oppure potresti usare il nuovissimo operatore tricheco (Python3.8 +):
A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]
EDIT (Montaggio):
(Non puro intorpidimento): in alternativa a numba (vedi il post di @ senderle) possiamo anche usare pythran.
Compila con 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)]
Qui numba
vince da un baffo dal punto di vista delle prestazioni:
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]
Roba precedente:
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
Tempi vs. numba (vecchio)
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])
dàarray([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)
. allora puoi semplicemente confrontare i prossimi elementi.