Python: взять каждый первый, второй, третий элемент в подсписке
Я использую Python 2.7 и имею следующее:
my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Я хотел бы создать 1-й список, где элементы упорядочены по позиции в подсписке, а затем по порядку подсписка. Таким образом, правильный вывод для приведенного выше списка:
[1, 4, 7, 2, 5, 8, 3, 6, 9]
Вот моя (неверная) попытка:
def reorder_and_flatten(my_list):
my_list = [item for sublist in my_list for item in sublist]
result_nums = []
for i in range(len(my_list)):
result_nums.extend(my_list[i::3])
return result_nums
result = reorder_and_flatten(my_list)
Это сглаживает мой 2-й список и дает мне:
[1, 4, 7, 2, 5, 8, 3, 6, 9, 4, 7, 5, 8, 6, 9, 7, 8, 9]
Первая половина этого списка верна, а вторая нет.
Я также хотел бы, чтобы моя функция могла обрабатывать только 2 подсписка. Например, если дано:
[[1, 2, 3], [], [7, 8, 9]
правильный вывод:
[1, 7, 2, 8, 3, 9]
Какие-нибудь мысли?
Спасибо!
4 ответа
Вы пытаетесь сгладить, а затем переупорядочить, что делает вещи намного сложнее, чем переупорядочение, а затем сглаживание.
Во-первых, для вашей первоначальной проблемы это просто "распаковать", как описано в документации для zip
:
>>> my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> list(zip(*my_list))
... [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
(В Python 2.7 вы могли бы просто написать zip(…)
здесь вместо list(zip(…))
, но в этом случае одна и та же демонстрация работает одинаково в 2.x и 3.x.)
И потом, вы уже знаете, как сгладить это:
>>> [item for sublist in zip(*my_list) for item in sublist]
[1, 4, 7, 2, 5, 8, 3, 6, 9]
Но все становится немного сложнее для вашего второго случая, когда некоторые списки могут быть пустыми (или, может быть, просто короче?).
Там нет функции, которая как zip
но пропускает пропущенные значения. Вы можете написать один довольно легко. Но вместо этого... есть функция, которая как zip
но заполняет пропущенные значения с None
(или что-то еще, что вы предпочитаете), izip_longest
, Итак, мы можем просто использовать это, а затем отфильтровать None
значения, как мы сглаживаем:
>>> my_list = [[1, 2, 3], [], [7, 8, 9]]
>>> from itertools import izip_longest
>>> list(izip_longest(*my_list))
[(1, None, 7), (2, None, 8), (3, None, 9)]
>>> [item for sublist in izip_longest(*my_list) for item in sublist if item is not None]
[1, 7, 2, 8, 3, 9]
(В Python 3 функция izip_longest
переименован zip_longest
.)
Стоит отметить, что roundrobin
Рецепт, как указано в ответе ShadowRanger, является еще более хорошим решением этой проблемы, и его еще проще использовать (просто скопируйте и вставьте его из документации, или pip install more_itertools
и использовать его оттуда). Это немного сложнее понять, но стоит потратить время, чтобы понять это (и попросить помощи, если вы застряли).
result = [l[i] for i in range(max(len(v) for v in my_list)) for l in my_list if l]
т.е.
my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[l[i] for i in range(max(len(v) for v in my_list)) for l in my_list if l]
# => [1, 4, 7, 2, 5, 8, 3, 6, 9]
my_list = [[1, 2, 3], [], [7, 8, 9]]
[l[i] for i in range(max(len(v) for v in my_list)) for l in my_list if l]
# => [1, 7, 2, 8, 3, 9]
Если вы счастливы использовать стороннюю библиотеку, вы можете использовать NumPy и np.ndarray.ravel
:
import numpy as np
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
res_a = A.ravel('F') # array([1, 4, 7, 2, 5, 8, 3, 6, 9])
Для случая, когда у вас есть один или несколько пустых списков, вы можете использовать filter
удалить пустые списки:
B = np.array(list(filter(None, [[1, 2, 3], [], [7, 8, 9]])))
res_b = B.ravel('F') # array([1, 7, 2, 8, 3, 9])
Оба решения требуют, чтобы непустые подсписки содержали одинаковое количество элементов. Если преобразование списка необходимо, вы можете использовать, например, res_a.tolist()
,
Хотя эти методы "черного ящика" многому вас не научат, они будут быстрее для больших массивов, чем list
операции. Смотрите также Каковы преимущества NumPy перед обычными списками Python?
itertools
раздел рецептов модуля обеспечивает roundrobin
рецепт, который будет делать именно то, что вы хотите. Он производит генератор, но ваше ожидаемое поведение будет видно с помощью:
# define roundrobin recipe here
from itertools import cycle, islice
def roundrobin(*iterables):
"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)
while pending:
try:
for next in nexts:
yield next()
except StopIteration:
pending -= 1
nexts = cycle(islice(nexts, pending))
def reorder_and_flatten(my_list):
return list(roundrobin(*my_list))
Основная проблема вашего исходного кода в том, что он зациклен на for i in range(len(my_list)):
, расширяясь с my_list[i::3]
, Проблема в том, что это приводит к дублированию элементов с индекса 3 и далее (индекс 3 уже был выбран в качестве второго элемента среза индекса 0). Здесь много других небольших логических ошибок, поэтому намного проще использовать рецепт.
Это будет довольно производительным и обобщать лучше, чем большинство решений, свернутых вручную (оно будет правильно округлять робин, даже если подсписки имеют неравномерную длину, и не требует фильтрации второго прохода или какой-либо специальной обработки, чтобы None
как значение как zip_longest
делает).