Quelle est la complexité temporelle du découpage d'une chaîne Python? Étant donné que Python sont immuables, je peux imaginer qu'elles soient soit O(1)
ou O(n)
selon la façon dont le découpage est mis en œuvre.
J'ai besoin d'écrire une fonction qui itère sur tous les suffixes d'une chaîne (potentiellement grosse). Je pourrais éviter de trancher la chaîne en représentant un suffixe comme un tuple de la chaîne entière plus un index pour commencer à lire les caractères, mais c'est moche. Si à la place j'écris naïvement ma fonction comme ceci:
def do_something_on_all_suffixes(big_string):
for i in range(len(big_string)):
suffix = big_string[i:]
some_constant_time_operation(suffix)
... sa complexité temporelle sera-t-elle O(n)
ou O(n2)
, où n
est len(big_string)
?
Réponse courte: str
tranches, en général, copie. Cela signifie que votre fonction qui effectue une tranche pour chacun des suffixes n
de votre chaîne effectue O(n2)
travail. Cela dit, vous pouvez éviter les copies si vous pouvez travailler avec des objets de type bytes
en utilisant memoryview
s pour obtenir des vues sans copie des données d'octets d'origine . Voir comment vous pouvez effectuer un découpage sans copie ci-dessous pour savoir comment le faire fonctionner.
Réponse longue: (C) Python str
ne tranche pas en référençant une vue d'un sous-ensemble des données. Il existe exactement trois modes de fonctionnement pour le découpage str
:
mystr[:]
: Renvoie une référence à la même str
(pas seulement les données partagées, le même objet réel, mystr is mystr[:]
Puisque str
est immuable donc il n'y a pas risque de le faire)mystr[1:1] is mystr[2:2] is ''
), et les chaînes ordinales basses de longueur un sont également des singletons mis en cache (sur CPython 3.5.0, il ressemble à tous les caractères représentables en latin-1, c'est-à-dire les ordinaux Unicode dans range(256)
, sont mis en cache)str
tranché est copié au moment de la création, puis sans rapport avec le str
d'origineLa raison pour laquelle # 3 est la règle générale est d'éviter les problèmes avec les gros str
étant gardés en mémoire par une vue d'une petite partie de celui-ci. Si vous aviez un fichier de 1 Go, lisez-le et découpez-le comme ça (oui, c'est du gaspillage quand vous pouvez le chercher, c'est à titre d'illustration):
with open(myfile) as f:
data = f.read()[-1024:]
alors vous auriez 1 Go de données en mémoire pour prendre en charge une vue qui montre le 1 Ko final, un sérieux gaspillage. Comme les tranches sont généralement de petite taille, il est presque toujours plus rapide de copier sur la tranche au lieu de créer des vues. Cela signifie également que str
peut être plus simple; il a besoin de connaître sa taille, mais il n'a pas besoin de suivre également un décalage dans les données.
Il y a façons d'effectuer un découpage basé sur la vue en Python, et dans Python 2, cela fonctionnera sur str
(car str
ressemble à des octets dans Python 2, prenant en charge le protocole tampon ). Avec Py2 str
et Py3 bytes
(ainsi que de nombreux autres types de données comme bytearray
, array.array
, numpy
tableaux, mmap.mmap
S, etc.), vous pouvez créer un memoryview
qui est une vue à copie nulle de l'objet d'origine , et peut être découpé sans copier de données. Donc, si vous pouvez utiliser (ou encoder) vers Py2 str
/Py3 bytes
, et votre fonction peut fonctionner avec des objets arbitraires semblables à bytes
, alors vous pourriez faire:
def do_something_on_all_suffixes(big_string):
# In Py3, may need to encode as latin-1 or the like
remaining_suffix = memoryview(big_string)
# Rather than explicit loop, just replace view with one shorter view
# on each loop
while remaining_suffix: # Stop when we've sliced to empty view
some_constant_time_operation(remaining_suffix)
remaining_suffix = remaining_suffix[1:]
Les tranches de memoryview
s créent de nouveaux objets d'affichage (ils sont juste ultra-légers avec une taille fixe sans rapport avec la quantité de données qu'ils consultent), tout simplement pas de données, donc some_constant_time_operation
Peut stocker un copier si nécessaire et il ne sera pas modifié lorsque nous le couperons plus tard. Si vous avez besoin d'une copie appropriée en tant que Py2 str
/Py3 bytes
, vous pouvez appeler .tobytes()
pour obtenir le brut bytes
obj, ou (dans Py3 uniquement il apparaît), le décoder directement dans un str
qui copie à partir du tampon, par exemple str(remaining_suffix[10:20], 'latin-1')
.
Tout dépend de la taille de vos tranches. J'ai jeté ensemble les deux repères suivants. La première tranche la chaîne entière et la seconde seulement un peu. Ajustement de courbe avec cet outil donne
# s[1:-1]
y = 0.09 x^2 + 10.66 x - 3.25
# s[1:1000]
y = -0.15 x + 17.13706461
Le premier semble assez linéaire pour des tranches de chaînes jusqu'à 4 Mo. Je suppose que cela mesure vraiment le temps nécessaire pour construire une deuxième chaîne. Le second est assez constant, bien qu'il soit si rapide qu'il n'est probablement pas aussi stable.
import time
def go(n):
start = time.time()
s = "abcd" * n
for j in xrange(50000):
#benchmark one
a = s[1:-1]
#benchmark two
a = s[1:1000]
end = time.time()
return (end - start) * 1000
for n in range(1000, 100000, 5000):
print n/1000.0, go(n)