from collections import defaultdict
class IndexedSet:
def __init__ ( self ) :
self ._elements = set ( )
self ._indices = set ( )
def add( self , element) :
self ._elements.add ( element)
for index in self ._indices:
index.add ( element)
def remove( self , element) :
self ._elements.remove ( element)
for index in self ._indices:
index.remove ( element)
def index( self , key) :
index = Index( key)
for element in self ._elements:
index.add ( element)
self ._indices.add ( index)
return index
class Index:
def __init__ ( self , key) :
self ._key = key
self ._elements = defaultdict( set )
def add( self , element) :
self ._elements[ self ._key( element) ] .add ( element)
def remove( self , element) :
self ._elements[ self ._key( element) ] .remove ( element)
if len ( self ._elements[ self ._key( element) ] ) == 0 :
del self ._elements[ self ._key( element) ]
def find( self , key) :
return self ._elements[ key]
# example:
from operator import attrgetter
class User:
def __init__ ( self , id , email_address, password_hash) :
self .id = id
self .email_address = email_address
self .password_hash = password_hash
@ property
def _tuple( self ) :
return self .id , self .email_address , self .password_hash
def __eq__ ( self , other) :
return isinstance ( other, User) and self ._tuple == other._tuple
def __hash__ ( self ) :
return hash ( self ._tuple)
users = IndexedSet( )
id_index = users.index ( attrgetter( 'id' ) )
email_address_index = users.index ( attrgetter( 'email_address' ) )
users.add ( User( 1 , 'rightfold@gmail.com' , b'abcd' ) )
users.add ( User( 2 , 'henk@example.com' , b'defg' ) )
print ( id_index.find ( 1 ) )
print ( id_index.find ( 2 ) )
print ( id_index.find ( 3 ) )
print ( email_address_index.find ( 'rightfold@gmail.com' ) )
print ( email_address_index.find ( 'henk@example.com' ) )
print ( email_address_index.find ( 'foo@bar.baz' ) )
ZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgZGVmYXVsdGRpY3QKCmNsYXNzIEluZGV4ZWRTZXQ6CglkZWYgX19pbml0X18oc2VsZik6CgkJc2VsZi5fZWxlbWVudHMgPSBzZXQoKQoJCXNlbGYuX2luZGljZXMgPSBzZXQoKQoKCWRlZiBhZGQoc2VsZiwgZWxlbWVudCk6CgkJc2VsZi5fZWxlbWVudHMuYWRkKGVsZW1lbnQpCgkJZm9yIGluZGV4IGluIHNlbGYuX2luZGljZXM6CgkJCWluZGV4LmFkZChlbGVtZW50KQoKCWRlZiByZW1vdmUoc2VsZiwgZWxlbWVudCk6CgkJc2VsZi5fZWxlbWVudHMucmVtb3ZlKGVsZW1lbnQpCgkJZm9yIGluZGV4IGluIHNlbGYuX2luZGljZXM6CgkJCWluZGV4LnJlbW92ZShlbGVtZW50KQoKCWRlZiBpbmRleChzZWxmLCBrZXkpOgoJCWluZGV4ID0gSW5kZXgoa2V5KQoJCWZvciBlbGVtZW50IGluIHNlbGYuX2VsZW1lbnRzOgoJCQlpbmRleC5hZGQoZWxlbWVudCkKCQlzZWxmLl9pbmRpY2VzLmFkZChpbmRleCkKCQlyZXR1cm4gaW5kZXgKCmNsYXNzIEluZGV4OgoJZGVmIF9faW5pdF9fKHNlbGYsIGtleSk6CgkJc2VsZi5fa2V5ID0ga2V5CgkJc2VsZi5fZWxlbWVudHMgPSBkZWZhdWx0ZGljdChzZXQpCgoJZGVmIGFkZChzZWxmLCBlbGVtZW50KToKCQlzZWxmLl9lbGVtZW50c1tzZWxmLl9rZXkoZWxlbWVudCldLmFkZChlbGVtZW50KQoKCWRlZiByZW1vdmUoc2VsZiwgZWxlbWVudCk6CgkJc2VsZi5fZWxlbWVudHNbc2VsZi5fa2V5KGVsZW1lbnQpXS5yZW1vdmUoZWxlbWVudCkKCQlpZiBsZW4oc2VsZi5fZWxlbWVudHNbc2VsZi5fa2V5KGVsZW1lbnQpXSkgPT0gMDoKCQkJZGVsIHNlbGYuX2VsZW1lbnRzW3NlbGYuX2tleShlbGVtZW50KV0KCglkZWYgZmluZChzZWxmLCBrZXkpOgoJCXJldHVybiBzZWxmLl9lbGVtZW50c1trZXldCgojIGV4YW1wbGU6CmZyb20gb3BlcmF0b3IgaW1wb3J0IGF0dHJnZXR0ZXIKCmNsYXNzIFVzZXI6CglkZWYgX19pbml0X18oc2VsZiwgaWQsIGVtYWlsX2FkZHJlc3MsIHBhc3N3b3JkX2hhc2gpOgoJCXNlbGYuaWQgPSBpZAoJCXNlbGYuZW1haWxfYWRkcmVzcyA9IGVtYWlsX2FkZHJlc3MKCQlzZWxmLnBhc3N3b3JkX2hhc2ggPSBwYXNzd29yZF9oYXNoCgoJQHByb3BlcnR5CglkZWYgX3R1cGxlKHNlbGYpOgoJCXJldHVybiBzZWxmLmlkLCBzZWxmLmVtYWlsX2FkZHJlc3MsIHNlbGYucGFzc3dvcmRfaGFzaAoKCWRlZiBfX2VxX18oc2VsZiwgb3RoZXIpOgoJCXJldHVybiBpc2luc3RhbmNlKG90aGVyLCBVc2VyKSBhbmQgc2VsZi5fdHVwbGUgPT0gb3RoZXIuX3R1cGxlCgoJZGVmIF9faGFzaF9fKHNlbGYpOgoJCXJldHVybiBoYXNoKHNlbGYuX3R1cGxlKQoKdXNlcnMgPSBJbmRleGVkU2V0KCkKaWRfaW5kZXggPSB1c2Vycy5pbmRleChhdHRyZ2V0dGVyKCdpZCcpKQplbWFpbF9hZGRyZXNzX2luZGV4ID0gdXNlcnMuaW5kZXgoYXR0cmdldHRlcignZW1haWxfYWRkcmVzcycpKQp1c2Vycy5hZGQoVXNlcigxLCAncmlnaHRmb2xkQGdtYWlsLmNvbScsIGInYWJjZCcpKQp1c2Vycy5hZGQoVXNlcigyLCAnaGVua0BleGFtcGxlLmNvbScsIGInZGVmZycpKQpwcmludChpZF9pbmRleC5maW5kKDEpKQpwcmludChpZF9pbmRleC5maW5kKDIpKQpwcmludChpZF9pbmRleC5maW5kKDMpKQpwcmludChlbWFpbF9hZGRyZXNzX2luZGV4LmZpbmQoJ3JpZ2h0Zm9sZEBnbWFpbC5jb20nKSkKcHJpbnQoZW1haWxfYWRkcmVzc19pbmRleC5maW5kKCdoZW5rQGV4YW1wbGUuY29tJykpCnByaW50KGVtYWlsX2FkZHJlc3NfaW5kZXguZmluZCgnZm9vQGJhci5iYXonKSk=