Eu tenho uma lista de conjuntos:
setlist = [s1,s2,s3...]
Quero s1 ∩ s2 ∩ s3 ...
Eu posso escrever uma função para fazer isso executando uma série de pares s1.intersection(s2)
, etc.
Existe uma maneira recomendada, melhor ou integrada?
Eu tenho uma lista de conjuntos:
setlist = [s1,s2,s3...]
Quero s1 ∩ s2 ∩ s3 ...
Eu posso escrever uma função para fazer isso executando uma série de pares s1.intersection(s2)
, etc.
Existe uma maneira recomendada, melhor ou integrada?
Respostas:
No Python versão 2.6, você pode usar vários argumentos para set.intersection()
, como
u = set.intersection(s1, s2, s3)
Se os conjuntos estiverem em uma lista, isso se traduzirá em:
u = set.intersection(*setlist)
onde *a_list
está a expansão da lista
Observe que nãoset.intersection
é um método estático, mas isso usa a notação funcional para aplicar a interseção do primeiro conjunto com o restante da lista. Portanto, se a lista de argumentos estiver vazia, isso falhará.
A partir da versão 2.6, set.intersection
leva arbitrariamente muitas iteráveis.
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s3 = set([2, 4, 6])
>>> s1 & s2 & s3
set([2])
>>> s1.intersection(s2, s3)
set([2])
>>> sets = [s1, s2, s3]
>>> set.intersection(*sets)
set([2])
Claramente set.intersection
é o que você quer aqui, mas, se você precisar de uma generalização de "pegue a soma de tudo isso", "pegue o produto de todos esses", "pegue o xor de todos esses", o que você está procurando é o reduce
função:
from operator import and_
from functools import reduce
print(reduce(and_, [{1,2,3},{2,3,4},{3,4,5}])) # = {3}
ou
print(reduce((lambda x,y: x&y), [{1,2,3},{2,3,4},{3,4,5}])) # = {3}
Se você não possui o Python 2.6 ou superior, a alternativa é escrever um loop for explícito:
def set_list_intersection(set_list):
if not set_list:
return set()
result = set_list[0]
for s in set_list[1:]:
result &= s
return result
set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print set_list_intersection(set_list)
# Output: set([1])
Você também pode usar reduce
:
set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print reduce(lambda s1, s2: s1 & s2, set_list)
# Output: set([1])
No entanto, muitos programadores de Python não gostam disso, incluindo o próprio Guido :
Há cerca de 12 anos, o Python adquiriu lambda, reduza (), filtre () e mapeie (), cortesia de (acredito) um hacker do Lisp que os perdeu e enviou os patches de trabalho. Mas, apesar do valor de RP, acho que esses recursos devem ser cortados do Python 3000.
Então agora reduza (). Na verdade, esse é o que eu mais odiei, porque, além de alguns exemplos envolvendo + ou *, quase sempre que vejo uma chamada de redução () com um argumento de função não trivial, preciso pegar papel e caneta para diagrama o que realmente está sendo alimentado nessa função antes que eu entenda o que o reduzir () deve fazer. Então, na minha opinião, a aplicabilidade de reduzir () é praticamente limitada aos operadores associativos e, em todos os outros casos, é melhor escrever explicitamente o ciclo de acumulação.
result
estiver vazio.
Aqui, estou oferecendo uma função genérica para interseção de vários conjuntos, tentando aproveitar o melhor método disponível:
def multiple_set_intersection(*sets):
"""Return multiple set intersection."""
try:
return set.intersection(*sets)
except TypeError: # this is Python < 2.6 or no arguments
pass
try: a_set= sets[0]
except IndexError: # no arguments
return set() # return empty set
return reduce(a_set.intersection, sets[1:])
Guido pode não gostar reduce
, mas eu meio que gosto disso :)
sets
vez de tentar acessar sets[0]
e capturar o arquivo IndexError
.
a_set
é usado no retorno final.
return reduce(sets[0], sets[1:]) if sets else set()
?
try
/ except
deve ser evitado, se possível. É um cheiro de código, é ineficiente e pode esconder outros problemas.
reduce
é "limitado a operadores associativos", o que é aplicável neste caso.reduce
muitas vezes é difícil descobrir, mas&
não é tão ruim.