from pyrsistent import pset, s #It is good idea to copy what you obrain. Otherwise something like this is possible # M = {1, 2, 3} # Relation = get_relation(M) # R = Relation() # R = R.add(1,3) # M.remove(3) def get_relation(base_set): bset = frozenset(base_set) # One of the things that were intentionaly left open was checking if elements # and relations are compatible. You either could do it or not. You should, # however, be consistent with the approach you choose. # For those who decided to do something multiline one needed to avoid code # repetition (e.g. if you used asserts it was just one line and no # simplification was really possible). You should write a function # performing these repeated checks. Another posibility which I used is # a decorator to perform the checks. I had done parametrized decorators, # which might be some overengineering as we always call the decorators with # the same parameters, hovewer, KI feel that this way it is # 1. slightly more educational regarding the use of decorators # 2. the usage of the decorators is slightly easier to understand def check_relations_base_set_equality(rel2pos): def decorator(f): def checked_f(*args, **kwargs): # If your function has exceptional cases, its standard to # deal with them as # if condition: # solve exceptional case # do regular stuff # and not do do it the other way. if bset is not args[rel2pos]._base_set: raise TypeError return f(*args, **kwargs) return checked_f return decorator def check_element(pos1, pos2): def decorator(f): def checked_f(*args, **kwargs): nonlocal bset if not {args[pos1], args[pos2]} <= bset: raise TypeError return f(*args, **kwargs) return checked_f return decorator # Many of you made an error of only have one Relation class with static variable # for the set. This is quite bad as if you do # R1 = get_relation({0,1}) # R2 = get_relation({0,1,2}) # R1 is now suprisingly a relation on {0,1,2}. # The question that was open was if the returned relation is a singleton, that is # if tou do # R1 = get_relation({0,1,2}) # R2 = get_relation({0,1,2}) # if classes R1 and R2 are the same. I decided not to do this. # If I wanted to do this I would do a class singleton class that has a method # that stores and returns relation classes and takes are that for each set only # one class exists. class Relation: _base_set = bset #The second parameter is not public. In case you decided it is public #I would use self._elements = pset(_set). #This costs nothing if we get pset, but saves the day if not. def __init__(self, _set = s()): self._elements = _set @check_element(1,2) def contains(self, element1, element2): return (element1, element2) in self._elements @check_element(1,2) def add(self, element1, element2): return Relation(self._elements | {(element1, element2)}) @check_relations_base_set_equality(1) def intersection(self, other): return Relation(self._elements & other._elements) @check_relations_base_set_equality(1) def union(self, other): return Relation(self._elements | other._elements) @check_relations_base_set_equality(1) def substract(self, other): return Relation(self._elements - other._elements) def inverse(self): return Relation(pset((x,y) for y,x in self._elements)) @check_relations_base_set_equality(1) def compose(self, other): res = {(a, d) for a,b in self._elements for c,d in other._elements if b==c} return Relation(pset(res)) def is_reflexive(self): nonlocal bset return all(self.contains(x,x) for x in bset) def is_symetric(self): return all(self.contains(x,y) for y,x in self._elements) def is_transitive(self): return all(self.contains(a,d) for a,b in self._elements for c,d in self._elements if b==c) def rt_closure(self): nonlocal bset previous = Relation(pset((x, x) for x in bset)) now = self.union(previous) while len(previous._elements) != len(now._elements): previous, now = now, now.union(now.compose(now)) return now return Relation # Comments regarding work with dictionaries. I saw a lot of this # for key in dict1: # if key not in dict2: # dict2[key] = [] # for item in dict1[key]: # if not dict2(key, item): # dict2[key].append(item) # When it should actually look like this # for key, value in dict1.items: # dict2.key = dict2.get(key, {}) + value # If you insist on using list you should write a simple function # to perform union. # You could also use defaultdict or something similar, you just # needed to realize that this is way more complex than it should # be and google something. if __name__ == "__main__": Relation = get_relation({1,2,3}) P = Relation() R = Relation() P = P.add(2,3) R = R.add(1,2) S1 = P.union(R) S2 = P.intersection(R) print(list(S1._elements)) print(list(S2._elements)) print(list(S1.inverse()._elements)) print(P.is_transitive()) print(S1.is_transitive()) print(list(S1.rt_closure()._elements))