51 votes

Meilleure façon d'échanger des éléments dans une liste?

J'ai un tas de listes qui ressemblent à ceci:

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Je veux échanger des éléments comme suit:

final_l = [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]

La taille de la liste peut varier, mais ils auront toujours contenir un nombre pair d'éléments.

Je suis assez nouveau à Python et je suis actuellement en train de faire comme ceci:

l =  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
final_l = []
for i in range(0, len(l)/2):
    final_l.append(l[2*i+1])
    final_l.append(l[2*i])

Je sais que ce n'est pas vraiment Pythonic et que vous souhaitez utiliser quelque chose de plus efficace. Peut-être une compréhension de liste?

112voto

Anzel Points 12343

Pas besoin de logique compliquée, tout simplement réorganiser la liste de tranchage et de l'étape:

In [1]: l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In [2]: l[::2], l[1::2] = l[1::2], l[::2]

In [3]: l
Out[3]: [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]

TLDR;

Édité avec explication

Je crois que la plupart des téléspectateurs sont déjà familiers avec la liste de tranchage et de l'affectation multiple. Dans le cas où vous ne le faites pas, je vais essayer de mon mieux pour expliquer ce qu'il se passe (j'espère ne pas faire pire).

Pour comprendre la liste de découpage, ici, a déjà une excellente réponse et une explication de la liste tranche de notation. Il suffit de mettre:

a[start:end] # items start through end-1
a[start:]    # items start through the rest of the array
a[:end]      # items from the beginning through end-1
a[:]         # a copy of the whole array

There is also the step value, which can be used with any of the above:

a[start:end:step] # start through not past end, by step

Regardons OP exigences:

 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  # list l
  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
  0  1  2  3  4  5  6  7  8  9    # respective index of the elements
l[0]  l[2]  l[4]  l[6]  l[8]      # first tier : start=0, step=2
   l[1]  l[3]  l[5]  l[7]  l[9]   # second tier: start=1, step=2
-----------------------------------------------------------------------
l[1]  l[3]  l[5]  l[7]  l[9]
   l[0]  l[2]  l[4]  l[6]  l[8]   # desired output

Premier niveau sera: l[::2] = [1, 3, 5, 7, 9] Deuxième niveau sera: l[1::2] = [2, 4, 6, 8, 10]

Comme nous voulons ré-affecter first = second & second = first, nous pouvons utiliser plusieurs attribution et à la mise à jour de la liste d'origine en place:

first , second  = second , first

c'est:

l[::2], l[1::2] = l[1::2], l[::2]

Comme une note de côté, pour obtenir une nouvelle liste, mais n'altérant pas les originaux l, nous pouvons attribuer une nouvelle liste à partir d' l, et d'effectuer ci-dessus, c'est:

n = l[:]  # assign n as a copy of l (without [:], n still points to l)
n[::2], n[1::2] = n[1::2], n[::2]

J'espère que je ne confondez pas tout de vous avec cet ajout d'explication. Si elle ne vous plaît, aidez-mise à jour de la mine et de faire mieux :-)

36voto

NPE Points 169956

Voici une liste de compréhension qui fait l'affaire:

 In [1]: l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In [2]: [l[i^1] for i in range(len(l))]
Out[2]: [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
 

La clé pour le comprendre est la démonstration suivante de la permutation des index de la liste:

 In [3]: [i^1 for i in range(10)]
Out[3]: [1, 0, 3, 2, 5, 4, 7, 6, 9, 8]
 

Le ^ est l' exclusif ou l' opérateur. Tout ce que i^1 fait est de retourner le bit le moins significatif de i , en échangeant effectivement 0 avec 1, 2 avec 3, etc.

20voto

alecxe Points 50783

Vous pouvez utiliser les paires d'itération et de chaînage pour aplatir la liste:

>>> from itertools import chain
>>>
>>> list(chain(*zip(l[1::2], l[0::2])))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]

Ou, vous pouvez utiliser l' itertools.chain.from_iterable() pour le déballage:

>>> list(chain.from_iterable(zip(l[1::2], l[0::2])))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]

11voto

Kasramvd Points 32864

Un benchmark entre les réponses les plus fréquentes:

Python 2.7:

('inp1 ->', 15.302665948867798) # NPE's answer 
('inp2a ->', 10.626379013061523) # alecxe's answer with chain
('inp2b ->', 9.739919185638428) # alecxe's answer with chain.from_iterable
('inp3 ->', 2.6654279232025146) # Anzel's answer

Python 3.4:

inp1 -> 7.913498195000102
inp2a -> 9.680125927000518
inp2b -> 4.728151862000232
inp3 -> 3.1804273489997286

Si vous êtes curieux de connaître les différentes performances entre python 2 et 3, voici les raisons:

Comme vous pouvez le voir @NPE de réponse (inp1) fonctionne très mieux en python3.4, la raison en est que, dans python3.X range() est un objet dynamique et n'a pas de conserver tous les éléments entre qui vont dans la mémoire comme une liste.

Dans beaucoup de façons de l'objet renvoyé par range() se comporte comme si c'est une liste, mais en fait il ne l'est pas. C'est un objet qui renvoie les éléments successifs de la séquence désirée lorsque vous effectuer une itération sur elle, mais il n'a pas vraiment d'en faire la liste, et ainsi de gagner de l'espace.

Et c'est pourquoi en python 3, il ne retourne pas une liste alors que vous tranche de l'objet range.

# python2.7
>>> range(10)[2:5]
[2, 3, 4]
# python 3.X
>>> range(10)[2:5]
range(2, 5)

Le deuxième changement important est la performance d'accrétion de la troisième approche (inp3). Comme vous pouvez le voir la différence entre elle et la dernière solution a diminué de ~2sec (à partir de ~7sec). La raison en est que de l' zip() fonction qui en python3.X il retourne un itérateur qui produit les éléments de la demande. Et depuis l' chain.from_iterable() des besoins d'itérer sur les éléments une fois de plus, il est complètement inutile de le faire avant que de trop (ce qui zip n'en python 2).

Code:

from timeit import timeit


inp1 = """
[l[i^1] for i in range(len(l))]
   """
inp2a = """
list(chain(*zip(l[1::2], l[0::2])))
"""
inp2b = """
list(chain.from_iterable(zip(l[1::2], l[0::2])))
"""
inp3 = """
l[::2], l[1::2] = l[1::2], l[::2]
"""

lst = list(range(100000))
print('inp1 ->', timeit(stmt=inp1,
                        number=1000,
                        setup="l={}".format(lst)))
print('inp2a ->', timeit(stmt=inp2a,
                        number=1000,
                        setup="l={}; from itertools import chain".format(lst)))
print('inp2b ->', timeit(stmt=inp2b,
                        number=1000,
                        setup="l={}; from itertools import chain".format(lst)))
print('inp3 ->', timeit(stmt=inp3,
                        number=1000,
                        setup="l={}".format(lst)))

3voto

Chris_Rands Points 15161

Vous pouvez également créer des listes imbriquées avec des paires inversant leur ordre, puis les aplatir avec itertools.chain.from_iterable

 >>> from itertools import chain
>>> l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(chain.from_iterable([[l[i+1],l[i]] for i in range(0,(len(l)-1),2)]))
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
 

EDIT: Je viens d’appliquer le test de référence de Kasramvd à ma solution et j’ai trouvé que cette solution était plus lente que les autres réponses principales, donc je ne la recommanderais pas pour les grandes listes. Je trouve toujours cela assez lisible si les performances ne sont pas critiques.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X