2010-04-07 25 views
10

Estoy tratando de crear un objeto personalizado que se comporte correctamente en las operaciones de conjunto.Comportamiento del objeto en operaciones de conjunto

Generalmente lo tengo funcionando, pero quiero asegurarme de que entiendo completamente las implicaciones. En particular, estoy interesado en el comportamiento cuando hay datos adicionales en el objeto que no están incluidos en los métodos de igualdad/hash. Parece que en la operación 'intersección', devuelve el conjunto de objetos que se están comparando, donde las operaciones 'unión' devuelve el conjunto de objetos que se están comparando.

Para ilustrar:

class MyObject: 
    def __init__(self,value,meta): 
     self.value = value 
     self.meta = meta 
    def __eq__(self,other): 
     return self.value == other.value 
    def __hash__(self): 
     return hash(self.value) 

a = MyObject('1','left') 
b = MyObject('1','right') 
c = MyObject('2','left') 
d = MyObject('2','right') 
e = MyObject('3','left') 
print a == b # True 
print a == C# False 

for i in set([a,c,e]).intersection(set([b,d])): 
    print "%s %s" % (i.value,i.meta) 
#returns: 
#1 right 
#2 right 

for i in set([a,c,e]).union(set([b,d])): 
    print "%s %s" % (i.value,i.meta) 
#returns: 
#1 left 
#3 left 
#2 left 

¿Es este comportamiento documentado en alguna parte y determinista? Si es así, ¿cuál es el principio rector?

Respuesta

4

No, no es determinista. El problema es que has roto invariantes de igual y de hash, que dos objetos son equivalentes cuando son iguales. Arregle su objeto, no intente ser inteligente y abuse de cómo funciona la implementación del conjunto. Si el metavalor es parte de la identidad de MyObject, debe incluirse en eq y hash.

No puede confiar en que la intersección del conjunto siga cualquier orden, por lo que no hay forma de hacer lo que desea con facilidad. Lo que terminaría haciendo es tomar la intersección solo por valor, luego mirar a través de todos sus objetos por uno más antiguo para reemplazarlo, para cada uno. No hay una buena manera de hacerlo algorítmicamente.

Los sindicatos no son tan malas:

##fix the eq and hash to work correctly 
class MyObject: 
    def __init__(self,value,meta): 
     self.value = value 
     self.meta = meta 
    def __eq__(self,other): 
     return self.value, self.meta == other.value, other.meta 
    def __hash__(self): 
     return hash((self.value, self.meta)) 
    def __repr__(self): 
     return "%s %s" % (self.value,self.meta) 

a = MyObject('1','left') 
b = MyObject('1','right') 
c = MyObject('2','left') 
d = MyObject('2','right') 
e = MyObject('3','left') 

union = set([a,c,e]).union(set([b,d])) 
print union 
#set([2 left, 2 right, 1 left, 3 left, 1 right]) 

##sort the objects, so that older objs come before the newer equivalents 
sl = sorted(union, key= lambda x: (x.value, x.meta)) 
print sl 
#[1 left, 1 right, 2 left, 2 right, 3 left] 
import itertools 
##group the objects by value, groupby needs the objs to be in order to do this 
filtered = itertools.groupby(sl, lambda x: x.value) 
##make a list of the oldest (first in group) 
oldest = [ next(group) for key, group in filtered] 
print oldest 
#[1 left, 2 left, 3 left] 
+0

En cuanto a la documentación para el método __hash__, no parece indicar que no puede haber datos en el objeto que no es hash. Puedo pensar en muchos ejemplos donde 2 objetos que son equivalentes tienen alguna forma de metadatos (una marca de tiempo o un nombre de archivo, tal vez) que son diferentes. De los documentos para __hash__: la única propiedad requerida es que los objetos que se comparan por igual tengan el mismo valor hash; se recomienda mezclar de alguna manera (por ejemplo, utilizando exclusivo o) los valores hash para los componentes del objeto que también desempeñan un papel en la comparación de los objetos. –

+1

Estoy confundido por su comentario, parece que está de acuerdo conmigo. Si un objeto tiene metadatos (como una marca de tiempo o un nombre de archivo) que son ignorados por eq y hash, entonces no son lo suficientemente importantes como para que los guarden, ya sea que los comparen los objetos. Si fueran lo suficientemente importantes como para distinguir los dos objetos, se incluirían en el algoritmo hash y eq. ¿Qué estás preguntando en este punto? – hlfrk414

+0

No estoy de acuerdo con usted;). Solo trato de entender cómo se pueden usar estas características. En este caso, un agente de supervisión crea objetos. Intentando correlacionar las condiciones de alerta recurrentes, que tienen diferentes marcas de tiempo. Preferiría conservar los objetos más antiguos, pero por supuesto puedo implementarlo de muchas otras maneras, porque sospecho que tienes razón. –

1

Orden no parece que importa:

>>> [ (u.value, u.meta) for u in set([b,d]).intersection(set([a,c,e])) ] 
[('1', 'right'), ('2', 'right')] 

>>> [ (u.value, u.meta) for u in set([a,c,e]).intersection(set([b,d])) ] 
[('1', 'right'), ('2', 'right')] 

Sin embargo, si usted hace esto:

>>> f = MyObject('3', 'right') 

y añadir a f el conjunto "correcto":

>>> [ (u.value, u.meta) for u in set([a,c,e]).intersection(set([b,d,f])) ] 
[('1', 'right'), ('3', 'right'), ('2', 'right')] 

>>> [ (u.value, u.meta) for u in set([b,d,f]).intersection(set([a,c,e])) ] 
[('1', 'left'), ('3', 'left'), ('2', 'left')] 

Para que pueda ver que el comportamiento depende del tamaño de los conjuntos (el mismo efecto ocurre si union). Puede depender de otros factores también. Creo que estás buscando la fuente python si quieres saber por qué.

0

Digamos que los objetos tienen dos tipos diferentes de atributos: clave atributos y datos atributos. En su ejemplo, MyObject.value es un atributo clave.

Almacene todos sus objetos como valores en un diccionario, marcados con los atributos clave, asegurándose de que solo se ingresen en su diccionario los que prefiera (por ejemplo, con la marca de tiempo más antigua). Realizar las operaciones de conjunto con la misma clave que se utiliza en el diccionario, y recuperar los objetos reales del diccionario:

result= [dict1[k] for k in set_operation_result] 
Cuestiones relacionadas