github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-runtime-src/third_party/stdlib/collections.py (about)

     1  '''This module implements specialized container datatypes providing
     2  alternatives to Python's general purpose built-in containers, dict,
     3  list, set, and tuple.
     4  
     5  * namedtuple   factory function for creating tuple subclasses with named fields
     6  * deque        list-like container with fast appends and pops on either end
     7  * Counter      dict subclass for counting hashable objects
     8  * OrderedDict  dict subclass that remembers the order entries were added
     9  * defaultdict  dict subclass that calls a factory function to supply missing values
    10  
    11  '''
    12  
    13  __all__ = ['Counter', 'namedtuple', 'OrderedDict', 'deque'] # 'deque', 'defaultdict',
    14  # For bootstrapping reasons, the collection ABCs are defined in _abcoll.py.
    15  # They should however be considered an integral part of collections.py.
    16  import _abcoll
    17  # TODO: Support from foo import * syntax.
    18  for name in _abcoll.__all__:
    19    globals()[name] = getattr(_abcoll, name)
    20  
    21  import _collections
    22  deque = _collections.deque
    23  #defaultdict = _collections.defaultdict
    24  import operator
    25  _itemgetter = operator.itemgetter
    26  _eq = operator.eq
    27  import keyword
    28  _iskeyword = keyword.iskeyword
    29  import sys as _sys
    30  import heapq as _heapq
    31  import itertools
    32  _repeat = itertools.repeat
    33  _chain = itertools.chain
    34  _starmap = itertools.starmap
    35  _imap = itertools.imap
    36  
    37  #try:
    38  import thread
    39  _get_ident = thread.get_ident
    40  #except ImportError:
    41  #    from dummy_thread import get_ident as _get_ident
    42  
    43  
    44  ################################################################################
    45  ### OrderedDict
    46  ################################################################################
    47  
    48  class OrderedDict(dict):
    49      'Dictionary that remembers insertion order'
    50      # An inherited dict maps keys to values.
    51      # The inherited dict provides __getitem__, __len__, __contains__, and get.
    52      # The remaining methods are order-aware.
    53      # Big-O running times for all methods are the same as regular dictionaries.
    54  
    55      # The internal self.__map dict maps keys to links in a doubly linked list.
    56      # The circular doubly linked list starts and ends with a sentinel element.
    57      # The sentinel element never gets deleted (this simplifies the algorithm).
    58      # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
    59  
    60      def __init__(*args, **kwds):
    61          '''Initialize an ordered dictionary.  The signature is the same as
    62          regular dictionaries, but keyword arguments are not recommended because
    63          their insertion order is arbitrary.
    64  
    65          '''
    66          if not args:
    67              raise TypeError("descriptor '__init__' of 'OrderedDict' object "
    68                              "needs an argument")
    69          self = args[0]
    70          args = args[1:]
    71          if len(args) > 1:
    72              raise TypeError('expected at most 1 arguments, got %d' % len(args))
    73          try:
    74              self.__root
    75          except AttributeError:
    76              self.__root = root = []                     # sentinel node
    77              root[:] = [root, root, None]
    78              self.__map = {}
    79          self.__update(*args, **kwds)
    80  
    81      def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
    82          'od.__setitem__(i, y) <==> od[i]=y'
    83          # Setting a new item creates a new link at the end of the linked list,
    84          # and the inherited dictionary is updated with the new key/value pair.
    85          if key not in self:
    86              root = self.__root
    87              last = root[0]
    88              last[1] = root[0] = self.__map[key] = [last, root, key]
    89          return dict_setitem(self, key, value)
    90  
    91      def __delitem__(self, key, dict_delitem=dict.__delitem__):
    92          'od.__delitem__(y) <==> del od[y]'
    93          # Deleting an existing item uses self.__map to find the link which gets
    94          # removed by updating the links in the predecessor and successor nodes.
    95          dict_delitem(self, key)
    96          link_prev, link_next, _ = self.__map.pop(key)
    97          link_prev[1] = link_next                        # update link_prev[NEXT]
    98          link_next[0] = link_prev                        # update link_next[PREV]
    99  
   100      def __iter__(self):
   101          'od.__iter__() <==> iter(od)'
   102          # Traverse the linked list in order.
   103          root = self.__root
   104          curr = root[1]                                  # start at the first node
   105          while curr is not root:
   106              yield curr[2]                               # yield the curr[KEY]
   107              curr = curr[1]                              # move to next node
   108  
   109      def __reversed__(self):
   110          'od.__reversed__() <==> reversed(od)'
   111          # Traverse the linked list in reverse order.
   112          root = self.__root
   113          curr = root[0]                                  # start at the last node
   114          while curr is not root:
   115              yield curr[2]                               # yield the curr[KEY]
   116              curr = curr[0]                              # move to previous node
   117  
   118      def clear(self):
   119          'od.clear() -> None.  Remove all items from od.'
   120          root = self.__root
   121          root[:] = [root, root, None]
   122          self.__map.clear()
   123          dict.clear(self)
   124  
   125      # -- the following methods do not depend on the internal structure --
   126  
   127      def keys(self):
   128          'od.keys() -> list of keys in od'
   129          return list(self)
   130  
   131      def values(self):
   132          'od.values() -> list of values in od'
   133          return [self[key] for key in self]
   134  
   135      def items(self):
   136          'od.items() -> list of (key, value) pairs in od'
   137          return [(key, self[key]) for key in self]
   138  
   139      def iterkeys(self):
   140          'od.iterkeys() -> an iterator over the keys in od'
   141          return iter(self)
   142  
   143      def itervalues(self):
   144          'od.itervalues -> an iterator over the values in od'
   145          for k in self:
   146              yield self[k]
   147  
   148      def iteritems(self):
   149          'od.iteritems -> an iterator over the (key, value) pairs in od'
   150          for k in self:
   151              yield (k, self[k])
   152  
   153      update = MutableMapping.update
   154  
   155      __update = update # let subclasses override update without breaking __init__
   156  
   157      __marker = object()
   158  
   159      def pop(self, key, default=__marker):
   160          '''od.pop(k[,d]) -> v, remove specified key and return the corresponding
   161          value.  If key is not found, d is returned if given, otherwise KeyError
   162          is raised.
   163  
   164          '''
   165          if key in self:
   166              result = self[key]
   167              del self[key]
   168              return result
   169          if default is self.__marker:
   170              raise KeyError(key)
   171          return default
   172  
   173      def setdefault(self, key, default=None):
   174          'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
   175          if key in self:
   176              return self[key]
   177          self[key] = default
   178          return default
   179  
   180      def popitem(self, last=True):
   181          '''od.popitem() -> (k, v), return and remove a (key, value) pair.
   182          Pairs are returned in LIFO order if last is true or FIFO order if false.
   183  
   184          '''
   185          if not self:
   186              raise KeyError('dictionary is empty')
   187          key = next(reversed(self) if last else iter(self))
   188          value = self.pop(key)
   189          return key, value
   190  
   191      def __repr__(self, _repr_running={}):
   192          'od.__repr__() <==> repr(od)'
   193          call_key = id(self), _get_ident()
   194          if call_key in _repr_running:
   195              return '...'
   196          _repr_running[call_key] = 1
   197          try:
   198              if not self:
   199                  return '%s()' % (self.__class__.__name__,)
   200              return '%s(%r)' % (self.__class__.__name__, self.items())
   201          finally:
   202              del _repr_running[call_key]
   203  
   204      def __reduce__(self):
   205          'Return state information for pickling'
   206          items = [[k, self[k]] for k in self]
   207          inst_dict = vars(self).copy()
   208          for k in vars(OrderedDict()):
   209              inst_dict.pop(k, None)
   210          if inst_dict:
   211              return (self.__class__, (items,), inst_dict)
   212          return self.__class__, (items,)
   213  
   214      def copy(self):
   215          'od.copy() -> a shallow copy of od'
   216          return self.__class__(self)
   217  
   218      @classmethod
   219      def fromkeys(cls, iterable, value=None):
   220          '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
   221          If not specified, the value defaults to None.
   222  
   223          '''
   224          self = cls()
   225          for key in iterable:
   226              self[key] = value
   227          return self
   228  
   229      def __eq__(self, other):
   230          '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
   231          while comparison to a regular mapping is order-insensitive.
   232  
   233          '''
   234          if isinstance(other, OrderedDict):
   235              return dict.__eq__(self, other) and all(_imap(_eq, self, other))
   236          return dict.__eq__(self, other)
   237  
   238      def __ne__(self, other):
   239          'od.__ne__(y) <==> od!=y'
   240          return not self == other
   241  
   242      # -- the following methods support python 3.x style dictionary views --
   243  
   244      def viewkeys(self):
   245          "od.viewkeys() -> a set-like object providing a view on od's keys"
   246          return KeysView(self)
   247  
   248      def viewvalues(self):
   249          "od.viewvalues() -> an object providing a view on od's values"
   250          return ValuesView(self)
   251  
   252      def viewitems(self):
   253          "od.viewitems() -> a set-like object providing a view on od's items"
   254          return ItemsView(self)
   255  
   256  
   257  ################################################################################
   258  ### namedtuple
   259  ################################################################################
   260  
   261  _class_template = '''\
   262  class {typename}(tuple):
   263      '{typename}({arg_list})'
   264  
   265      __slots__ = ()
   266  
   267      _fields = {field_names!r}
   268  
   269      def __new__(_cls, {arg_list}):
   270          'Create new instance of {typename}({arg_list})'
   271          return _tuple.__new__(_cls, ({arg_list}))
   272  
   273      @classmethod
   274      def _make(cls, iterable, new=tuple.__new__, len=len):
   275          'Make a new {typename} object from a sequence or iterable'
   276          result = new(cls, iterable)
   277          if len(result) != {num_fields:d}:
   278              raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result))
   279          return result
   280  
   281      def __repr__(self):
   282          'Return a nicely formatted representation string'
   283          return '{typename}({repr_fmt})' % self
   284  
   285      def _asdict(self):
   286          'Return a new OrderedDict which maps field names to their values'
   287          return OrderedDict(zip(self._fields, self))
   288  
   289      def _replace(_self, **kwds):
   290          'Return a new {typename} object replacing specified fields with new values'
   291          result = _self._make(map(kwds.pop, {field_names!r}, _self))
   292          if kwds:
   293              raise ValueError('Got unexpected field names: %r' % kwds.keys())
   294          return result
   295  
   296      def __getnewargs__(self):
   297          'Return self as a plain tuple.  Used by copy and pickle.'
   298          return tuple(self)
   299  
   300      __dict__ = _property(_asdict)
   301  
   302      def __getstate__(self):
   303          'Exclude the OrderedDict from pickling'
   304          pass
   305  
   306  {field_defs}
   307  '''
   308  
   309  _repr_template = '{name}=%r'
   310  
   311  _field_template = '''\
   312      {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}')
   313  '''
   314  
   315  #def namedtuple(typename, field_names, verbose=False, rename=False):
   316  #    """Returns a new subclass of tuple with named fields.
   317  #
   318  #    >>> Point = namedtuple('Point', ['x', 'y'])
   319  #    >>> Point.__doc__                   # docstring for the new class
   320  #    'Point(x, y)'
   321  #    >>> p = Point(11, y=22)             # instantiate with positional args or keywords
   322  #    >>> p[0] + p[1]                     # indexable like a plain tuple
   323  #    33
   324  #    >>> x, y = p                        # unpack like a regular tuple
   325  #    >>> x, y
   326  #    (11, 22)
   327  #    >>> p.x + p.y                       # fields also accessible by name
   328  #    33
   329  #    >>> d = p._asdict()                 # convert to a dictionary
   330  #    >>> d['x']
   331  #    11
   332  #    >>> Point(**d)                      # convert from a dictionary
   333  #    Point(x=11, y=22)
   334  #    >>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
   335  #    Point(x=100, y=22)
   336  #
   337  #    """
   338  #
   339  #    # Validate the field names.  At the user's option, either generate an error
   340  #    # message or automatically replace the field name with a valid name.
   341  #    if isinstance(field_names, basestring):
   342  #        field_names = field_names.replace(',', ' ').split()
   343  #    field_names = map(str, field_names)
   344  #    typename = str(typename)
   345  #    if rename:
   346  #        seen = set()
   347  #        for index, name in enumerate(field_names):
   348  #            if (not all(c.isalnum() or c=='_' for c in name)
   349  #                or _iskeyword(name)
   350  #                or not name
   351  #                or name[0].isdigit()
   352  #                or name.startswith('_')
   353  #                or name in seen):
   354  #                field_names[index] = '_%d' % index
   355  #            seen.add(name)
   356  #    for name in [typename] + field_names:
   357  #        if type(name) != str:
   358  #            raise TypeError('Type names and field names must be strings')
   359  #        if not all(c.isalnum() or c=='_' for c in name):
   360  #            raise ValueError('Type names and field names can only contain '
   361  #                             'alphanumeric characters and underscores: %r' % name)
   362  #        if _iskeyword(name):
   363  #            raise ValueError('Type names and field names cannot be a '
   364  #                             'keyword: %r' % name)
   365  #        if name[0].isdigit():
   366  #            raise ValueError('Type names and field names cannot start with '
   367  #                             'a number: %r' % name)
   368  #    seen = set()
   369  #    for name in field_names:
   370  #        if name.startswith('_') and not rename:
   371  #            raise ValueError('Field names cannot start with an underscore: '
   372  #                             '%r' % name)
   373  #        if name in seen:
   374  #            raise ValueError('Encountered duplicate field name: %r' % name)
   375  #        seen.add(name)
   376  #
   377  #    # Fill-in the class template
   378  #    class_definition = _class_template.format(
   379  #        typename = typename,
   380  #        field_names = tuple(field_names),
   381  #        num_fields = len(field_names),
   382  #        arg_list = repr(tuple(field_names)).replace("'", "")[1:-1],
   383  #        repr_fmt = ', '.join(_repr_template.format(name=name)
   384  #                             for name in field_names),
   385  #        field_defs = '\n'.join(_field_template.format(index=index, name=name)
   386  #                               for index, name in enumerate(field_names))
   387  #    )
   388  #    if verbose:
   389  #        print class_definition
   390  #
   391  #    # Execute the template string in a temporary namespace and support
   392  #    # tracing utilities by setting a value for frame.f_globals['__name__']
   393  #    namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
   394  #                     OrderedDict=OrderedDict, _property=property, _tuple=tuple)
   395  #    try:
   396  #        exec class_definition in namespace
   397  #    except SyntaxError as e:
   398  #        raise SyntaxError(e.message + ':\n' + class_definition)
   399  #    result = namespace[typename]
   400  #
   401  #    # For pickling to work, the __module__ variable needs to be set to the frame
   402  #    # where the named tuple is created.  Bypass this step in environments where
   403  #    # sys._getframe is not defined (Jython for example) or sys._getframe is not
   404  #    # defined for arguments greater than 0 (IronPython).
   405  #    try:
   406  #        result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
   407  #    except (AttributeError, ValueError):
   408  #        pass
   409  #
   410  #    return result
   411  
   412  
   413  ########################################################################
   414  ###  Counter
   415  ########################################################################
   416  
   417  class Counter(dict):
   418      '''Dict subclass for counting hashable items.  Sometimes called a bag
   419      or multiset.  Elements are stored as dictionary keys and their counts
   420      are stored as dictionary values.
   421  
   422      >>> c = Counter('abcdeabcdabcaba')  # count elements from a string
   423  
   424      >>> c.most_common(3)                # three most common elements
   425      [('a', 5), ('b', 4), ('c', 3)]
   426      >>> sorted(c)                       # list all unique elements
   427      ['a', 'b', 'c', 'd', 'e']
   428      >>> ''.join(sorted(c.elements()))   # list elements with repetitions
   429      'aaaaabbbbcccdde'
   430      >>> sum(c.values())                 # total of all counts
   431      15
   432  
   433      >>> c['a']                          # count of letter 'a'
   434      5
   435      >>> for elem in 'shazam':           # update counts from an iterable
   436      ...     c[elem] += 1                # by adding 1 to each element's count
   437      >>> c['a']                          # now there are seven 'a'
   438      7
   439      >>> del c['b']                      # remove all 'b'
   440      >>> c['b']                          # now there are zero 'b'
   441      0
   442  
   443      >>> d = Counter('simsalabim')       # make another counter
   444      >>> c.update(d)                     # add in the second counter
   445      >>> c['a']                          # now there are nine 'a'
   446      9
   447  
   448      >>> c.clear()                       # empty the counter
   449      >>> c
   450      Counter()
   451  
   452      Note:  If a count is set to zero or reduced to zero, it will remain
   453      in the counter until the entry is deleted or the counter is cleared:
   454  
   455      >>> c = Counter('aaabbc')
   456      >>> c['b'] -= 2                     # reduce the count of 'b' by two
   457      >>> c.most_common()                 # 'b' is still in, but its count is zero
   458      [('a', 3), ('c', 1), ('b', 0)]
   459  
   460      '''
   461      # References:
   462      #   http://en.wikipedia.org/wiki/Multiset
   463      #   http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html
   464      #   http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm
   465      #   http://code.activestate.com/recipes/259174/
   466      #   Knuth, TAOCP Vol. II section 4.6.3
   467  
   468      def __init__(*args, **kwds):
   469          '''Create a new, empty Counter object.  And if given, count elements
   470          from an input iterable.  Or, initialize the count from another mapping
   471          of elements to their counts.
   472  
   473          >>> c = Counter()                           # a new, empty counter
   474          >>> c = Counter('gallahad')                 # a new counter from an iterable
   475          >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
   476          >>> c = Counter(a=4, b=2)                   # a new counter from keyword args
   477  
   478          '''
   479          if not args:
   480              raise TypeError("descriptor '__init__' of 'Counter' object "
   481                              "needs an argument")
   482          self = args[0]
   483          args = args[1:]
   484          if len(args) > 1:
   485              raise TypeError('expected at most 1 arguments, got %d' % len(args))
   486          super(Counter, self).__init__()
   487          self.update(*args, **kwds)
   488  
   489      def __missing__(self, key):
   490          'The count of elements not in the Counter is zero.'
   491          # Needed so that self[missing_item] does not raise KeyError
   492          return 0
   493  
   494      def most_common(self, n=None):
   495          '''List the n most common elements and their counts from the most
   496          common to the least.  If n is None, then list all element counts.
   497  
   498          >>> Counter('abcdeabcdabcaba').most_common(3)
   499          [('a', 5), ('b', 4), ('c', 3)]
   500  
   501          '''
   502          # Emulate Bag.sortedByCount from Smalltalk
   503          if n is None:
   504              return sorted(self.iteritems(), key=_itemgetter(1), reverse=True)
   505          return _heapq.nlargest(n, self.iteritems(), key=_itemgetter(1))
   506  
   507      def elements(self):
   508          '''Iterator over elements repeating each as many times as its count.
   509  
   510          >>> c = Counter('ABCABC')
   511          >>> sorted(c.elements())
   512          ['A', 'A', 'B', 'B', 'C', 'C']
   513  
   514          # Knuth's example for prime factors of 1836:  2**2 * 3**3 * 17**1
   515          >>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
   516          >>> product = 1
   517          >>> for factor in prime_factors.elements():     # loop over factors
   518          ...     product *= factor                       # and multiply them
   519          >>> product
   520          1836
   521  
   522          Note, if an element's count has been set to zero or is a negative
   523          number, elements() will ignore it.
   524  
   525          '''
   526          # Emulate Bag.do from Smalltalk and Multiset.begin from C++.
   527          return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
   528  
   529      # Override dict methods where necessary
   530  
   531      @classmethod
   532      def fromkeys(cls, iterable, v=None):
   533          # There is no equivalent method for counters because setting v=1
   534          # means that no element can have a count greater than one.
   535          raise NotImplementedError(
   536              'Counter.fromkeys() is undefined.  Use Counter(iterable) instead.')
   537  
   538      def update(*args, **kwds):
   539          '''Like dict.update() but add counts instead of replacing them.
   540  
   541          Source can be an iterable, a dictionary, or another Counter instance.
   542  
   543          >>> c = Counter('which')
   544          >>> c.update('witch')           # add elements from another iterable
   545          >>> d = Counter('watch')
   546          >>> c.update(d)                 # add elements from another counter
   547          >>> c['h']                      # four 'h' in which, witch, and watch
   548          4
   549  
   550          '''
   551          # The regular dict.update() operation makes no sense here because the
   552          # replace behavior results in the some of original untouched counts
   553          # being mixed-in with all of the other counts for a mismash that
   554          # doesn't have a straight-forward interpretation in most counting
   555          # contexts.  Instead, we implement straight-addition.  Both the inputs
   556          # and outputs are allowed to contain zero and negative counts.
   557  
   558          if not args:
   559              raise TypeError("descriptor 'update' of 'Counter' object "
   560                              "needs an argument")
   561          self = args[0]
   562          args = args[1:]
   563          if len(args) > 1:
   564              raise TypeError('expected at most 1 arguments, got %d' % len(args))
   565          iterable = args[0] if args else None
   566          if iterable is not None:
   567              if isinstance(iterable, Mapping):
   568                  if self:
   569                      self_get = self.get
   570                      for elem, count in iterable.iteritems():
   571                          self[elem] = self_get(elem, 0) + count
   572                  else:
   573                      super(Counter, self).update(iterable) # fast path when counter is empty
   574              else:
   575                  self_get = self.get
   576                  for elem in iterable:
   577                      self[elem] = self_get(elem, 0) + 1
   578          if kwds:
   579              self.update(kwds)
   580  
   581      def subtract(*args, **kwds):
   582          '''Like dict.update() but subtracts counts instead of replacing them.
   583          Counts can be reduced below zero.  Both the inputs and outputs are
   584          allowed to contain zero and negative counts.
   585  
   586          Source can be an iterable, a dictionary, or another Counter instance.
   587  
   588          >>> c = Counter('which')
   589          >>> c.subtract('witch')             # subtract elements from another iterable
   590          >>> c.subtract(Counter('watch'))    # subtract elements from another counter
   591          >>> c['h']                          # 2 in which, minus 1 in witch, minus 1 in watch
   592          0
   593          >>> c['w']                          # 1 in which, minus 1 in witch, minus 1 in watch
   594          -1
   595  
   596          '''
   597          if not args:
   598              raise TypeError("descriptor 'subtract' of 'Counter' object "
   599                              "needs an argument")
   600          self = args[0]
   601          args = args[1:]
   602          if len(args) > 1:
   603              raise TypeError('expected at most 1 arguments, got %d' % len(args))
   604          iterable = args[0] if args else None
   605          if iterable is not None:
   606              self_get = self.get
   607              if isinstance(iterable, Mapping):
   608                  for elem, count in iterable.items():
   609                      self[elem] = self_get(elem, 0) - count
   610              else:
   611                  for elem in iterable:
   612                      self[elem] = self_get(elem, 0) - 1
   613          if kwds:
   614              self.subtract(kwds)
   615  
   616      def copy(self):
   617          'Return a shallow copy.'
   618          return self.__class__(self)
   619  
   620      def __reduce__(self):
   621          return self.__class__, (dict(self),)
   622  
   623      def __delitem__(self, elem):
   624          'Like dict.__delitem__() but does not raise KeyError for missing values.'
   625          if elem in self:
   626              super(Counter, self).__delitem__(elem)
   627  
   628      def __repr__(self):
   629          if not self:
   630              return '%s()' % self.__class__.__name__
   631          items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
   632          return '%s({%s})' % (self.__class__.__name__, items)
   633  
   634      # Multiset-style mathematical operations discussed in:
   635      #       Knuth TAOCP Volume II section 4.6.3 exercise 19
   636      #       and at http://en.wikipedia.org/wiki/Multiset
   637      #
   638      # Outputs guaranteed to only include positive counts.
   639      #
   640      # To strip negative and zero counts, add-in an empty counter:
   641      #       c += Counter()
   642  
   643      def __add__(self, other):
   644          '''Add counts from two counters.
   645  
   646          >>> Counter('abbb') + Counter('bcc')
   647          Counter({'b': 4, 'c': 2, 'a': 1})
   648  
   649          '''
   650          if not isinstance(other, Counter):
   651              return NotImplemented
   652          result = Counter()
   653          for elem, count in self.items():
   654              newcount = count + other[elem]
   655              if newcount > 0:
   656                  result[elem] = newcount
   657          for elem, count in other.items():
   658              if elem not in self and count > 0:
   659                  result[elem] = count
   660          return result
   661  
   662      def __sub__(self, other):
   663          ''' Subtract count, but keep only results with positive counts.
   664  
   665          >>> Counter('abbbc') - Counter('bccd')
   666          Counter({'b': 2, 'a': 1})
   667  
   668          '''
   669          if not isinstance(other, Counter):
   670              return NotImplemented
   671          result = Counter()
   672          for elem, count in self.items():
   673              newcount = count - other[elem]
   674              if newcount > 0:
   675                  result[elem] = newcount
   676          for elem, count in other.items():
   677              if elem not in self and count < 0:
   678                  result[elem] = 0 - count
   679          return result
   680  
   681      def __or__(self, other):
   682          '''Union is the maximum of value in either of the input counters.
   683  
   684          >>> Counter('abbb') | Counter('bcc')
   685          Counter({'b': 3, 'c': 2, 'a': 1})
   686  
   687          '''
   688          if not isinstance(other, Counter):
   689              return NotImplemented
   690          result = Counter()
   691          for elem, count in self.items():
   692              other_count = other[elem]
   693              newcount = other_count if count < other_count else count
   694              if newcount > 0:
   695                  result[elem] = newcount
   696          for elem, count in other.items():
   697              if elem not in self and count > 0:
   698                  result[elem] = count
   699          return result
   700  
   701      def __and__(self, other):
   702          ''' Intersection is the minimum of corresponding counts.
   703  
   704          >>> Counter('abbb') & Counter('bcc')
   705          Counter({'b': 1})
   706  
   707          '''
   708          if not isinstance(other, Counter):
   709              return NotImplemented
   710          result = Counter()
   711          for elem, count in self.items():
   712              other_count = other[elem]
   713              newcount = count if count < other_count else other_count
   714              if newcount > 0:
   715                  result[elem] = newcount
   716          return result
   717  
   718  
   719  if __name__ == '__main__':
   720      pass
   721  #    # verify that instances can be pickled
   722  #    from cPickle import loads, dumps
   723  #    Point = namedtuple('Point', 'x, y', True)
   724  #    p = Point(x=10, y=20)
   725  #    assert p == loads(dumps(p))
   726  #
   727  #    # test and demonstrate ability to override methods
   728  #    class Point(namedtuple('Point', 'x y')):
   729  #        __slots__ = ()
   730  #        @property
   731  #        def hypot(self):
   732  #            return (self.x ** 2 + self.y ** 2) ** 0.5
   733  #        def __str__(self):
   734  #            return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)
   735  #
   736  #    for p in Point(3, 4), Point(14, 5/7.):
   737  #        print p
   738  #
   739  #    class Point(namedtuple('Point', 'x y')):
   740  #        'Point class with optimized _make() and _replace() without error-checking'
   741  #        __slots__ = ()
   742  #        _make = classmethod(tuple.__new__)
   743  #        def _replace(self, _map=map, **kwds):
   744  #            return self._make(_map(kwds.get, ('x', 'y'), self))
   745  #
   746  #    print Point(11, 22)._replace(x=100)
   747  #
   748  #    Point3D = namedtuple('Point3D', Point._fields + ('z',))
   749  #    print Point3D.__doc__
   750  #
   751  #    import doctest
   752  #    TestResults = namedtuple('TestResults', 'failed attempted')
   753  #    print TestResults(*doctest.testmod())