Понимание списка, где условие зависит от генерируемого списка - PullRequest
2 голосов
/ 19 февраля 2020

Я новичок в Python программировании. Я хочу переписать следующий код для понимания списка:

lx = [1, 2, 3, 4, 5, 1, 2]
ly = [2, 5, 4]

lz = []
for x in lx:
    if x in ly and x not in lz:
        lz.append(x)

Это создаст новый список с общими элементами lx и ly; но условие x not in lz зависит от создаваемого списка. Как этот код можно переписать для понимания списка?

Ответы [ 3 ]

3 голосов
/ 19 февраля 2020

Вы не можете сделать это таким образом в понимании списка, поскольку вы не можете сравнить со списком lz, который еще не существует - при условии, что вы пытаетесь избежать дубликатов в результирующем списке, как в вашем примере.

Вместо этого вы можете использовать python set, который будет приводить в действие только один экземпляр каждого значения:

lz = set(x for x in lx if x in ly)

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

lz = set(lx) & set(ly)

ОБНОВЛЕНИЕ: Как указано @ Błotosmętek в комментариях - использование набора не сохранит порядок элементов в качестве набора по определению неупорядочен. Если порядок элементов значительный, потребуется другая стратегия.

0 голосов
/ 19 февраля 2020

Если вы не хотите использовать set, это может быть другой подход, использующий понимание списка.

lx = [1, 2, 3, 4, 5, 1, 2]
ly = [2, 5, 4]

lz=[]
[lz.append(x) for x in lx if (x in ly and x not in lz)]
print(lz)
0 голосов
/ 19 февраля 2020

Правильный ответ здесь - использовать наборы, потому что (1) наборы, естественно, имеют различные элементы, и (2) наборы более эффективны, чем списки для тестов на членство. Таким образом, простое решение - list(set(lx) & set(ly)).

Однако наборы не сохраняют порядок, в который вставляются элементы, поэтому в случае важности порядка, вот решение, которое сохраняет порядок с lx. (Если вам нужен порядок от ly, просто поменяйте местами два списка.)

def ordered_intersection(lx, ly):
    ly_set = set(ly)
    return [ly_set.remove(x) or x for x in lx if x in ly_set]

Пример:

>>> ordered_intersection(lx, ly)
[2, 4, 5]
>>> ordered_intersection(ly, lx)
[2, 5, 4]

Это работает, потому что ly_set.remove(x) всегда возвращает None, что неверно, поэтому ly_set.remove(x) or x всегда имеет значение x.

Причина, по которой вы не можете сделать это с помощью более простого понимания списка, такого как lz = [... if x in lz], заключается в том, что будет выполнено вычисление всего списка. до результирующий список присваивается переменной lz; поэтому тест x in lz даст NameError, потому что такой переменной еще нет.


Тем не менее, можно переписать ваш код для непосредственного использования генератора выражение (что-то вроде понимания списка) вместо цикла for; но это плохой код , и вы не должны этого делать:

def ordered_intersection_bad_dont_do_this(lx, ly):
    lz = []
    lz.extend(x for x in lx if x in ly and x not in lz)
    return lz

Это не просто плохо из-за многократного тестирования членства в списках; это хуже, потому что это зависит от неопределенного поведения метода extend. В частности, он добавляет каждый элемент один за другим, а не исчерпывает сначала итератор, а затем добавляет их все сразу. В документах не говорится, что это гарантированно произойдет, поэтому это плохое решение не обязательно будет работать в других версиях Python.

...