Voici une approche O (max (x) + len (x)) utilisant 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]]
Cela fonctionne en créant une matrice clairsemée avec des entrées aux positions (x [0], 0), (x [1], 1), ... CSC
format (colonne creuse compressée) c'est assez simple. La matrice est ensuite convertie au LIL
format (liste liée). Ce format stocke les indices de colonne pour chaque ligne sous forme de liste dans son rows
attribut, donc tout ce que nous devons faire est de prendre cela et de le convertir en liste.
Notez que pour les petites baies, les argsort
solutions basées sont probablement plus rapides, mais à une taille pas incroyablement grande, cela se croisera.
ÉDITER:
argsort
basée sur une numpy
seule solution:
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])]
Si l'ordre des indices au sein des groupes n'a pas d'importance, vous pouvez également essayer argpartition
(cela ne fait aucune différence dans ce petit exemple mais ce n'est pas garanti en général):
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])]
ÉDITER:
@Divakar déconseille l'utilisation de np.split
. Au lieu de cela, une boucle est probablement plus rapide:
A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]
Ou vous pouvez utiliser le tout nouvel opérateur de morse (Python3.8 +):
A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]
MODIFIER (MODIFIÉ):
(Pas pur numpy): Comme alternative à numba (voir le post de @ senderle), nous pouvons également utiliser pythran.
Compiler avec 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)]
Ici numba
gagne par une moustache en termes de performances:
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]
Trucs plus anciens:
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
Timings vs numba (ancien)
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])
donnearray([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)
. alors vous pouvez simplement comparer les éléments suivants.