5
votes

Ordre inverse des chiffres séquentiels

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.


0 commentaires

4 Réponses :


1
votes

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)


1 commentaires

Idéalement moins d'une seconde pour un tableau de l'ordre de np.random.choice (10,5_000_000)



2
votes

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()


2 commentaires

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



2
votes

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]


MODIFIER après que la réponse a été acceptée

(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.


1 commentaires

@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.



1
votes

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.


4 commentaires

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.