2012-05-30 17 views
5

Por lo tanto, existe una manera fácil de calcular la intersección de dos conjuntos a través de set.intersection(). Sin embargo, tengo el siguiente problema:.Intersección del conjunto personalizado de Python

class Person(Object):      
    def __init__(self, name, age):              
     self.name = name                 
     self.age = age                 

l1 = [Person("Foo", 21), Person("Bar", 22)]            
l2 = [Person("Foo", 21), Person("Bar", 24)]            

union_list = list(set(l1).union(l2))           
# [Person("Foo", 21), Person("Bar", 22), Person("Bar", 24)] 

(Object es una clase base proporcionado por mi ORM que implementa básica __hash__ y __eq__ funcionalidad, que esencialmente se suma a todos los miembros de la clase a la picadillo En otras palabras, la __hash__ devuelta será un hash de cada elemento de la clase)

en esta etapa, me gustaría realizar una operación de intersección de conjuntos sólo .name, para encontrar, por ejemplo, Person('Bar', -1).intersection(union_list) #= [Person("Bar", -1), Person("Bar", 22), Person("Bar", 24)]. (La típica .intersection() en este punto no me daría nada, no puedo anular __hash__ o __eq__ en la clase Person, ya que esto anularía la unión conjunto original (I que)

¿Cuál es la mejor manera de hacerlo esto en Python 2.x

EDIT:? Tenga en cuenta que la solución no tienen que depender de un set sin embargo, tengo que encontrar uniones y luego intersecciones, lo que parece que esta es susceptible de un conjunto. (¡Pero estoy dispuesto a aceptar soluciones que utilicen la magia que consideren digna, siempre que resuelva mi problema!)

+0

No entiendo el resultado deseado. ¿Podrías * explicar * qué debería contener el resultado? –

+0

Err mierda, que debe ser .union, not .intersection. He actualizado la pregunta. ¿Me permite saber si esto es más claro? –

+0

Todavía estoy un poco confundido ya que el código de ejemplo no tiene el resultado que usted reclama. –

Respuesta

1

Odio responder a mis propias preguntas, por lo que me abstendré de marcar esto como la 'respuesta' por un tiempo.

Resulta que la manera de hacer esto es como sigue:

import types 
p = Person("Bar", -1) 
new_hash_method = lambda obj: hash(obj.name) 
p.__hash__ = types.MethodType(new_hash_method, p) 
for i in xrange(0, len(union_list)): 
    union_list[i].__hash__ = types.MethodType(new_hash_method, union_list[i]) 
set(union_list).intersection(p) 

Es ciertamente sucio y que se basa en types.MethodType, pero es menos intensa que la mejor solución propuesta hasta ahora (solución de glglgl) como mi real union_list puede contener potencialmente en el orden de miles de elementos, por lo que esto me ahorrará la recreación de objetos cada vez que ejecuto este procedimiento de intersección.

+0

¿Esto realmente funciona? La documentación indica que los métodos mágicos como '__hash__' se buscan en la clase, no en la instancia. https://docs.python.org/3/reference/datamodel.html#special-lookup –

+0

En realidad, parece que funciona para las clases de estilo antiguo, pero no para las nuevas clases de estilo: https://docs.python.org /2/reference/datamodel.html#special-method-lookup-for-old-style-classes –

0

Deberá sobrescribir __hash__ y los métodos de comparación si desea usar conjuntos como este.

Si no lo hace, entonces

Person("Foo", 21) == Person("Foo", 21) 

siempre habrá falsa.

Si sus objetos son gestionados por un ORM, entonces tendrá que comprobar cómo se comparan los objetos. Por lo general, solo mira la identificación de los objetos y la comparación solo funciona si se administran ambos objetos. Si intenta comparar un objeto que obtuvo del ORM con una instancia que creó usted mismo antes de que persista en la base de datos, es probable que sean diferentes. De todos modos, un ORM no debería tener problemas con el suministro de su propia lógica de comparación.

