Pointed Set type

Gribouillis 6 Tallied Votes 266 Views Share

This snippet implements a pointed set, that is to say an ordinary set of elements with a distinguished element called the basepoint of the set. For example the seven days of the week could be stored in a pointed set, the basepoint being monday because today is monday. It is a very simple structure but use cases are easily found.

#!/usr/bin/env python
# -*-coding: utf8-*-
# Author: Gribouillis for the python forum at www.daniweb.com
# Date: 2014 May 26
# License: Public Domain
# Use this code freely

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from collections import MutableSet

__version__ = '0.0.2'

if repr(set([0])).startswith('{'): # python 3
    def _set_repr(aset):
        s = repr(aset)
        return 'set([{}])'.format(s[1:-1])
else: # python 2
    _set_repr = repr

class PointedSet(MutableSet):
    """PointedSet(iterable) --> new PointedSet instance.

    It is an error if @iterable is empty.
    
    This class implements a non empty ordinary set with a distinguished element
    called the basepoint. This element can be read and written through the attribute
    
        self.basepoint
        
    The selected element always belong to the set. Failing operations, such as
    attempts to empty the set or define an invalid basepoint may raise KeyError
    or TypeError.
    
    The class implements the MutableSet abstract interface defined in module
    collections.
    
    The "pointed set" terminology is borrowed from mathematics. See for example
    https://en.wikipedia.org/wiki/Pointed_set
    

        >>> s = PointedSet(range(10))
        >>> s                               # doctest: +ELLIPSIS
        PointedSet([...])
        >>> len(s)
        10
        >>> 0 <= s.basepoint < 10
        True
        >>> s &= range(5)
        >>> s                               # doctest: +ELLIPSIS
        PointedSet([...])
        >>> sorted(s)
        [0, 1, 2, 3, 4]
        >>> s.basepoint = 3
        >>> s.basepoint
        3
        >>> s.basepoint = 7
        Traceback (most recent call last):
        ...
        KeyError: 7
        >>> s.basepoint
        3
        >>> s -= range(4)
        >>> s.basepoint
        4
        >>> s
        PointedSet([4])
        >>> s.pop()
        Traceback (most recent call last):
        ...
        TypeError: discard would empty PointedSet

    """
    __slots__ = ('_aggregate', '_basepoint')
    
    def __init__(self, iterable):
        self._aggregate = set(iterable)
        try:
            self._basepoint = next(iter(self._aggregate))
        except StopIteration:
            raise TypeError('PointedSet initialized from empty iterable')

    @property
    def basepoint(self):
        return self._basepoint
    
    @basepoint.setter
    def basepoint(self, item):
        if item in self._aggregate:
            self._basepoint = item
        else:
            raise KeyError(item)
        
    def __contains__(self, item):
        return item in self._aggregate
    
    def __iter__(self):
        return iter(self._aggregate)
    
    def __len__(self):
        return len(self._aggregate)
    
    def add(self, item):
        self._aggregate.add(item)
        
    def discard(self, item):
        if item in self._aggregate:
            if len(self._aggregate) == 1:
                raise TypeError('discard would empty PointedSet')
            else:
                self._aggregate.discard(item)
                if self._basepoint not in self._aggregate:
                    self._basepoint = next(iter(self._aggregate))
                    
    def __repr__(self):
        r = repr(self._aggregate)
        return "PointedS{}".format(_set_repr(self._aggregate)[1:])


if __name__ == "__main__":
    import doctest
    doctest.testmod()