web-dev-qa-db-fra.com

Union de plusieurs ensembles dans python

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

J'ai une liste de listes. Mon objectif est de vérifier si une sous-liste a quelque chose en commun avec d'autres sous-listes (à l'exclusion du premier objet d'index à comparer). S'il a quelque chose en commun, unifiez ces sous-listes.

Par exemple, pour cet exemple, ma réponse finale devrait être quelque chose comme:

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

Je peux comprendre que je devrais convertir les sous-listes en ensembles, puis utiliser les opérations union () et intersection (). Mais ce qui me pose problème, c'est de comparer chaque ensemble/sous-liste. Je ne peux pas exécuter une boucle sur la liste et comparer chaque sous-liste une par une car le contenu de la liste serait modifié et cela entraînerait une erreur.

Ce que je veux savoir, est-ce qu'il existe une méthode efficace pour comparer toutes les sous-listes (converties en ensembles) et en obtenir l'union?

21
Tapojyoti Mandal

Le module itertools résout ce problème:

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

Une autre façon de le faire est de décompresser la liste en arguments séparés pour union () :

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

Cette dernière façon élimine tous les doublons et ne nécessite pas que les entrées soient d'abord converties en ensembles. De plus, il ne nécessite pas d'importation.

35
Raymond Hettinger

Utilisation de l'opérateur déballage * :

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

(Merci Raymond Hettinger pour le commentaire!)

(Notez que

set.union(*tup)

déballera à

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

)

21
Ami Tavory

Personnellement, j'aime la lisibilité de reduce, associée à une simple fonction conditionnelle, quelque chose comme

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'}

Bien sûr, vous pouvez lancer ce résultat dans une liste 2D si vous le souhaitez list([reduce(...

Je noterai que c'est quelque chose comme 3 fois plus lent que le chain.fromiterable répondre.

2
dermen
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']

Mettre à jour:

Merci pour les commentaires

1
Ajay

Vous pouvez utiliser itertools pour effectuer cette action. Supposons que votre liste ait un nom de variable 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)
1
Arpit Goyal
>>> 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'])

Et si vous voulez vraiment une liste d'une liste comme résultat final

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

Et si vous n'aimez pas recoder une fonction lambda pour l'ajout de liste:

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

MODIFIER : après votre recommandation d'utiliser itertools.chain au lieu de list .__ add__ J'ai exécuté un timeit pour les deux avec la variable d'origine utilisée par l'affiche originale.

Il semble que timeit times list .__ add__ environ 2,8 s et itertools.chain environ 3,5 secondes.

J'ai vérifié sur cette page et oui, vous aviez raison avec l'itertools.chain contient une méthode from_iterable qui accorde une énorme amélioration des performances. voir ci-dessous avec la liste .__ add__, itertools.chain et 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

Merci beaucoup pour vos conseils :)

1
Azurtree