Учитывая список
a = [0,1,2,3,4,5,6,7,8,9]
как я могу получить
b = [0,9,1,8,2,7,3,6,4,5]
То есть, создайте новый список, в котором каждый последующий элемент поочередно берется с двух сторон исходного списка?
Учитывая список
a = [0,1,2,3,4,5,6,7,8,9]
как я могу получить
b = [0,9,1,8,2,7,3,6,4,5]
То есть, создайте новый список, в котором каждый последующий элемент поочередно берется с двух сторон исходного списка?
>>> [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, чем итерация.
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]
Очень хороший однострочный слой в 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
даст вам дублирующее среднее значение, поэтому будьте предупреждены.
Вы можете просто pop
назад и вперед:
b = [a.pop(-1 if i%2 else 0) for i in range(len(a))]
Примечание. Это разрушает исходный список, a
.
Для удовольствия, вот вариант 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)
четное. Для этого потребуется специальный код для нечетного ввода.
Наслаждайтесь!
Не сильно отличается от некоторых других ответов, но он избегает условного выражения для определения знака индекса.
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))
.)
Основным принципом вашего вопроса является так называемый алгоритм 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)))
Используйте правую 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)))
mylist = [0,1,2,3,4,5,6,7,8,9]
result = []
for i in mylist:
result += [i, mylist.pop()]
Примечание:
Остерегайтесь: Как сказал @Tadhg McDonald-Jensen (см. комментарий ниже) он уничтожит половину исходного объекта списка.
Не уверен, что это можно записать более компактно, но оно эффективно, поскольку оно использует только итераторы/генераторы
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)]
Не совсем элегантный, но это неуклюжий однострочный:
a = range(10)
[val for pair in zip(a[:len(a)//2],a[-1:(len(a)//2-1):-1]) for val in pair]
Обратите внимание, что он предполагает, что вы делаете это для списка четной длины. Если это ломается, тогда это прерывается (оно уменьшает средний срок). Обратите внимание, что я получил часть идеи здесь.
Две версии пока не видны:
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)))]
Я бы сделал что-то вроде этого
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
Один из способов сделать это для списков четного размера (вдохновленный этим сообщением):
a = range(10)
b = [val for pair in zip(a[:5], a[5:][::-1]) for val in pair]
Вы можете разбить список на две части относительно середины, перевернуть вторую половину и застегнуть два раздела, например:
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]