Найти "перекрытие" между 2 списками python

Учитывая 2 списка:

a = [3,4,5,5,5,6]
b = [1,3,4,4,5,5,6,7]

Я хочу найти "перекрытие":

c = [3,4,5,5,6]

Мне также понравится, если бы я смог извлечь "остаток" часть a и b, которая не находится в c.

a_remainder = [5,]
b_remainder = [1,4,7,]

Примечание: a имеет три 5 в нем, а b - два. b имеет два 4 в нем, а a имеет один.

Результирующий список c должен иметь два 5 (ограниченных списком b) и один 4 (ограничен списком a).

Это дает мне то, что я хочу, но я не могу не думать об этом гораздо лучше.

import copy

a = [3,4,5,5,5,6]
b = [1,3,4,4,5,5,6,7]

c = []
for elem in copy.deepcopy(a):
    if elem in b:
        a.pop(a.index(elem))
        c.append(b.pop(b.index(elem)))

# now a and b both contain the "remainders" and c contains the "overlap"

В другой заметке, что более точное имя для того, что я прошу, чем "перекрытие" и "остаток"?

Ответы

Ответ 1

collection.Counter, доступный в Python 2.7, может использоваться для реализации мультимножеств, которые делают именно то, что вы хотите.

a = [3,4,5,5,5,6]
b = [1,3,4,4,5,5,6,7]

a_multiset = collections.Counter(a)
b_multiset = collections.Counter(b)

overlap = list((a_multiset & b_multiset).elements())
a_remainder = list((a_multiset - b_multiset).elements())
b_remainder = list((b_multiset - a_multiset).elements())

print overlap, a_remainder, b_remainder

Ответ 2

В языке наборов перекрытие - это "пересечение", а остальная часть - "разность заданий". Если у вас были разные элементы, вам не нужно было бы выполнять эти операции самостоятельно, если вы заинтересованы, просмотрите http://docs.python.org/library/sets.html.

Поскольку мы не работаем с определенными элементами, ваш подход разумный. Если вы хотите, чтобы это выполнялось быстрее, вы могли бы создать словарь для каждого списка и сопоставить число, сколько элементов в каждом массиве (например, в a, 3- > 1, 4- > 1, 5- > 2 и т.д..). Затем вы перебирали через карту a, определяли, существовала ли эта буква, уменьшайте ее количество и добавьте в новый список

Неподтвержденный код, но это идея

def add_or_update(map,value):
    if value in map:
        map[value]+=1
    else
        map[value]=1

b_dict = dict()
for b_elem in b:
    add_or_update(b_dict,b_elem)

intersect = []; diff = [];

for a_elem in a:
    if a_elem in b_dict and b_dict[a_elem]>0:
        intersect.add(a_elem);

for k,v in diff:
    for i in range(v):
        diff.add(k);

Ответ 3

ОК, многословный, но классный (похож по духу на идею collections.Counter, но более самодельный):

import itertools as it
flatten = it.chain.from_iterable 
sorted(
   v for u,v in 
    set(flatten(enumerate(g) 
        for k, g in it.groupby(a))).intersection(
    set(flatten(enumerate(g)
        for k, g in it.groupby(b))))
   )

Основная идея состоит в том, чтобы сделать каждый из списков в новом списке, который прикрепляет счетчик к каждому объекту, пронумерованный для учета дубликатов, - так что тогда вы можете использовать операции set для этих кортежей в конце концов.

Чтобы быть немного менее подробным:

 aa = set(flatten(enumerate(g) for k, g in it.groupby(a)))
 bb = set(flatten(enumerate(g) for k, g in it.groupby(b)))
 # aa = set([(0, 3), (0, 4), (0, 5), (0, 6), (1, 5), (2, 5)])
 # bb = set([(0, 1), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (1, 4), (1, 5)])

 cc = aa.intersection(bb)
 # cc = set([(0, 3), (0, 4), (0, 5), (0, 6), (1, 5)])
 c = sorted(v for u,v in cc)
 # c = [3, 4, 5, 5, 6]
  • groupby - создает список списков, содержащих идентичные элементы (но из-за синтаксиса требуется g for k,g in it.groupby(a) для извлечения каждого списка).
  • enumerate - добавляет счетчик к каждому элементу каждого подсписок
  • flatten - создать единый список
  • set - преобразовать в набор
  • intersection - найти общие элементы
  • sorted(v for u,v in cc) - избавиться от счетчиков и отсортировать результат

Наконец, я не уверен, что вы подразумеваете под остатками; мне кажется, что это должны быть мои aa-cc и bb-cc, но я не знаю, где вы получаете a_remainder = [4]:

sorted(v for u,v in aa-cc)
# [5]

sorted(v for u,v in bb-cc)
# [1, 4, 7]

Ответ 4

Ответ от керио в #python на freenode:

[ i for i in itertools.chain.from_iterable([k] * v for k, v in \
  (Counter(a) & Counter(b)).iteritems())
]

Ответ 5

Использовать набор python

intersection = set(a) & set(b)
a_remainder = set(a) - set(b)
b_remainder = set(b) - set(a)

Ответ 6

Попробуйте difflib.SequenceMatcher(), "гибкий класс для сравнения пар последовательностей любого типа"...

Быстрая попытка:

a = [3,4,5,5,5,6]
b = [1,3,4,4,5,5,6,7]

sm = difflib.SequenceMatcher(None, a, b)
c = []
a_remainder = []
b_remainder = []

for tag, i1, i2, j1, j2 in sm.get_opcodes():
    if tag == 'replace':
        a_remainder.extend(a[i1:i2])
        b_remainder.extend(b[j1:j2])
    elif tag == 'delete':
        a_remainder.extend(a[i1:i2])
    elif tag == 'insert':
        b_remainder.extend(b[j1:j2])
    elif tag == 'equal':
        c.extend(a[i1:i2])

И теперь...

>>> print c
[3, 4, 5, 5, 6]
>>> print a_remainder
[5]
>>> print b_remainder
[1, 4, 7]

Ответ 8

Я не думаю, что вы действительно должны использовать это решение, но я воспользовался этой возможностью, чтобы практиковать с лямбда-функциями, и вот что я придумал:)

a = [3,4,5,5,5,6]
b = [1,3,4,4,5,5,6,7]
dedup = lambda x: [set(x)] if len(set(x)) == len(x) else [set(x)] + dedup([x[i] for i in range(1, len(x)) if x[i] == x[i-1]])
default_set = lambda x: (set() if x[0] is None else x[0], set() if x[1] is None else x[1])
deduped = map(default_set, map(None, dedup(a), dedup(b)))
get_result = lambda f: reduce(lambda x, y: list(x) + list(y), map(lambda x: f(x[0], x[1]), deduped))
c = get_result(lambda x, y: x.intersection(y))          # [3, 4, 5, 6, 5]
a_remainder = get_result(lambda x, y: x.difference(y))  # [5]
b_remainder = get_result(lambda x, y: y.difference(x))  # [1, 7, 4]

Я уверен, izip_longest немного упростил бы это (не понадобилось бы лямбда default_set), но Я тестировал это с помощью Python 2.5.

Вот некоторые из промежуточных значений, используемых при расчете, если кто-то хочет это понять:

dedup(a) = [set([3, 4, 5, 6]), set([5]), set([5])]
dedup(b) = [set([1, 3, 4, 5, 6, 7]), set([4, 5])]
deduped = [(set([3, 4, 5, 6]), set([1, 3, 4, 5, 6, 7])), (set([5]), set([4, 5])), (set([5]), set([]))]