Цикл списка из чередующихся сторон
Учитывая список
a = [0,1,2,3,4,5,6,7,8,9]
как я могу получить
b = [0,9,1,8,2,7,3,6,4,5]
То есть, создайте новый список, в котором каждый последующий элемент поочередно берется с двух сторон исходного списка?
Ответы
Ответ 1
>>> [a[-i//2] if i % 2 else a[i//2] for i in range(len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Объяснение:
Этот код выбирает числа из начала (a[i//2]
) и из конца (a[-i//2]
) of a
, чередуясь (if i%2 else
). Общее количество len(a)
выбрано, поэтому это не дает никаких негативных последствий, даже если len(a)
является нечетным.
[-i//2 for i in range(len(a))]
дает 0, -1, -1, -2, -2, -3, -3, -4, -4, -5
,
[ i//2 for i in range(len(a))]
дает 0, 0, 1, 1, 2, 2, 3, 3, 4, 4
,
и i%2
чередуется между False
и True
,
поэтому индексы, которые мы извлекаем из a
, равны: 0, -1, 1, -2, 2, -3, 3, -4, 4, -5
.
Моя оценка питоничности:
Самое приятное в этом однострочном лайнере состоит в том, что оно короткое и показывает симметрию (+i//2
и -i//2
).
Плохая вещь, однако, в том, что эта симметрия обманчива:
Можно подумать, что -i//2
были такими же, как i//2
, когда знак перевернулся. Но в Python целочисленное деление возвращает пол результата вместо усечения в нуль. Итак, -1//2 == -1
.
Кроме того, я нахожу доступ к элементам списка по индексу менее pythonic, чем итерация.
Ответ 2
cycle
между получением элементов из форвардной iter
и reversed
один. Просто убедитесь, что вы остановились на len(a)
с помощью islice
.
from itertools import islice, cycle
iters = cycle((iter(a), reversed(a)))
b = [next(it) for it in islice(iters, len(a))]
>>> b
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Это можно легко поместить в одну строку, но затем становится намного труднее прочитать:
[next(it) for it in islice(cycle((iter(a),reversed(a))),len(a))]
Помещение в одну строку также помешает вам использовать другую половину итераторов, если вы хотите:
>>> iters = cycle((iter(a), reversed(a)))
>>> [next(it) for it in islice(iters, len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
>>> [next(it) for it in islice(iters, len(a))]
[5, 4, 6, 3, 7, 2, 8, 1, 9, 0]
Ответ 3
Очень хороший однострочный слой в Python 2.7:
results = list(sum(zip(a, reversed(a))[:len(a)/2], ()))
>>>> [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Сначала вы закроете список своим обратным, возьмите половину этого списка, суммируйте кортежи, чтобы сформировать один кортеж, а затем конвертируйте в список.
В Python 3, zip
возвращает генератор, поэтому вам нужно использовать islice
из itertools
:
from itertools import islice
results = list(sum(islice(zip(a, reversed(a)),0,int(len(a)/2)),()))
Edit: Похоже, что это работает отлично только для длин четного списка - длины нечетного списка будут опускать средний элемент:( Небольшая поправка для int(len(a)/2)
to int(len(a)/2) + 1
даст вам дублирующее среднее значение, поэтому будьте предупреждены.
Ответ 4
Вы можете просто pop
назад и вперед:
b = [a.pop(-1 if i%2 else 0) for i in range(len(a))]
Примечание. Это разрушает исходный список, a
.
Ответ 5
Для удовольствия, вот вариант itertools:
>>> a = [0,1,2,3,4,5,6,7,8,9]
>>> list(chain.from_iterable(izip(islice(a, len(a)//2), reversed(a))))
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
Это работает там, где len(a)
четное. Для этого потребуется специальный код для нечетного ввода.
Наслаждайтесь!
Ответ 6
Не сильно отличается от некоторых других ответов, но он избегает условного выражения для определения знака индекса.
a = range(10)
b = [a[i // (2*(-1)**(i&1))] for i in a]
i & 1
чередуется между 0 и 1. Это заставляет экспонента чередоваться между 1 и -1. Это приводит к тому, что делитель индекса чередуется между 2 и -2, что приводит к тому, что индекс чередуется от конца к концу по мере увеличения i
. Последовательность a[0]
, a[-1]
, a[1]
, a[-2]
, a[2]
, a[-3]
и т.д.
(я повторяю i
над a
, так как в этом случае каждое значение a
равно его индексу. В общем случае итерация по range(len(a))
.)
Ответ 7
Основным принципом вашего вопроса является так называемый алгоритм roundrobin. itertools
-документация-страница содержит возможную реализацию:
from itertools import cycle, islice
def roundrobin(*iterables):
"""This function is taken from the python documentation!
roundrobin('ABC', 'D', 'EF') --> A D E B F C
Recipe credited to George Sakkis"""
pending = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables) # next instead of __next__ for py2
while pending:
try:
for next in nexts:
yield next()
except StopIteration:
pending -= 1
nexts = cycle(islice(nexts, pending))
так что все, что вам нужно сделать, - это разбить ваш список на два подсписок, начиная с левого и с правого конца:
import math
mid = math.ceil(len(a)/2) # Just so that the next line doesn't need to calculate it twice
list(roundrobin(a[:mid], a[:mid-1:-1]))
# Gives you the desired result: [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
в качестве альтернативы вы могли бы создать более длинный список (содержащий переменные элементы из последовательности, идущие слева направо, и элементы полной последовательности, идущие справа налево) и принимать только соответствующие элементы:
list(roundrobin(a, reversed(a)))[:len(a)]
или используя его как явный генератор с next
:
rr = roundrobin(a, reversed(a))
[next(rr) for _ in range(len(a))]
или быстрый вариант, предложенный @Tadhg McDonald-Jensen (спасибо!):
list(islice(roundrobin(a,reversed(a)),len(a)))
Ответ 8
Используйте правую toolz.
from toolz import interleave, take
b = list(take(len(a), interleave((a, reversed(a)))))
Во-первых, я попробовал нечто похожее на решение Раймонда Хеттингера с itertools (Python 3).
from itertools import chain, islice
interleaved = chain.from_iterable(zip(a, reversed(a)))
b = list(islice(interleaved, len(a)))
Ответ 9
mylist = [0,1,2,3,4,5,6,7,8,9]
result = []
for i in mylist:
result += [i, mylist.pop()]
Примечание:
Остерегайтесь: Как сказал @Tadhg McDonald-Jensen (см. комментарий ниже) он уничтожит половину исходного объекта списка.
Ответ 10
Не уверен, что это можно записать более компактно, но оно эффективно, поскольку оно использует только итераторы/генераторы
a = [0,1,2,3,4,5,6,7,8,9]
iter1 = iter(a)
iter2 = reversed(a)
b = [item for n, item in enumerate(
next(iter) for _ in a for iter in (iter1, iter2)
) if n < len(a)]
Ответ 11
Не совсем элегантный, но это неуклюжий однострочный:
a = range(10)
[val for pair in zip(a[:len(a)//2],a[-1:(len(a)//2-1):-1]) for val in pair]
Обратите внимание, что он предполагает, что вы делаете это для списка четной длины. Если это ломается, тогда это прерывается (оно уменьшает средний срок). Обратите внимание, что я получил часть идеи здесь.
Ответ 12
Две версии пока не видны:
b = list(sum(zip(a, a[::-1]), ())[:len(a)])
и
import itertools as it
b = [a[j] for j in it.accumulate(i*(-1)**i for i in range(len(a)))]
Ответ 13
Я бы сделал что-то вроде этого
a = [0,1,2,3,4,5,6,7,8,9]
b = []
i = 0
j = len(a) - 1
mid = (i + j) / 2
while i <= j:
if i == mid and len(a) % 2 == 1:
b.append(a[i])
break
b.extend([a[i], a[j]])
i = i + 1
j = j - 1
print b
Ответ 14
Один из способов сделать это для списков четного размера (вдохновленный этим сообщением):
a = range(10)
b = [val for pair in zip(a[:5], a[5:][::-1]) for val in pair]
Ответ 15
Вы можете разбить список на две части относительно середины, перевернуть вторую половину и застегнуть два раздела, например:
a = [0,1,2,3,4,5,6,7,8,9]
mid = len(a)//2
l = []
for x, y in zip(a[:mid], a[:mid-1:-1]):
l.append(x)
l.append(y)
# if the length is odd
if len(a) % 2 == 1:
l.append(a[mid])
print(l)
Вывод:
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]