Общие элементы между двумя списками, не использующие наборы в Python

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

a=[2,2,1,1]
b=[1,1,3,3]

set(a) и set(b) работают
a и b не работают

Можно ли это сделать без набора и словаря?


person Thomas    schedule 28.04.2010    source источник
comment
Почему вы не хотите использовать наборы?   -  person Nick Presta    schedule 28.04.2010
comment
У меня есть повторяющиеся элементы в списке   -  person Thomas    schedule 28.04.2010
comment
каковы ожидаемые возвращаемые значения для [1, 2, 1] и [1, 3, 2]?   -  person SilentGhost    schedule 28.04.2010
comment
... и какой желаемый результат для [1, 2, 2] и [1, 1, 2]?   -  person Mark Dickinson    schedule 28.04.2010
comment
Думаю, он ожидает [1, 2] в обоих случаях   -  person o0'.    schedule 28.04.2010
comment
Судя по комментарию ниже, он хочет [1,2,1] в первом случае. До сих пор неясно, что он хочет за второй.   -  person Jason R. Coombs    schedule 28.04.2010


Ответы (3)


В Python 3.x (и Python 2.7, когда он будет выпущен) вы можете использовать collections.Counter для этого:

>>> from collections import Counter
>>> list((Counter([2,2,1,1]) & Counter([1,3,3,1])).elements())
[1, 1]

Вот альтернатива с использованием collections.defaultdict (доступно в Python 2.5 и более поздних версиях). . Его приятное свойство состоит в том, что порядок результата является детерминированным (по сути, он соответствует порядку второго списка).

from collections import defaultdict

def list_intersection(list1, list2):
    bag = defaultdict(int)
    for elt in list1:
        bag[elt] += 1

    result = []
    for elt in list2:
        if elt in bag:
            # remove elt from bag, making sure
            # that bag counts are kept positive
            if bag[elt] == 1:
                del bag[elt]
            else:
                bag[elt] -= 1
            result.append(elt)

    return result

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

person Mark Dickinson    schedule 28.04.2010
comment
if bag[elt]: добавляет предмет elt: 0 в корзину, когда elt in bag имеет значение false. Когда (как намекнул OP) дубликатов немного, может быть лучше сделать if elt in bag and bag[elt]:, иначе сумка может раздуться с бесполезными значениями 0. - person John Machin; 28.04.2010
comment
@Джон Мачин: Хм. Хорошая точка зрения. В качестве альтернативы можно было бы убедиться, что количество сумок всегда строго положительное, добавив после bag[elt] -= 1 if not bag[elt]: del bag[elt]. Тогда тест if может быть просто if elt in bag:, что читается лучше. Я отредактирую ответ. - person Mark Dickinson; 28.04.2010

Использование наборов является наиболее эффективным, но вы всегда можете сделать r = [i for i in l1 if i in l2].

person Max Shawabkeh    schedule 28.04.2010
comment
Заметное отличие: предложенный метод set() и set() вернет одно значение, общее для двух списков (в данном примере set([1])), тогда как ваше решение вернет [1, 1] и т. д. . - person Nick Presta; 28.04.2010
comment
Спасибо, но это какое-то более производительное решение? - person Thomas; 28.04.2010
comment
Мне нужна функция, которая возвращает [1,1] - person Thomas; 28.04.2010
comment
@Thomas: Это решение дает [1, 1], если l1 == [1, 1] и l2 == [1], но дает [1], если l1 == [1] и l2 == [1, 1]. Это поведение, которое вы хотите? - person Mark Dickinson; 28.04.2010

SilentGhost, Mark Dickinson и Lo'oris правы. Большое спасибо за сообщение об этой проблеме - мне нужна общая часть списков, поэтому для:

a=[1,1,1,2]

b=[1,1,3,3]

результат должен быть [1,1]

Извините за комментарий в неподходящем месте - я зарегистрировался сегодня.

Я изменил ваши решения:

def count_common(l1,l2):
        l2_copy=list(l2)
        counter=0
        for i in l1:
            if i in l2_copy:
                counter+=1
                l2_copy.remove(i)
        return counter

l1=[1,1,1]
l2=[1,2]
print count_common(l1,l2)

1

person Thomas    schedule 28.04.2010
comment
добро пожаловать! Возможно, вы захотите отправить электронное письмо [email protected] и попросить их объединить вашу новую зарегистрированную учетную запись с предыдущей временной учетной записью. См.: meta.stackexchange.com/questions/18232/ - person Mark Dickinson; 28.04.2010