1. # Refer to examples in ReversibleDict.__str__
  2.  
  3. class PrintableList(list):
  4.    
  5.     def __init__(self, list_, separator=', ', separate_last=False):
  6.         self.separator = separator
  7.         self.separate_last = separate_last
  8.         list.__init__(self, list_)
  9.    
  10.     def __str__(self):
  11.         """
  12.        Return a valid English string representation.
  13.        
  14.        Example:
  15.        >>> for i in range(5): print(i, PrintableList(range(i)))
  16.        ...
  17.        (0, [])
  18.        (1, [0])
  19.        (2, [0, 1])
  20.        (3, [0, 1, 2])
  21.        (4, [0, 1, 2, 3])
  22.        """
  23.        
  24.         separator = self.separator
  25.         separator_last = separator if self.separate_last else ' '
  26.         separator_last = '{0}and '.format(separator_last)
  27.        
  28.         s = (str(i) for i in self)
  29.         s = separator.join(s)
  30.         s = separator_last.join(s.rsplit(separator, 1))
  31.        
  32.         return s
  33.    
  34.  
  35. class ReversibleDict(dict):
  36.    
  37.     def reversed(self, sort_values=True):
  38.         """
  39.        Return a reversed dict, with common values in the original dict
  40.        grouped into a list in the returned dict.
  41.        
  42.        Example:
  43.        >>> d = ReversibleDict({'a': 3, 'c': 2, 'b': 2, 'e': 3, 'd': 1, 'f': 2})
  44.        >>> d.reversed()
  45.        {1: ['d'], 2: ['b', 'c', 'f'], 3: ['a', 'e']}
  46.        """
  47.        
  48.         revdict = {}
  49.         for k, v in self.iteritems():
  50.             revdict.setdefault(v, []).append(k)
  51.         if sort_values:
  52.             revdict = dict((k, sorted(v)) for k, v in revdict.items())
  53.         return revdict
  54.    
  55.     def _reversed_tuple_revlensorted(self):
  56.         """
  57.        Return a tuple created from the reversed dict's items (see the
  58.        `reversed` method), with the items in the tuple being reverse sorted by
  59.        the length of the reversed dict's values.
  60.        
  61.        Example:
  62.        >>> d = ReversibleDict({'a': 3, 'c': 2, 'b': 2, 'e': 3, 'd': 1, 'f': 2})
  63.        >>> d._reversed_tuple_revlensorted()
  64.        ((2, ['b', 'c', 'f']), (3, ['a', 'e']), (1, ['d']))
  65.        """
  66.        
  67.         revitems = self.reversed().items()
  68.         sortkey = lambda i: (len(i[1]), i[0])
  69.         revtuple = tuple(sorted(revitems, key=sortkey, reverse=True))
  70.         return revtuple
  71.    
  72.     def __str__(self):
  73.         """
  74.        Return a string representation of the reversed dict's items (see the
  75.        `reversed` method), sorted per the `_reversed_tuple_revlensorted`
  76.        method.
  77.        
  78.        Example
  79.        >>> print(ReversibleDict({'a':3, 'c':2, 'b':2, 'e':3, 'd':1, 'f':2}))
  80.        b, c and f (2); a and e (3); and d (1)
  81.        >>> print(ReversibleDict({'a': 3, 'c': 2}))
  82.        a (3) and c (2)
  83.        """
  84.         revtuple = self._reversed_tuple_revlensorted()
  85.        
  86.         revstrs = ('{0} ({1})'.format(PrintableList(values), key)
  87.                    for key, values in revtuple)
  88.         pl_args = ('; ', True) if (max(len(i[1]) for i in revtuple) > 1) else ()
  89.         revstrs = PrintableList(revstrs, *pl_args)
  90.         revstr = str(revstrs)
  91.         return revstr