Объединение нескольких наборов в python

[[1, '34', '44'], [1, '40', '30', '41'], [1, '41', '40', '42'], [1, '42', '41', '43'], [1, '43', '42', '44'], [1, '44', '34', '43']]

У меня есть список списков. Моя цель - проверить, имеет ли какой-либо один подсписок что-то общее с другими подсписками (за исключением первого объекта индекса для сравнения). Если у него есть что-то общее, объедините эти подсписки.

Например, для этого примера мой окончательный ответ должен быть примерно таким:

[[1, '34, '44', '40' '30', '41', '42', '43']]

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

Что я хочу знать, есть ли какой-либо эффективный способ сравнить все подсписки (преобразованные в наборы) и получить их объединение?


person Tapojyoti Mandal    schedule 11.06.2015    source источник
comment
нужен такой же заказ?   -  person Ajay    schedule 11.06.2015
comment
Нет, порядок охранять не надо.   -  person Tapojyoti Mandal    schedule 11.06.2015
comment
Могут ли быть другие списки, начинающиеся, например, с. 2, которые не должны сочетаться?   -  person Peter Wood    schedule 11.06.2015
comment
Вы этого хотите?stackoverflow.com/a/27803361/2867928   -  person kasravnd    schedule 11.06.2015
comment
На самом деле, я забыл подчеркнуть одно важное условие. Извините за мою ошибку. Я также упомянул, что подсписки должны быть унифицированы тогда и только тогда, когда они имеют что-то общее, в противном случае они должны оставаться такими, какие они есть. Итак, сначала необходимо проверить наличие пересечения (), которое, если оно не пусто, должно выполняться только объединение. @Peter Wood Нет, не будет никакого подсписка с отдельным начальным элементом индекса, таким как «2» или «3». Я имею в виду, что в списке списков все подсписки будут иметь один и тот же первый элемент индекса.   -  person Tapojyoti Mandal    schedule 11.06.2015
comment
@Kasra Да, спасибо, это точно та же проблема, с которой я столкнулся.   -  person Tapojyoti Mandal    schedule 11.06.2015
comment
@TapojyotiMandal Добро пожаловать! ;)   -  person kasravnd    schedule 11.06.2015


Ответы (7)


Модуль itertools быстро справляется с работой этой проблемы:

>>> from itertools import chain
>>> list(set(chain.from_iterable(d)))
[1, '41', '42', '43', '40', '34', '30', '44']

Другой способ сделать это — распаковать список в отдельные аргументы для union():

>>> list(set().union(*d))
[1, '41', '42', '43', '40', '34', '30', '44']

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

person Raymond Hettinger    schedule 11.06.2015
comment
Как обычно масштабируется itertools? По вашему опыту, может ли такая операция обрабатывать десятки или сотни миллионов длинных списков элементов («элементы» здесь являются строками)? Или даже крупнее? - person Hassan Baig; 24.09.2017
comment
Шаг chain.from_iterable() не зависит от масштаба. В любой момент времени все его состояние хранится всего в двух указателях на итераторы. Части set() и list() потребляют память пропорционально общему количеству уникальных входов. На моей 64-битной машине сто миллионов уникальных входных данных занимают 4,3 ГБ ОЗУ для объекта набора и 0,9 ГБ для объекта списка. - person Raymond Hettinger; 25.09.2017
comment
Вам лучше написать set.union(), так как set() инициализируется пустым набором. В данном случае это нормально, но я потратил много времени на поиск ошибок, потому что предположил, что это обобщает пересечение. С set. вы можете делать и объединение, и пересечение! - person Radio Controlled; 07.07.2020
comment
@RadioControlled: set().union(*d) работает с пустым d, что является более важным фактором, чем симметрия с тем, что вы сделали бы для перекрестка. - person user2357112 supports Monica; 04.01.2021
comment
Жаль, что все ответы отвечают на вопрос, отличный от заданного, возможно, из-за плохого выбора примера в вопросе. Похоже, что вопрос на самом деле требовал чего-то более близкого к алгоритму связанных компонентов гиперграфа, а не просто сбрасывать все в один набор. (ответ дермена немного отличается, но в итоге оказывается еще более неправильным.) - person user2357112 supports Monica; 04.01.2021

Используя оператор распаковки *:

>> list(set().union(*a))
[1, '44', '30', '42', '43', '40', '41', '34']

(Спасибо Raymond Hettinger и ShadowRanger за комментарии!)

(Обратите внимание, что

set.union(*tup)

распаковать в

set.union(tup[0], tup[1], ... tup[n - 1])

)

person Ami Tavory    schedule 11.06.2015
comment
Этот метод отлично работает. Спасибо. Не могли бы вы объяснить, для чего используется «*» в коде? Или, может быть, предоставить ссылку, где я мог бы изучить, связанный с этим, чтобы понять больше. - person Tapojyoti Mandal; 11.06.2015
comment
FWIW, шаг tuple не имеет никакого эффекта, потому что распаковка по звездочке работает на любом итерируемом объекте. Вы также можете заменить понимание списка на map(set, a). Результат сводится к list(set.union(*map(set, a))). - person Raymond Hettinger; 11.06.2015
comment
@TapojyotiMandal Смотрите объяснение в ответе. - person Ami Tavory; 11.06.2015
comment
Вы можете значительно сократить количество временных set, изменив set.union(*map(set, a)) на set().union(*a). Единственная причина, по которой map(set, была необходима, заключалась в том, что вы вызывали set.union, и первый аргумент становился собой, к которому он вызывался, но если вы делаете пустой set в качестве базы, union принимает произвольные итерации для остальных аргументов. - person ShadowRanger; 09.05.2019

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

import itertools

single_list_with_all_values = list(itertools.chain(*A))
single_list_with_all_values.sort()

print set(single_list_with_all_values)
person Arpit Goyal    schedule 11.06.2015
comment
Это очень хорошо. Однако несколько улучшений. 1) chain(*it) всегда следует менять на chain.from_iterable(it). 2) Нет необходимости sort(), потому что при создании набора теряется порядок. 3) Без сортировки нет необходимости преобразовывать в список перед созданием набора. С этими изменениями все сводится к set(chain.from_iterable(d)). - person Raymond Hettinger; 11.06.2015

