Python: moyen le plus rapide de créer une liste de n listes


97

Je me demandais donc comment créer au mieux une liste de listes vierges:

[[],[],[]...]

En raison de la façon dont Python fonctionne avec les listes en mémoire, cela ne fonctionne pas:

[[]]*n

Cela crée [[],[],...]mais chaque élément est la même liste:

d = [[]]*n
d[0].append(1)
#[[1],[1],...]

Quelque chose comme une compréhension de liste fonctionne:

d = [[] for x in xrange(0,n)]

Mais cela utilise la machine virtuelle Python pour la boucle. Existe-t-il un moyen d'utiliser une boucle implicite (en tirant parti du fait qu'elle est écrite en C)?

d = []
map(lambda n: d.append([]),xrange(0,10))

C'est en fait plus lent. :(


1
Je serais surpris s'il y avait quelque chose de beaucoup plus rapide que d = [[] for x in xrange(0,n)]. Vous devez soit boucler explicitement en Python, soit appeler une fonction Python / lambda à plusieurs reprises (ce qui devrait être plus lent). Mais en espérant toujours que quelqu'un publiera quelque chose qui montre que je me trompe :).
MAK

1
Lorsque vous les avez mesurés timeit, qu'avez-vous appris?
S.Lott

Je viens de confirmer que map(lambda x: [], xrange(n))c'est plus lent qu'une compréhension de liste.
Andrew Clark

Réponses:


105

Le seul moyen probablement qui est légèrement plus rapide que

d = [[] for x in xrange(n)]

est

from itertools import repeat
d = [[] for i in repeat(None, n)]

Il n'est pas nécessaire de créer un nouvel intobjet à chaque itération et est environ 15% plus rapide sur ma machine.

Edit : En utilisant NumPy, vous pouvez éviter la boucle Python en utilisant

d = numpy.empty((n, 0)).tolist()

mais c'est en fait 2,5 fois plus lent que la compréhension de la liste.


Et pourquoi pas map(lambda x:[], repeat(None,n))?
PaulMcG

3
@Paul: Ce serait à nouveau beaucoup plus lent en raison de la surcharge d'appel de fonction de l'expression lambda.
Sven Marnach

Cela ne devrait-il pas être mis à jour maintenant que la plage est différente dans Python 3?
beruic

@beruic Je cite simplement le code de la question dans le premier bloc de code, donc cela n'a pas vraiment de sens de changer cela.
Sven Marnach

12

