Лучший способ поменять элементы в списке? - программирование
Подтвердить что ты не робот

Лучший способ поменять элементы в списке?

У меня есть список списков, которые выглядят следующим образом:

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

Я хочу поменять элементы следующим образом:

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

Размер списков может отличаться, но они всегда будут содержать четное число элементов.

Я новичок в Python и сейчас делаю это вот так:

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

Я знаю, что это действительно не Pythonic и хотелось бы использовать что-то более эффективное. Может быть, понимание списка?

4b9b3361

Ответ 1

Нет необходимости в сложной логике, просто измените список с нарезкой и шагом:

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]

TL;DR;

Отредактировано с объяснением

Я считаю, что большинство зрителей уже знакомы с нарезкой списка и множественным назначением. В случае, если вы этого не сделаете, я попытаюсь изо всех сил объяснить, что происходит (надеюсь, что я не сделаю это хуже).

Чтобы понять, как нарезать список, здесь уже есть отличный ответ и объяснение нотации списка разделов. Проще говоря:

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

Посмотрите на требования к OP:

 [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

Первый уровень будет: l[::2] = [1, 3, 5, 7, 9] Второй уровень будет: l[1::2] = [2, 4, 6, 8, 10]

Поскольку мы хотим переписать first = second и second = first, мы можем использовать множественное назначение и обновить исходный список на месте:

first , second  = second , first

то есть:

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

В качестве дополнительной заметки, чтобы получить новый список, но не изменяя оригинальный l, мы можем назначить новый список из l и выполнить выше, то есть:

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]

Надеюсь, я не смущаю кого-либо из вас этим дополнительным объяснением. Если это так, пожалуйста, помогите обновить мою версию и сделать ее лучше:-)

Ответ 2

Вот одно понимание списка, которое делает трюк:

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]

Ключом к пониманию этого является следующая демонстрация того, как он изменяет индексы списка:

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

^ - это эксклюзивный или оператор. Все, что делает i^1, переворачивает младший бит i, эффективно заменяя 0 на 1, 2 на 3 и т.д.

Ответ 3

Вы можете использовать парную итерацию и привязать к сгладить список:

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

Или вы можете использовать itertools.chain.from_iterable(), чтобы избежать дополнительной распаковки:

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

Ответ 4

Контрольная точка между верхними ответами:

Python 2.7:

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

Python 3.4:

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

Если вам интересно узнать о разных действиях между python 2 и 3, вот почему:

Как вы можете видеть, ответ @NPE (inp1) работает очень хорошо в python3.4, причина в том, что в python3.X range() является интеллектуальным объектом и не сохраняет все элементы между этим диапазоном в как список.

Во многих случаях объект, возвращаемый range(), ведет себя так, как будто это список, но на самом деле это не так. Это объект, который возвращает последовательные элементы желаемой последовательности, когда вы перебираете ее, но это действительно не делает список, тем самым экономя пространство.

И поэтому в python 3 он не возвращает список, пока вы нарезаете объект диапазона.

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

Второе значительное изменение - усиление производительности третьего подхода (inp3). Как вы видите, разница между ним и последним решением уменьшилась до ~ 2 секунд (от ~ 7 секунд). Причина в том, что функция zip(), которая в python3.X возвращает итератор, который производит элементы по требованию. А поскольку chain.from_iterable() нужно снова перебирать элементы, он полностью избыточен, чтобы делать это до этого (что это делает zip в python 2).

код:

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

Ответ 5

Другой способ: создать вложенные списки с парами, изменяющими их порядок, затем сгладить списки с помощью 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: Я применил тест теста Kasramvd к моему решению, и я нашел это решение медленнее, чем другие лучшие ответы, поэтому я бы не рекомендовал его для больших списки. Я все еще считаю это вполне читаемым, хотя производительность не критична.

Ответ 6

Один из возможных ответов с использованием chain и list comprehension

>>> l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(chain([(l[2*i+1], l[2*i]) for i in range(0, len(l)/2)]))
[(2, 1), (4, 3), (6, 5), (8, 7), (10, 9)]

Ответ 7

Другой подход с просто перепривязывающей и нарезкой техники

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for a in range(0,len(l),2):
    l[a:a+2] = l[a-len(l)+1:a-1-len(l):-1]
print l

Выход

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

Ответ 8

Для удовольствия, если мы интерпретируем "swap" как означающий "reverse" в более общей области, подход itertools.chain.from_iterable может использоваться для подпоследовательностей более длинных длин.

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

def chunk(list_, n):
    return (list_[i:i+n] for i in range(0, len(list_), n))

list(chain.from_iterable(reversed(c) for c in chunk(l, 4)))
# [4, 3, 2, 1, 8, 7, 6, 5, 10, 9]

Ответ 9

Альтернативный вариант:

final_l = list() # make an empty list
for i in range(len(l)): # for as many items there are in the original list
    if i % 2 == 0: # if the item is even
        final_l.append(l[i+1]) # make this item in the new list equal to the next in the original list
    else: # else, so when the item is uneven
        final_l.append(l[i-1]) # make this item in the new list equal to the previous in the original list

Это предполагает, что исходный список имеет четное количество элементов. Если нет, можно добавить try-except:

final_l = list()
for i in range(len(l)):
    if i % 2 == 0:
        try: # try if we can add the next item
            final_l.append(l[i+1])
        except:  # if we can't (because i+1 doesnt exist), add the current item
            final_l.append(l[i])
    else:
            final_l.append(l[i-1])

Ответ 10

Способ использования Numpy

import numpy as np

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
l = np.array(l)
final_l = list(np.flip(l.reshape(len(l)//2,2), 1).flatten())

Ответ 11

Новое в стек переполнения. Пожалуйста, оставляйте комментарии или отзывы об этом решении.  swap = [2, 1, 4, 3, 5]

lst = []
for index in range(len(swap)):
        if index%2 == 0 and index < len(swap)-1:
            swap[index],swap[index+1]  = swap[index+1],swap[index]
        lst.append(swap[index])
print(lst)


out = [1, 2, 3, 4, 5]

Ответ 12

Я не вижу ничего плохого в вашей реализации вообще. Но вы могли бы сделать простой обмен.

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

ИЗМЕНИТЬ По-видимому, у Python есть более удобный способ сделать своп, который сделает код таким образом

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

Ответ 13

newList = [(x[2*i+1], x[2*i]) for i in range(0, len(x)/2)]

Теперь найдите способ распаковать кортежи. Я не буду делать все домашнее задание.

Ответ 14

Здесь решение, основанное на операторе modulo:

l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even = []
uneven = []
for i,item in enumerate(l):
    if i % 2 == 0:
        even.append(item)
    else:
        uneven.append(item)

list(itertools.chain.from_iterable(zip(uneven, even)))