In [20]: s
Out[20]: 
[[1, '34', '44'],
 [1, '40', '30', '41'],
 [1, '41', '40', '42'],
 [1, '42', '41', '43'],
 [1, '43', '42', '44'],
 [1, '44', '34', '43']]
In [31]: list({x for _list in s for x in _list})
Out[31]: [1, '44', '30', '42', '43', '40', '41', '34']

Обновлять:

Спасибо за комментарии

person Ajay    schedule 11.06.2015
comment
Вам не нужно понимание списка, так как конструктор множества может использовать генератор. - person Peter Wood; 11.06.2015
comment
@PeterWood OP попросил список, который будет его окончательным ответом - person Ajay; 11.06.2015
comment
Нет, понимание передается set. Вам это не нужно. - person Peter Wood; 11.06.2015
comment
Замена понимания списка на понимание множества сводит это к хорошему, чистому ответу: list({x for _list in s for x in _list}). - person Raymond Hettinger; 11.06.2015

>>> big = [[1, '34', '44'], [1, '40', '30', '41'], [1, '41', '40', '42'], [1, '42', '41', '43'], [1, '43', '42', '44'], [1, '44', '34', '43']]
>>> set(reduce ( lambda l,a : l + a, big))
set([1, '44', '30', '42', '43', '40', '41', '34'])

И если вам действительно нужен список списка в качестве конечного результата

>>>>[list(set(reduce ( lambda l,a : l + a, big)))]
[[1, '44', '30', '42', '43', '40', '41', '34']]

И если вам не нравится перекодировать лямбда-функцию для добавления в список:

>>>>[list(set(reduce ( list.__add__, big)))]
[[1, '44', '30', '42', '43', '40', '41', '34']]

EDIT: после вашей рекомендации использовать itertools.chain вместо list.__add__ я запустил timeit для обоих с исходной переменной, используемой исходным плакатом.

Кажется, что timeit list.__add__ около 2,8 с, а itertools.chain около 3,5 секунды.

Я проверил на этой странице, и да, вы были правы с тем, что itertools.chain содержит метод from_iterable, который дает огромный прирост производительности. см. ниже список.__add__, itertools.chain и itertools.chain.from_iterable.

>>> timeit.timeit("[list(set(reduce ( list.__add__, big)))]", setup="big = [ [10,20,30,40] for ele in range(10000)]", number=30)
16.051744650801993
>>> timeit.timeit("[list(set(reduce ( itertools.chain, big)))]", setup="big = [ [10,20,30,40] for ele in range(10000)]", number=30)
54.721315866467194
>>> timeit.timeit("list(set(itertools.chain.from_iterable(big)))", setup="big = [ [10,20,30,40] for ele in range(10000)]", number=30)
0.040056066849501804

Большое спасибо за ваши советы :)

person Azurtree    schedule 11.06.2015
comment
Сложение списков вместе, как это, является неэффективной операцией O(n**2) и почти всегда является плохой идеей. Пожалуйста, используйте itertools.chain вместо этого. - person Raymond Hettinger; 11.06.2015

from functools import reduce

out = list(reduce(set.union, iterable))

пока хотя бы первый элемент iterable является набором. Иначе,

out = list(reduce(set.union, iterable[1:], set(iterable[0])))
person PeterFoster    schedule 28.04.2020

Протестировано только с python 2: мне лично нравится читабельность reduce в сочетании с простой условной функцией, что-то вроде

# PYTHON 2 ONLY!
somelists = [[1, '41', '40', '42'], [1, '42', '41', '43'], [1, '43', '42', '44'], [1, '44', '34', '43']] # your original lists
somesets = map(set,somelists) #your lists as sets

def condition(s1,s2): # condition to apply recursively to the sets
    if s1.intersection(s2):
        return s1.union(s2)
reduce( condition,somesets)
#{1, '30', '34', '40', '41', '42', '43', '44'}

Конечно, вы можете привести этот результат к 2d-списку, если хотите list([reduce(...

Отмечу, что это примерно в 3 раза медленнее, чем ответ chain.fromiterable.

person dermen    schedule 30.07.2015