Pero si por alguna razón no puede anular __hash__ y __eq__, entonces no puede usar conjuntos para intersección y unión con los objetos originales. Usted podría:

  • calcular la intersección/Unión mismo
  • crear una clase contenedora que es comparable:

    class Person:      
        def __init__(self, name, age):              
         self.name = name                 
         self.age = age                 
    
    l1 = [Person("Foo", 21), Person("Bar", 22)]            
    l2 = [Person("Foo", 21), Person("Bar", 24)]            
    
    class ComparablePerson: 
        def __init__(self, person): 
         self.person = person 
    
        def __hash__(self): 
         return hash(self.person.name) + 31*hash(self.person.age) 
    
        def __eq__(self, other): 
         return (self.person.name == other.person.name and 
           self.person.age == other.person.age) 
        def __repr__(self): 
         return "<%s - %d>" % (self.person.name, self.person.age) 
    
    c1 = set(ComparablePerson(p) for p in l1) 
    c2 = set(ComparablePerson(p) for p in l2) 
    
    print c1 
    print c2 
    print c1.union(c2) 
    print c2.intersection(c1) 
    
+1

Ver mi comentario (sobre la pregunta original); la anulación ya es tratada por un ORM. Actualizaré la pregunta para reflejar esto. –

0

Ésta es torpe, pero ...

set(p for p in union_list for q in l2 if p.name == q.name and p.age != q.age) | (set(p for p in l2 for q in union_list if p.name == q.name and p.age != q.age)) 
# {person(name='Bar', age=22), person(name='Bar', age=24)} 
5

Suena como

>>> class Person: 
...  def __init__(self, name, age): 
...   self.name = name 
...   self.age = age 
...  def __eq__(self, other): 
...   return self.name == other.name 
...  def __hash__(self): 
...   return hash(self.name) 
...  def __str__(self): 
...   return self.name 
... 
>>> l1 = [Person("Foo", 21), Person("Bar", 22)] 
>>> l2 = [Person("Foo", 21), Person("Bar", 24)] 
>>> union_list = list(set(l1).union(l2)) 
>>> [str(l) for l in union_list] 
['Foo', 'Bar'] 

es lo que quiere, ya que name es su clave única?

+0

Ah, no, el ORM que estoy usando ya proporciona un método __eq__ y __hash__ (y, como tal, set.union() ya produce resultados 'sanos'). Estoy buscando una forma de hacer una operación de intersección que * solo * use uno de los miembros de la clase como la clave, y como tal no puede anular '__hash__' o' __eq__'. –

+0

Ya veo, ¿entonces quizás la solución de glglgl sería adecuada? –

1

Si desea que el age a ser irrelevante con respecto a la comparación, debe reemplazar __hash__() y __eq__() en Person a pesar de que lo tiene en su Object.

Si necesita este comportamiento sólo en esto (y semejantes) contextos, se puede crear un objeto contenedor que contiene el Person y se comporta de manera diferente, como

class PersonWrapper(Object): 
    def __init__(self, person): 
     self.person = person 
    def __eq__(self, other): 
     if hasattr(other, 'person'): 
      return self.person.name == other.person.name 
     else: 
      return self.person.name == other.name 
    def __hash__(self): 
     return hash(self.person.name) 

y luego hacer

union_list = list(set(PersonWrapper(i) for i in l1).union(PersonWrapper(i) for i in l2)) 
# [Person("Foo", 21), Person("Bar", 22), Person("Bar", 24)] 

(no probado)

+0

El problema es que necesito los métodos '__hash__' y' __eq__' tal como están, de lo contrario '.union()' no funcionará de la forma en que lo hace. –

+0

Hmm, interesante. Entonces, ¿no hay forma de hacer esto sin reconstruir objetos? Sé que C++ me da la opción de pasar un comparador personalizado; Python no tiene la misma habilidad? –

+0

Hay una manera de hacerlo con funciones como 'sorted()' donde se puede dar una función 'cmp' así como una función' key', pero no con 'set's, alas ... – glglgl

1

¿Qué tal:

d1 = {p.name:p for p in l1} 
d2 = {p.name:p for p in l2} 

intersectnames = set(d1.keys()).intersection(d2.keys) 
intersect = [d1[k] for k in intersectnames] 

Podría ser más rápido para lanzar intersectnames a su ORM, en cuyo caso no se podía construir diccionarios, simplemente recoger nombres en las listas.

Cuestiones relacionadas