
# 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
    

class TestFailure(Exception):
    pass

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 TestFaliure, "trivial subset fails: %s" % X
        if not X==X:
           raise TestFailure, "trivial cmp fails: %s" % X
        if not X:
           raise TestFailure, "nonzero fails: %s" % X
        if X is S:
           if not S.member(0):
              raise TestFailure, "huh 1?"
           if S.member(123):
              raise TestFailure, "huh 2?: %s" % S
           S.add(999)
           del S[1]
           if not S.has_key(999):
              raise TestFailure, "huh 3?: %s" % S
        else:
           print "values", X.values()
           print "keys", X.keys()
           print X, "inverted", ~X
           if not X.member(0,1):
              raise TestFailure, "member test fails (0,1): %s" % 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 TestFailure, "member test fails (999,888): %s" % X
           if X.has_key(999):
              raise TestFailure, "has_key fails 999: %s" % X
           if not X.has_key(0):
              raise TestFailure, "has_key fails 0: %s" % 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 TestFailure, "bad cmp!: %s, %s" % (X, Y)
            print "cmp(X,Y), -cmp(Y,X)", x,-y
            print "X.subset(Y)", X.subset(Y)
            


           
    