Les compréhensions de liste sont en fait implémentées plus efficacement que le bouclage explicite (voir la dissortie pour les fonctions d'exemple ) et la mapméthode doit invoquer un objet appelable ophaque à chaque itération, ce qui entraîne une surcharge considérable.

Quoi qu'il en soit, [[] for _dummy in xrange(n)]c'est la bonne façon de le faire et aucune des minuscules différences de vitesse (voire existantes) entre les différentes autres méthodes ne devrait avoir d' importance. À moins bien sûr que vous ne passiez la plupart de votre temps à faire cela - mais dans ce cas, vous devriez plutôt travailler sur vos algorithmes. À quelle fréquence créez-vous ces listes?


5
S'il vous plaît, non _comme nom de variable! Sinon belle réponse :)
Sven Marnach

11
@Sven: Pourquoi pas? Il est couramment utilisé pour les variables inutilisées (s'il était appelé i, je chercherais pour ma part où il est utilisé). Le seul inconvénient serait que cela masque la _tenue du dernier résultat dans la REPL ... et ce n'est le cas que dans 2.x où les compréhensions de liste fuient.

Pas très souvent, c'est pourquoi je suis allé de l'avant et j'ai utilisé une compréhension de liste. J'ai pensé que ce serait intéressant de voir ce que les gens avaient à dire. J'ai vu Dropbox parler à PyCon de l'utilisation de itertools.imap au lieu d'une boucle for pour mettre à jour un hachage md5 un nombre absurdement élevé de fois, et je suis un peu obsédé par les boucles C depuis lors.
munchybunch

12
La raison la plus importante pour ne pas l'utiliser est qu'elle a tendance à semer la confusion chez les gens, leur faisant penser qu'il s'agit d'une sorte de syntaxe spéciale. Et en plus du conflit avec _dans l'interpréteur interactif, il entre également en conflit avec l'alias commun gettext. Si vous voulez préciser que la variable est une variable factice, appelez-la dummy, non _.
Sven Marnach

12

Voici deux méthodes, l'une douce et simple (et conceptuelle), l'autre plus formelle et pouvant être étendue dans une variété de situations, après avoir lu un jeu de données.

Méthode 1: conceptuelle

X2=[]
X1=[1,2,3]
X2.append(X1)
X3=[4,5,6]
X2.append(X3)
X2 thus has [[1,2,3],[4,5,6]] ie a list of lists. 

Méthode 2: formelle et extensible

Une autre façon élégante de stocker une liste sous forme de liste de listes de différents numéros - qu'elle lit à partir d'un fichier. (Le fichier ici a le train de jeu de données) Train est un jeu de données avec disons 50 lignes et 20 colonnes. c'est à dire. Le train [0] me donne la première ligne d'un fichier csv, le train [1] me donne la deuxième ligne et ainsi de suite. Je suis intéressé par la séparation de l'ensemble de données avec 50 lignes en une seule liste, à l'exception de la colonne 0, qui est ma variable expliquée ici, doit donc être supprimée de l'ensemble de données du train d'origine, puis à l'échelle de la liste après la liste - c'est-à-dire une liste d'une liste . Voici le code qui fait cela.

Notez que je lis "1" dans la boucle interne car je ne m'intéresse qu'aux variables explicatives. Et je réinitialise X1 = [] dans l'autre boucle, sinon le X2.append ([0: (len (train [0]) - 1)]) réécrira X1 encore et encore - en plus d'être plus efficace en mémoire.

X2=[]
for j in range(0,len(train)):
    X1=[]
    for k in range(1,len(train[0])):
        txt2=train[j][k]
        X1.append(txt2)
    X2.append(X1[0:(len(train[0])-1)])

4

Pour créer une liste et une liste de listes, utilisez la syntaxe ci-dessous

     x = [[] for i in range(10)]

cela créera une liste 1-d et pour l'initialiser, mettre le nombre dans [[nombre] et définir la longueur de la liste mettre la longueur dans la plage (longueur)

  • Pour créer une liste de listes, utilisez la syntaxe ci-dessous.
    x = [[[0] for i in range(3)] for i in range(10)]

cela initialisera la liste des listes avec une dimension 10 * 3 et avec la valeur 0

  • Pour accéder / manipuler l'élément
    x[1][5]=value

2

J'ai donc fait des comparaisons de vitesse pour obtenir le moyen le plus rapide. La compréhension des listes est en effet très rapide. La seule façon de se rapprocher est d'éviter que le bytecode ne soit excusé lors de la construction de la liste. Ma première tentative a été la méthode suivante, qui semble en principe plus rapide:

l = [[]]
for _ in range(n): l.extend(map(list,l))

(produit une liste de longueur 2 ** n, bien sûr) Cette construction est deux fois plus lente que la compréhension de la liste, selon le temps, pour les listes courtes et longues (un million).

Ma deuxième tentative a été d'utiliser starmap pour appeler le constructeur de liste pour moi.Il y a une construction, qui semble exécuter le constructeur de liste à la vitesse maximale, mais qui est toujours plus lente, mais seulement d'une infime quantité:

from itertools import starmap
l = list(starmap(list,[()]*(1<<n)))

Assez intéressant, le temps d'exécution suggère que c'est l'appel final de la liste qui ralentit la solution starmap, car son temps d'exécution est presque exactement égal à la vitesse de:

l = list([] for _ in range(1<<n))

Ma troisième tentative est survenue lorsque j'ai réalisé que list (()) produisait également une liste, alors j'ai essayé la simplicité apparemment:

l = list(map(list, [()]*(1<<n)))

mais c'était plus lent que l'appel starmap.

Conclusion: pour les maniaques de vitesse: utilisez la compréhension de liste. N'appelez des fonctions que si vous le devez. Utilisez des fonctions intégrées.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.