J'ai un très long tableau, et j'essaye de faire ce qui suit de la manière la plus efficace possible:
Pour chaque morceau incrémenté consécutivement dans la liste, je dois inverser son ordre.
Donc, pour le tableau suivant:
array([7,5,1,3,5,2,45,4,12,10,5,1])
Je voudrais obtenir:
a = np.array([1,5,7,3,2,5,4,45,1,5,10,12])
Je me demandais si cela pourrait être vectorisé, en utilisant peut-être numpy ?
J'ai déjà eu des réponses dans cette question précédente, mais les résultats, bien qu'ils soient de grandes améliorations, sont encore un peu lents.
4 Réponses :
Comment est-ce? Cela semble être plus rapide, mais sans savoir à quelle vitesse est assez rapide, il est difficile de le dire avec certitude
all_l = []
sub_l = []
for i in a:
if sub_l:
if sub_l[0] > i:
all_l.extend(sub_l)
sub_l = [i]
else:
sub_l.insert(0, i)
else:
sub_l = [i]
all_l.extend(sub_l)
Idéalement moins d'une seconde pour un tableau de l'ordre de np.random.choice (10,5_000_000)
Pouvez-vous utiliser des pandas?
[7, 5, 1, 3, 5, 2, 45, 4, 12, 10, 5, 1]
Résultat:
import pandas as pd a = [1,5,7,3,2,5,4,45,1,5,10,12] aa = pd.Series(a) aa.groupby(aa.diff().bfill().lt(0).cumsum()).apply(lambda x: x[::-1]).tolist()
Oui, trop lent. 5 millions de rangs.
Appréciez l'effort cependant. Peut-être qu'avec une simple boucle python, c'est aussi bon que possible
Autre option sans dépendances:
chunks = reverse_if_chunk_increases(array) for chunk in chunks: print(chunk) # [7, 5, 1] # [3] # [5, 2] # [45, 4] # [12, 10, 5, 1]
Version modifiée qui est un peu plus rapide:
def reverse_if_chunk_increases(array):
i, x, size, res = 0, 0, len(array), []
while i < size-1:
if array[i] > array[i+1]:
yield array[x:i+1][::-1]
x = i +1
i += 1
yield array[x:size][::-1]
(Peut-être que c'est utile .)
J'ai pu obtenir le résultat si facilement et apparemment un codage un peu plus rapide dans Ruby que:
array.chunk_while { |x, y| x < y }.flat_map{ |chunk| chunk.reverse }
Alors, je me suis demandé pourquoi il n'y avait pas n'est pas un itertool comme chunk_ while . Ensuite, j'ai essayé d'en coder un similaire en utilisant yield:
def reverse_if_chunck_increases(array):
res, memo, last_memo = [], [], None
for e in array:
if not last_memo or e > last_memo:
last_memo = e
memo.append(e)
else:
res.extend(memo[::-1])
last_memo, memo = e, [e]
res.extend(memo[::-1])
return res
print(reverse_if_chunck_increases(array) == [7, 5, 1, 3, 5, 2, 45, 4, 12, 10, 5, 1])
#=> True
L'exécution est ultra-rapide mais elle renvoie un générateur à itérer au lieu d'une liste: p>
array = [1,5,7,3,2,5,4,45,1,5,10,12]
res, memo = [], []
for e in array:
if len(memo) == 0 or e > memo[-1]: memo.append(e)
else:
res.extend(reversed(memo))
memo = [e]
res.extend(reversed(memo))
res # => [7, 5, 1, 3, 5, 2, 45, 4, 12, 10, 5, 1]
Il peut être converti en une liste, ce qui est le processus le plus lent.
Notez qu'un générateur ne peut être appelé qu'une seule fois.
En supprimant [:: - 1] , vous obtenez un résultat similaire à l'énumérateur / générateur Ruby chunk_ while.
@Gerald, j'essayais toujours de trouver un moyen plus rapide, mais le plus rapide que j'obtiens est d'utiliser des générateurs, voir ma modification. Merci pour le choix, au fait.
Je ne pense pas que vous allez être beaucoup plus rapide que d'utiliser une boucle python pure.
Par exemple, voici une solution numpy + itertools:
b = np.random.choice(10, 100000) %%timeit reverse_increasing_sequences_numpy(b) #47.1 ms ± 778 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) %%timeit reverse_increasing_sequences_iGian(b) #40.3 ms ± 1.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) %%%timeit reverse_increasing_sequences_hchw(b) #26.1 ms ± 1.35 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Mais en regardant les résultats du test de vitesse:
import numpy as np
from itertools import chain, groupby
from operator import itemgetter
def reverse_increasing_sequences_numpy(a):
idx = (np.diff(np.concatenate([[a[0]], a]))<0).cumsum()
return list(
chain.from_iterable(
(reversed([x[0] for x in g]) for v, g in groupby(zip(a, idx), itemgetter(1)))
)
)
print(reverse_increasing_sequences(a))
#[7, 5, 1, 3, 5, 2, 45, 4, 12, 10, 5, 1]
solution a> run est presque 2 fois plus rapide que ma version numpy.
Merci beaucoup @pault! Oui, j'avais l'impression qu'une boucle python était peut-être la meilleure solution. Très belle solution quand même et merci encore pour l'effort et la complexité du temps comparisson
Belle solution numpy @pault. Intéressant que numpy ne puisse pas surpasser les boucles python droites.
@ScottBoston Je ne peux pas penser à un moyen d'optimiser cela de quelque manière que ce soit en utilisant numpy. La solution la plus rapide est un passage dans la liste, créant la sortie au fur et à mesure.
@pault Oui, casser la partie de la liste et réassembler prend du temps.