it-swarm.com.ru

Лучший способ чередовать два или более списков в Python?

Предположим, у меня есть список:

l=['a','b','c']

И его список суффиксов:

l2 = ['a_1', 'b_1', 'c_1']

Я хотел бы, чтобы желаемый результат был:

out_l = ['a','a_1','b','b_2','c','c_3']

Результатом является чередованная версия двух списков выше.

Я могу написать обычный цикл for, чтобы сделать это, но мне интересно, есть ли более Pythonic способ (например, использование списка или лямбда), чтобы сделать это.

Я пробовал что-то вроде этого:

list(map(lambda x: x[1]+'_'+str(x[0]+1), enumerate(a)))
# this only returns ['a_1', 'b_2', 'c_3']

Кроме того, какие изменения необходимо внести для общего случая, то есть для 2 или более списков, где l2 не обязательно является производной от l?

26
user1330974

yield

Вы можете использовать генератор для элегантного решения. На каждой итерации выведите дважды— один раз с исходным элементом и один раз с элементом с добавленным суффиксом.

Генератор должен быть исчерпан; это можно сделать, подключив вызов list в конце.

def transform(l):
    for i, x in enumerate(l, 1):
        yield x
        yield f'{x}_{i}'  # {}_{}'.format(x, i)

Вы также можете переписать это, используя синтаксис yield from для делегирования генератора:

def transform(l):
    for i, x in enumerate(l, 1):
        yield from (x, f'{x}_{i}') # (x, {}_{}'.format(x, i))

out_l = list(transform(l))
print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Если у вас версия старше Python-3.6, замените f'{x}_{i}' на '{}_{}'.format(x, i).

Обобщая
Рассмотрим общий сценарий, где у вас есть N списков в форме:

l1 = [v11, v12, ...]
l2 = [v21, v22, ...]
l3 = [v31, v32, ...]
...

Который вы хотели бы чередовать. Эти списки не обязательно являются производными друг от друга.

Чтобы обрабатывать операции чередования с этими N списками, вам нужно выполнить итерации по парам:

def transformN(*args):
    for vals in Zip(*args):
        yield from vals

out_l = transformN(l1, l2, l3, ...)

Нарезанный list.__setitem__

Я бы порекомендовал это с точки зрения производительности. Сначала выделите место для пустого списка, а затем назначьте элементы списка на их соответствующие позиции, используя назначение нарезанного списка. l входит в четные индексы, а l' (измененная l) входит в нечетные индексы. 

out_l = [None] * (len(l) * 2)
out_l[::2] = l
out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]  # [{}_{}'.format(x, i) ...]

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Это всегда самый быстрый из моих таймингов (ниже).

Обобщая
Для обработки N списков, итеративно присваивать срезы. 

list_of_lists = [l1, l2, ...]

out_l = [None] * len(list_of_lists[0]) * len(list_of_lists)
for i, l in enumerate(list_of_lists):
    out_l[i::2] = l

Zip + chain.from_iterable

Функциональный подход, аналогичный решению @chrisz. Создайте пары, используя Zip, а затем выровняйте их, используя itertools.chain.

from itertools import chain
# [{}_{}'.format(x, i) ...]
out_l = list(chain.from_iterable(Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))) 

print(out_l)
['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

iterools.chain широко рассматривается как подход к выравниванию списков Pythonic.

Обобщая
Это самое простое решение для обобщения, и я подозреваю, что наиболее эффективно для нескольких списков, когда N велико.

list_of_lists = [l1, l2, ...]
out_l = list(chain.from_iterable(Zip(*list_of_lists)))

Спектакль

Давайте посмотрим на некоторые perf-тесты для простого случая двух списков (один список с суффиксом). Общие случаи не будут проверяться, так как результаты широко варьируются в зависимости от данных.

from timeit import timeit

import pandas as pd
import matplotlib.pyplot as plt

res = pd.DataFrame(
       index=['ajax1234', 'cs0', 'cs1', 'cs2', 'cs3', 'chrisz', 'sruthiV'],
       columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000],
       dtype=float
)

for f in res.index: 
    for c in res.columns:
        l = ['a', 'b', 'c', 'd'] * c
        stmt = '{}(l)'.format(f)
        setp = 'from __main__ import l, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=50)

ax = res.div(res.min()).T.plot(loglog=True) 
ax.set_xlabel("N"); 
ax.set_ylabel("time (relative)");

plt.show()

 enter image description here

Функции

def ajax1234(l):
    return [
        i for b in [[a, '{}_{}'.format(a, i)] 
        for i, a in enumerate(l, start=1)] 
        for i in b
    ]

def cs0(l):
    # this is in Ajax1234's answer, but it is my suggestion
    return [j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]

def cs1(l):
    def _cs1(l):
        for i, x in enumerate(l, 1):
            yield x
            yield f'{x}_{i}'

    return list(_cs1(l))

def cs2(l):
    out_l = [None] * (len(l) * 2)
    out_l[::2] = l
    out_l[1::2] = [f'{x}_{i}' for i, x in enumerate(l, 1)]

    return out_l

def cs3(l):
    return list(chain.from_iterable(
        Zip(l, [f'{x}_{i}' for i, x in enumerate(l, 1)]))
    )

def chrisz(l):
    return [
        val 
        for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) 
        for val in pair
    ]

def sruthiV(l):
    return [ 
        l[int(i / 2)] + "_" + str(int(i / 2) + 1) if i % 2 != 0 else l[int(i/2)] 
        for i in range(0,2*len(l))
    ]

Программного обеспечения

Система - Mac OS X High Sierra - 2,4 ГГц Intel Core i7
Python-3.6.0
IPython-6.2.1 

57
coldspeed

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

l=['a','b','c']
new_l = [i for b in [[a, '{}_{}'.format(a, i)] for i, a in enumerate(l, start=1)] for i in b]

Результат:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']

Необязательный, более короткий метод:

[j for i, a in enumerate(l, 1) for j in [a, '{}_{}'.format(a, i)]]
6
Ajax1234

Вы можете использовать Zip :

[val for pair in Zip(l, [f'{k}_{j+1}' for j, k in enumerate(l)]) for val in pair]

Результат:

['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
5
user3483203

Вот моя простая реализация

l=['a','b','c']
# generate new list with the indices of the original list
new_list=l + ['{0}_{1}'.format(i, (l.index(i) + 1)) for i in l]
# sort the new list in ascending order
new_list.sort()
print new_list
# Should display ['a', 'a_1', 'b', 'b_2', 'c', 'c_3']
2
Isaac Boakye

Если вы хотите вернуть [["a","a_1"],["b","b_2"],["c","c_3"]], вы можете написать 

new_l=[[x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)]

Это не то, что вы хотите, вместо этого вы хотите ["a","a_1"]+["b","b_2"]+["c","c_3"]. Это можно сделать из результата описанной выше операции, используя sum(); Поскольку вы суммируете списки, вам нужно добавить пустой список в качестве аргумента, чтобы избежать ошибки. Так что дает

new_l=sum(([x,"{}_{}".format(x,i+1)] for i,x in enumerate(l)),[])

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

0
Especially Lime

Очень простое решение:

out_l=[]
for i,x in enumerate(l,1):
    out_l.extend([x,f"{x}_{i}"])
0
kantal