Понимание для выравнивания последовательности последовательностей?

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

Допустим, я хочу поменять местами элементы каждой пары в последовательности. Я использую строку в качестве последовательности здесь:

>>> from itertools import chain
>>> seq = '012345'
>>> swapped_pairs = zip(seq[1::2], seq[::2])
>>> swapped_pairs
[('1', '0'), ('3', '2'), ('5', '4')]
>>> "".join(chain(*swapped_pairs))
'103254'

Я использую zip на четных и нечетных фрагментах последовательности, чтобы поменять местами пары. Но в итоге я получаю список кортежей, которые теперь нужно сплющить. Поэтому я использую цепочку (). Есть ли способ, которым я мог бы выразить это с пониманием вместо этого?

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

4 ответа

Решение

С пониманием? Что ж...

>>> seq = '012345'
>>> swapped_pairs = zip(seq[1::2], seq[::2])
>>> ''.join(item for pair in swapped_pairs for item in pair)
'103254'

Самое быстрое, что я нашел - начать с пустого массива и расширить его:

In [1]: a = [['abc', 'def'], ['ghi'],['xzy']]

In [2]: result = []

In [3]: extend = result.extend

In [4]: for l in a:
   ...:     extend(l)
   ...: 

In [5]: result
Out[5]: ['abc', 'def', 'ghi', 'xzy']

Это более чем в два раза быстрее, чем в примере с попыткой Алекса Мартелли: сделать плоский список из списка списков в Python

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 86.3 usec per loop

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99'  'b = []' 'extend = b.extend' 'for sub in l:' '    extend(sub)'
10000 loops, best of 3: 36.6 usec per loop

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

Кстати, это только линейное ускорение:

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]'  'b = []' 'extend = b.extend' 'for sub in l:' '    extend(sub)'
1000000 loops, best of 3: 0.844 usec per loop

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]' '[item for sublist in l for item in sublist]'
1000000 loops, best of 3: 1.56 usec per loop

Вы также можете использовать map(results.extend, a), но это медленнее, так как он строит свой собственный список Nones.

Это также дает вам некоторые преимущества неиспользования функционального программирования. т.е.

  • Вы можете расширить существующий список вместо создания пустого,
  • Вы все еще можете понять код с первого взгляда, минут, дней или даже месяцев спустя.

Кстати, наверное, лучше всего избегать списочных представлений. Маленькие не так уж и плохи, но в целом понимание списков на самом деле не экономит много времени на наборе текста, но их часто труднее понять и очень трудно изменить или изменить (когда-либо видели трехуровневое понимание списка?). Руководства по кодированию Google советуют против них, за исключением простых случаев. Мое мнение таково, что они полезны только в "выбрасываемом" коде, то есть в коде, где автору нет дела до читабельности, или в коде, который, как известно, никогда не требует дальнейшего обслуживания.

Сравните эти два способа написания одного и того же:

result = [item for sublist in l for item in sublist]

с этим:

result = []
for sublist in l:
    for item in sublist:
        result.append(item)

YMMV, но первый остановил меня, и я должен был подумать об этом. Во втором вложенность становится очевидной из отступа.

Вы можете использовать снижение, чтобы достичь своей цели:

In [6]: import operator
In [7]: a = [(1, 2), (2,3), (4,5)]
In [8]: reduce(operator.add, a, ())
Out[8]: (1, 2, 2, 3, 4, 5)

Это возвращает кортеж вместо списка, потому что элементы в вашем исходном списке являются кортежами, которые объединяются. Но из этого можно легко составить список, и метод join также принимает кортежи.

Понимание списка, кстати, не самый подходящий инструмент для этого. По сути, понимание списка создает новый список, описывая, как должны выглядеть элементы этого списка. Вы хотите уменьшить список элементов только до одного значения.

>>> a = [(1, 2), (3, 4), (5, 6)]
>>> reduce(tuple.__add__, a)
>>> (1, 2, 3, 4, 5, 6)

Или, чтобы быть агностиком относительно типа внутренних последовательностей (если они все одинаковы):

>>> reduce(a[0].__class__.__add__, a)
Другие вопросы по тегам