# kjbuckets in pure python ### needs more thorough testing! #import sys # for debug def kjtabletest(x): #print "kjtabletest" try: return x.is_kjtable except: return 0 unhashable = "unhashable key error" class kjGraph: is_kjtable = 1 def __init__(self, *args): #print "kjGraph.__init__", args key_to_list = self.key_to_list = {} self.dirty = 0 self.hashed = None #print args if args: if len(args)>1: raise ValueError, "only 1 or 0 argument supported" from types import IntType, ListType, TupleType arg = args[0] targ = type(arg) test = key_to_list.has_key if type(arg) is IntType: return # ignore int initializer (presize not implemented) elif type(arg) is ListType or type(arg) is TupleType: for (x,y) in arg: if test(x): key_to_list[x].append(y) else: key_to_list[x] = [y] return aclass = arg.__class__ if aclass is kjGraph: aktl = arg.key_to_list for k in aktl.keys(): key_to_list[k] = aktl[k][:] return if aclass is kjDict or aclass is kjSet: adict = arg.dict for k in adict.keys(): key_to_list[k] = [ adict[k] ] return raise ValueError, "arg for kjGraph must be tuple, list, or kjTable" def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self.items()) def _setitems(self, thing): #print "kjGraph._setitem", thing #print "setitems", thing if self.hashed is not None: raise ValueError, "table has been hashed, it is immutable" try: for (k,v) in thing: #print k,v, "going" #inlined __setitem__ try: klist = self.key_to_list[k] #print "klist gotten" except KeyError: try: klist = self.key_to_list[k] = [] except TypeError: raise unhashable if v not in klist: klist.append(v) except (TypeError, KeyError): #print sys.exc_type, sys.exc_value if kjtabletest(thing): self._setitems(thing._pairs()) self.dirty = thing.dirty else: raise ValueError, "cannot setitems with %s" % type(thing) except unhashable: raise TypeError, "unhashable type" def __setitem__(self, item, value): ktl = self.key_to_list if ktl.has_key(item): l = ktl[item] if value not in l: l.append(value) else: ktl[item] = [value] def __getitem__(self, item): return self.key_to_list[item][0] def __delitem__(self, item): self.dirty = 1 del self.key_to_list[item] def choose_key(self): return self.key_to_list.keys()[0] def _pairs(self, justtot=0): myitems = self.key_to_list.items() tot = 0 for (k, v) in myitems: tot = tot + len(v) if justtot: return tot else: result = [None]*tot i = 0 for (k,v) in myitems: for x in v: result[i] = (k,x) i = i+1 return result def __len__(self): v = self.key_to_list.values() lv = map(len, v) from operator import add return reduce(add, lv, 0) def items(self): return self._pairs() def values(self): v = self.key_to_list.values() from operator import add tot = reduce(add, map(len, v), 0) result = [None] * tot count = 0 for l in v: next = count + len(l) result[count:next] = l count = next return result def keys(self): return self.key_to_list.keys() def member(self, k, v): ktl = self.key_to_list if ktl.has_key(k): return v in ktl[k] return 0 _member = member # because member redefined for kjSet def add(self, k, v): ktl = self.key_to_list if ktl.has_key(k): l = ktl[k] if v not in l: l.append(v) else: ktl[k] = [v] def delete_arc(self, k, v): self.dirty = 1 if self.hashed is not None: raise ValueError, "table has been hashed, it is immutable" try: l = self.key_to_list[k] i = l.index(v) del l[i] if not l: del self.key_to_list[k] except: raise KeyError, "not in table"# % (k,v) def has_key(self, k): return self.key_to_list.has_key(k) def subset(self, other): oc = other.__class__ if oc is kjGraph: oktl = other.key_to_list sktl = self.key_to_list otest = oktl.has_key for k in sktl.keys(): if otest(k): l = sktl[k] ol = oktl[k] for x in l: if x not in ol: return 0 else: return 0 return 1 elif oc is kjSet or oc is kjDict: sktl = self.key_to_list odict = other.dict otest = odict.has_key for k in sktl.keys(): if otest(k): l = sktl[k] ov = odict[k] for x in l: if ov!=x: return 0 else: return 0 return 1 def neighbors(self, k): try: return self.key_to_list[k][:] except: return [] def reachable(self, k): try: horizon = self.key_to_list[k] except: return kjSet() else: if not horizon: return [] d = {} for x in horizon: d[x] = 1 done = 0 while horizon: newhorizon = [] for n in horizon: for n2 in self.neighbors(n): if not d.has_key(n2): newhorizon.append(n2) d[n2] = 1 horizon = newhorizon return kjSet(d.keys()) def items(self): return self._pairs() # ???? def ident(self): result = kjDict(self) result.dirty = self.dirty or result.dirty return result def tclosure(self): # quick and dirty try: raise self except (kjSet, kjDict): raise ValueError, "tclosure only defined on graphs" except kjGraph: pass except: raise ValueError, "tclosure only defined on graphs" result = kjGraph(self) result.dirty = self.dirty addit = result.add while 1: #print result more = result*result if more.subset(result): return result for (x,y) in more.items(): addit(x,y) def Clean(self): if self.dirty: return None return self def Wash(self): self.dirty = 0 def Soil(self): self.dirty = 1 def remap(self, X): # really only should be defined for kjdict, but whatever return kjDict(X*self).Clean() def dump(self, seq): result = map(None, seq) for i in range(len(result)): result[i] = self[result[i]] if len(seq) == 1: return result[0] return tuple(result) def __hash__(self): # should test better """in conformance with kjbuckets, permit unhashable keys""" if self.hashed is not None: return self.hashed items = self._pairs() for i in xrange(len(items)): (a,b) = items[i] try: b = hash(b) except: b = 1877777 items[i] = hash(a)^~b items.sort() result = self.hashed = hash(tuple(items)) return result def __cmp__(self, other): #print "kjGraph.__cmp__" ls = len(self) lo = len(other) test = cmp(ls, lo) if test: return test si = self._pairs() oi = other._pairs() si.sort() oi.sort() return cmp(si, oi) def __nonzero__(self): if self.key_to_list: return 1 return 0 def __add__(self, other): result = kjGraph(self) rktl = result.key_to_list rtest = rktl.has_key result.dirty = self.dirty or other.dirty oc = other.__class__ if oc is kjGraph: oktl = other.key_to_list for k in oktl.keys(): l = oktl[k] if rtest(k): rl = rktl[k] for x in l: if x not in rl: rl.append(x) else: rktl[k] = l[:] elif oc is kjSet or oc is kjDict: odict = other.dict for k in odict.keys(): ov = odict[k] if rtest(k): rl = rktl[k] if ov not in rl: rl.append(ov) else: rktl[k] = [ov] else: raise ValueError, "kjGraph adds only with kjTable" return result __or__ = __add__ def __sub__(self, other): result = kjGraph() rktl = result.key_to_list sktl = self.key_to_list oc = other.__class__ if oc is kjGraph: oktl = other.key_to_list otest = oktl.has_key for k in sktl.keys(): l = sktl[k][:] if otest(k): ol = oktl[k] for x in ol: if x in l: l.remove(x) if l: rktl[k] = l else: rktl[k] = l elif oc is kjSet or oc is kjDict: odict = other.dict otest = odict.has_key for k in sktl.keys(): l = sktl[k][:] if otest(k): ov = odict[k] if ov in l: l.remove(ov) if l: rktl[k] = l else: raise ValueError, "kjGraph diffs only with kjTable" return result def __mul__(self, other): result = kjGraph() rktl = result.key_to_list sktl = self.key_to_list oc = other.__class__ if oc is kjGraph: oktl = other.key_to_list otest = other.has_key for sk in sktl.keys(): sklist = [] for sv in sktl[sk]: if otest(sv): sklist[0:0] = oktl[sv] if sklist: rktl[sk] = sklist elif oc is kjSet or oc is kjDict: odict = other.dict otest = odict.has_key for sk in sktl.keys(): sklist=[] for sv in sktl[sk]: if otest(sv): sklist.append(odict[sv]) if sklist: rktl[sk] = sklist else: raise ValueError, "kjGraph composes only with kjTable" return result def __invert__(self): result = self.__class__() pairs = self._pairs() for i in xrange(len(pairs)): (k,v) = pairs[i] pairs[i] = (v,k) result._setitems(pairs) result.dirty = self.dirty or result.dirty return result def __and__(self, other): sktl = self.key_to_list oc = other.__class__ if oc is kjGraph: result = kjGraph() rktl = result.key_to_list oktl = other.key_to_list otest = oktl.has_key for k in self.keys(): if otest(k): l = sktl[k] ol = oktl[k] rl = [] for x in l: if x in ol: rl.append(x) if rl: rktl[k] = rl elif oc is kjSet or oc is kjDict: result = oc() # less general! rdict = result.dict odict = other.dict stest = sktl.has_key for k in odict.keys(): if stest(k): v = odict[k] l = sktl[k] if v in l: rdict[k] = v else: raise ValueError, "kjGraph intersects only with kjTable" result.dirty = self.dirty or other.dirty return result def __coerce__(self, other): return (self, other) # ?is this sufficient? class kjDict(kjGraph): def __init__(self, *args): #print "kjDict.__init__", args self.hashed = None dict = self.dict = {} self.dirty = 0 if not args: return if len(args)==1: from types import TupleType, ListType, IntType arg0 = args[0] targ0 = type(arg0) if targ0 is IntType: return if targ0 is ListType or targ0 is TupleType: otest = dict.has_key for (a,b) in arg0: if otest(a): if dict[a]!=b: self.dirty = 1 dict[a] = b return argc = arg0.__class__ if argc is kjGraph: ktl = arg0.key_to_list for k in ktl.keys(): l = ktl[k] if len(l)>1: self.dirty=1 for v in l: dict[k] = v return if argc is kjSet or argc is kjDict: adict = arg0.dict for (k,v) in adict.items(): dict[k]=v return raise ValueError, "kjDict initializes only from list, tuple, kjTable, or int" def _setitems(self, thing): #print "kjDict._setitem", thing if self.hashed is not None: raise KeyError, "table hashed, cannot modify" dict = self.dict try: for (k,v) in thing: if dict.has_key(k) and dict[k]!=v: self.dirty = 1 dict[k] = v except: self._setitems(thing._pairs()) # maybe too tricky! def dump(self, dumper): ld = len(dumper) if ld==1: return self.dict[dumper[0]] else: sdict = self.dict result = [None] * ld for i in xrange(ld): result[i] = sdict[ dumper[i] ] return tuple(result) def __setitem__(self, item, value): if self.hashed is not None: raise ValueError, "table has been hashed, it is immutable" d = self.dict if d.has_key(item): if d[item]!=value: self.dirty = 1 self.dict[item]=value def __getitem__(self, item): return self.dict[item] def __delitem__(self, item): if self.hashed is not None: raise ValueError, "table has been hashed, it is immutable" self.dirty = 1 del self.dict[item] def choose_key(self): return self.dict.keys()[0] def __len__(self): return len(self.dict) def _pairs(self, justtot=0): if justtot: return len(self.dict) return self.dict.items() def values(self): return self.dict.values() def keys(self): return self.dict.keys() def items(self): return self.dict.items() def remap(self, X): if X.__class__ is kjGraph: if self.dirty or X.dirty: return None result = kjDict() resultd = result.dict selfd = self.dict inself = selfd.has_key inresult = resultd.has_key ktl = X.key_to_list for k in ktl.keys(): for v in ktl[k]: if inself(v): map = selfd[v] if inresult(k): if resultd[k]!=map: return None else: resultd[k]=map return result else: return (kjDict(X*self)).Clean() def __cmp__(s,o): from types import InstanceType if type(o) is not InstanceType: return -1 oc = o.__class__ if oc is kjDict or oc is kjSet: return cmp(s.dict, o.dict) return kjGraph.__cmp__(s, o) def __hash__(s): h = s.hashed if h is not None: return h return kjGraph.__hash__(s) def __add__(s,o): oc = o.__class__ if oc is kjDict or oc is kjSet: result = kjDict() result.dirty = s.dirty or o.dirty rdict = result.dict rtest = result.has_key sdict = s.dict for k in sdict.keys(): rdict[k] = sdict[k] odict = o.dict for k in odict.keys(): if rtest(k): if rdict[k]!=odict[k]: result.dirty=1 else: rdict[k] = odict[k] return result if oc is kjGraph: return kjGraph.__add__(o,s) else: raise ValueError, "kjDict unions only with kjTable" __or__ = __add__ def __and__(s,o): oc = o.__class__ if oc is kjDict or oc is kjSet: result = oc() result.dirty = s.dirty or o.dirty rdict = result.dict odict = o.dict sdict = s.dict stest = sdict.has_key for k in odict.keys(): v = odict[k] if stest(k) and sdict[k]==v: rdict[k] = v return result elif oc is kjGraph: return kjGraph.__and__(o,s) def __sub__(s,o): oc = o.__class__ result = kjDict() result.dirty = s.dirty or o.dirty sdict = s.dict rdict = result.dict if oc is kjDict: odict = o.dict otest = odict.has_key for k in sdict.keys(): v = sdict[k] if otest(k): if odict[k]!=v: rdict[k] = v else: rdict[k] = v return result if oc is kjGraph: oktl = o.key_to_list otest = oktl.has_key for k in sdict.keys(): v = sdict[k] if otest(k): if v not in oktl[k]: rdict[k] = v else: rdict[k] = v return result raise ValueError, "kjDict only diffs with kjGraph, kjDict" def __mul__(s,o): oc = o.__class__ sdict = s.dict if oc is kjDict or oc is kjSet: result = kjDict() result.dirty = s.dirty or o.dirty rdict = result.dict odict = o.dict otest = odict.has_key for k in sdict.keys(): kv = sdict[k] if otest(kv): rdict[k] = odict[kv] return result elif oc is kjGraph: return kjGraph(s) * o else: raise ValueError, "kjDict only composes with kjTable" def member(self, k, v): d = self.dict try: return d[k] == v except: return 0 _member = member def delete_arc(self, k, v): if self.dict[k] == v: del self.dict[k] else: raise KeyError, "pair not in table" def has_key(self, k): return self.dict.has_key(k) def neighbors(self, k): try: return [ self.dict[k] ] except: return [] def reachable(self, k): result = {} d = self.dict try: while 1: next = d[k] if result.has_key(next): break result[next] = 1 k = next except KeyError: pass return kjSet(result.keys()) def __invert__(self): result = kjDict() dr = result.dict drtest = dr.has_key ds = self.dict for (a,b) in ds.items(): if drtest(b): result.dirty=1 dr[b]=a result.dirty = self.dirty or result.dirty return result def __nonzero__(self): if self.dict: return 1 return 0 def subset(s, o): oc = o.__class__ sdict = s.dict if oc is kjDict or oc is kjSet: odict = o.dict otest = odict.has_key for k in sdict.keys(): v = sdict[k] if otest(k): if odict[k]!=v: return 0 else: return 0 elif oc is kjGraph: oktl = o.key_to_list otest = oktl.has_key for k in sdict.keys(): v = sdict[k] if otest(k): if v not in oktl[k]: return 0 else: return 0 else: raise ValueError, "kjDict subset test only for kjTable" return 1 def add(s, k, v): if s.hashed is not None: raise ValueError, "table has been hashed, immutable" sdict = s.dict if sdict.has_key(k): if sdict[k]!=v: s.dirty = 1 sdict[k] = v class kjSet(kjDict): def __init__(self, *args): #print "kjSet.__init__", args # usual cases first dict = self.dict = {} self.hashed = None self.dirty = 0 largs = len(args) if largs<1: return if largs>1: raise ValueError, "at most one argument supported" from types import IntType, TupleType, ListType arg0 = args[0] targ0 = type(arg0) if targ0 is IntType: return if targ0 is TupleType or targ0 is ListType: for x in arg0: dict[x] = x return argc = arg0.__class__ if argc is kjDict or argc is kjSet: stuff = arg0.dict.keys() elif argc is kjGraph: stuff = arg0.key_to_list.keys() else: raise ValueError, "kjSet from kjTable, int, list, tuple only" for x in stuff: dict[x] = x def __add__(s,o): oc = o.__class__ if oc is kjSet: result = kjSet() result.dirty = s.dirty or o.dirty rdict = result.dict for x in s.dict.keys(): rdict[x]=x for x in o.dict.keys(): rdict[x]=x return result elif oc is kjDict: return kjDict.__add__(o,s) elif oc is kjGraph: return kjGraph.__add__(o,s) __or__ = __add__ def __sub__(s,o): if o.__class__ is kjSet: result = kjSet() result.dirty = s.dirty or o.dirty rdict = result.dict otest = o.dict.has_key for x in s.dict.keys(): if not otest(x): rdict[x] = x return result else: return kjDict.__sub__(s,o) def __and__(s,o): oc = o.__class__ if oc is kjSet or oc is kjDict: result = kjSet() result.dirty = s.dirty or o.dirty rdict = result.dict odict = o.dict otest = odict.has_key for x in s.dict.keys(): if otest(x) and odict[x]==x: rdict[x] = x return result elif oc is kjGraph: return kjGraph.__and__(o,s) raise ValueError, "kjSet only intersects with kjTable" # illegal methods values = keys = remap = None def __repr__(self): return "kjSet(%s)" % self.items() def _setelts(self, items): #print "kjSet.setelts", items try: items = items._pairs() except: items = list(items) for i in xrange(len(items)): items[i] = (items[i], items[i]) self._setitems(items) else: items = list(items) for i in xrange(len(items)): items[i] = (items[i][0], items[i][0]) self._setitems(items) # hack! #D = self.dict #for x in D.keys(): # D[x] = x def _pairs(self, justtot=0): if justtot: return kjDict._pairs(self, justtot=1) pairs = kjDict.keys(self) for i in xrange(len(pairs)): pairs[i] = (pairs[i], pairs[i]) return pairs member = kjDict.has_key items = kjDict.keys #def neighbors(self, x): # raise ValueError, "operation on kjSet undefined" #reachable = neighbors def __getitem__(self, item): test = self.dict.has_key(item) if test: return 1 raise KeyError, "item not in set" def __setitem__(self, item, ignore): d = self.dict if self.hashed: raise ValueError, "table hashed, immutable" d[item] = item def add(self, elt): if self.hashed: raise ValueError, "table hashed, immutable" self.dict[elt] = elt def __mul__(s,o): oc = o.__class__ if oc is kjSet: return s.__and__(o) else: return kjDict.__mul__(s, o) def more_general(t1, t2): try: raise t1 except kjSet: try: raise t2 except (kjGraph, kjDict, kjSet): return t2.__class__ except kjDict: try: raise t2 except kjSet: return t1.__class__ except (kjDict, kjGraph): return t2.__class__ except kjGraph: return t1.__class__ except: raise ValueError, "cannot coerce, not kjtable" def less_general(t1,t2): try: raise t1 except kjSet: return t1.__class__ except kjDict: try: raise t2 except kjSet: return t2.__class__ except (kjDict, kjGraph): return t1.__class__ except kjGraph: return t2.__class__ except: raise ValueError, "cannot coerce, not kjtable" def kjUndump(t1, t2): result = kjDict() rdict = result.dict lt1 = len(t1) if lt1 == 1: rdict[t1[0]] = t2 else: # tightly bound to implementation for i in xrange(lt1): rdict[t1[i]] = t2[i] return result def test(): global S, D, G G = kjGraph() r3 = range(3) r = map(None, r3, r3) for i in range(3): G[i] = i+1 D = kjDict(G) D[9]=0 G[0]=10 S = kjSet(G) S[-1] = 5 print "%s.remap(%s) = %s" % (D, G, D.remap(G)) print "init test" for X in (S, D, G, r, tuple(r), 1): print "ARG", X for C in (kjGraph, kjSet, kjDict): print "AS", C T = C(X) T2 = C() print X, T, T2 ALL = (S, D, G) for X in ALL: print "X=", X print "key", X.choose_key() print "len", len(X) print "items", X.items() print X, "Clean before", X.Clean() del X[2] print X, "Clean after", X.Clean() if not X.subset(X): raise "trivial subset fails", X if not X==X: raise "trivial cmp fails", X if not X: raise "nonzero fails", X if X is S: if not S.member(0): raise "huh 1?" if S.member(123): raise "huh 2?", S S.add(999) del S[1] if not S.has_key(999): raise "huh 3?", S else: print "values", X.values() print "keys", X.keys() print X, "inverted", ~X if not X.member(0,1): raise "member test fails (0,1)", X print "adding to", X X.add(999,888) print "added", X X.delete_arc(999,888) print "deleted", X if X.member(999,888): raise "member test fails (999,888)", X if X.has_key(999): raise "has_key fails 999", X if not X.has_key(0): raise "has_key fails 0", X for Y in ALL: print "Y", Y if (X!=S and Y!=S): print "diff", X, Y print "%s-%s=%s" % (X,Y,X-Y) elif X==S: D = kjSet(Y) print "diff", X, D print "%s-%s=%s" % (X,D,X-D) print "%s+%s=%s" % (X,Y,X+Y) print "%s&%s=%s" % (X,Y,X&Y) print "%s*%s=%s" % (X,Y,X*Y) x,y = cmp(X,Y), cmp(Y,X) if x!=-y: raise "bad cmp!", (X, Y) print "cmp(X,Y), -cmp(Y,X)", x,-y print "X.subset(Y)", X.subset(Y)