Ticket #3661: trac_3661-1.patch

File trac_3661-1.patch, 13.8 KB (added by mhansen, 11 years ago)
  • sage/combinat/all.py

    # HG changeset patch
    # User Nicolas M. Thiery <nthiery@users.sf.net>
    # Date 1216181237 18000
    # Node ID b870354dc81bb12bca773c0cf302ba8a1678792b
    # Parent  65fd39fc98c8409605a1086bdd9ce08856bda0f3
    Initial commit for families.
    
    diff -r 65fd39fc98c8 -r b870354dc81b sage/combinat/all.py
    a b  
    8383
    8484from multichoose_nk import MultichooseNK
    8585
    86 
     86from family import Family, FiniteFamily, LazyFamily
  • new file sage/combinat/family.py

    diff -r 65fd39fc98c8 -r b870354dc81b sage/combinat/family.py
    - +  
     1from sage.combinat.combinat import CombinatorialClass
     2from sage.combinat.finite_class import FiniteCombinatorialClass_l
     3
     4def Family(indices, function = None, name = None, hidden_keys = [], hidden_function = None):
     5    r"""
     6    A Family is an associative container which models a family
     7    (f_i)_{i in I}. Then, f[i] returns the element of the family
     8    indexed by i. Whenever available, set and combinatorial class
     9    operations (counting, iteration, listing) on the family are
     10    induced from those of the index set.
     11
     12    There are several available implementations (classes) for
     13    different usages; Family serves as a factory, and will create
     14    instances of the appropriate classes depending on its arguments.
     15
     16    EXAMPLES:
     17
     18        In its simplest form, a list l by itself is considered as the
     19        family $(l[i]_{i in I})$ where $I$ is the range $0\dots,len(l)$.
     20        So Family(l) just returns it.
     21
     22            sage: f = Family([1,2,3])
     23            sage: f
     24            [1, 2, 3]
     25
     26        A family can also be constructed from a dictionary t. The
     27        resulting family is very close to t, except that the elements
     28        of the family are the values of t. Here, we define the family
     29        (f_i)_{i in \{3,4,7\}} with f_3='a', f_4='b', and f_7='d':
     30
     31            sage: f = Family({3: 'a', 4: 'b', 7: 'd'})
     32            sage: f
     33            Finite family {3: 'a', 4: 'b', 7: 'd'}
     34            sage: f[7]
     35            'd'
     36            sage: len(f)
     37            3
     38            sage: list(f)
     39            ['a', 'b', 'd']
     40            sage: [ x for x in f ]
     41            ['a', 'b', 'd']
     42            sage: f.keys()
     43            [3, 4, 7]
     44            sage: 'b' in f       # todo: not implemented
     45            True
     46            sage: 'e' in f       # todo: not implemented
     47            False
     48
     49        A familly can also be constructed by its index set $I$ and a
     50        function $f$, as in $(f(i))_{i in I}$:
     51
     52            sage: f = Family([3,4,7], lambda i: 2*i)
     53            sage: f
     54            Finite family {3: 6, 4: 8, 7: 14}
     55            sage: f.keys()
     56            [3, 4, 7]
     57            sage: f[7]
     58            14
     59            sage: list(f)
     60            [6, 8, 14]
     61            sage: [ x for x in f]
     62            [6, 8, 14]
     63            sage: len(f)
     64            3
     65
     66        By default, if the index set is a list, all images are
     67        computed right away, and stored in an internal
     68        dictionary. Note that this requires all the elements of the
     69        list to be hashable. One can ask instead for the images $f(i)$
     70        to be computed lazily, when needed:
     71
     72            sage: f = LazyFamily([3,4,7], lambda i: 2*i)
     73            sage: f
     74            Lazy family (f(i))_{i in [3, 4, 7]}
     75            sage: f[7]
     76            14
     77            sage: list(f)
     78            [6, 8, 14]
     79            sage: [ x for x in f]
     80            [6, 8, 14]
     81            sage: len(f)
     82            3
     83
     84        This allows in particular for modeling infinite families:
     85            sage: f = Family(ZZ, lambda i: 2*i)
     86            sage: f
     87            Lazy family (f(i))_{i in Integer Ring}
     88            sage: f.keys()
     89            Integer Ring
     90            sage: f[1]
     91            2
     92            sage: f[-5]
     93            -10
     94            sage: i = f.__iter__()
     95            sage: i.next(), i.next(), i.next(), i.next(), i.next()
     96            (0, 2, -2, 4, -4)
     97
     98        Caveat: families with lazy behavior cannot be pickled
     99
     100
     101        Finally, it can occasionally be useful to add some hidden
     102        elements in a familly, which are accessible as f[i], but
     103        do not appear in the keys or the container operations.
     104
     105            sage: f = Family([3,4,7], lambda i: 2*i, hidden_keys=[2])
     106            sage: f
     107            Finite family {3: 6, 4: 8, 7: 14}
     108            sage: f.keys()
     109            [3, 4, 7]
     110            sage: f.hidden_keys()
     111            [2]
     112            sage: f[7]
     113            14
     114            sage: f[2]
     115            4
     116            sage: list(f)
     117            [6, 8, 14]
     118            sage: [ x for x in f]
     119            [6, 8, 14]
     120            sage: len(f)
     121            3
     122
     123        The following example illustrates when the function is actually called:
     124            sage: def compute_value(i):
     125            ...       print('computing 2*'+str(i))
     126            ...       return 2*i
     127            sage: f = Family([3,4,7], compute_value, hidden_keys=[2])
     128            computing 2*3
     129            computing 2*4
     130            computing 2*7
     131            sage: f
     132            Finite family {3: 6, 4: 8, 7: 14}
     133            sage: f.keys()
     134            [3, 4, 7]
     135            sage: f.hidden_keys()
     136            [2]
     137            sage: f[7]
     138            14
     139            sage: f[2]
     140            computing 2*2
     141            4
     142            sage: f[2]
     143            4
     144            sage: list(f)
     145            [6, 8, 14]
     146            sage: [ x for x in f]
     147            [6, 8, 14]
     148            sage: len(f)
     149            3
     150
     151        Here is a close variant where the function for the hidden keys
     152        is different from that for the other keys:
     153
     154            sage: f = Family([3,4,7], lambda i: 2*i, hidden_keys=[2], hidden_function = lambda i: 3*i)
     155            sage: f
     156            Finite family {3: 6, 4: 8, 7: 14}
     157            sage: f.keys()
     158            [3, 4, 7]
     159            sage: f.hidden_keys()
     160            [2]
     161            sage: f[7]
     162            14
     163            sage: f[2]
     164            6
     165            sage: list(f)
     166            [6, 8, 14]
     167            sage: [ x for x in f]
     168            [6, 8, 14]
     169            sage: len(f)
     170            3
     171
     172        Family behaves the same way with FiniteCombinatorialClass
     173        instances and lists.  This feature will eventually disapear
     174        when FiniteCombinatorialClass won't be needed anymore.
     175       
     176            sage: f = Family(FiniteCombinatorialClass([1,2,3]))
     177            sage: f
     178            Combinatorial class with elements in [1, 2, 3]
     179
     180            sage: f = Family(FiniteCombinatorialClass([3,4,7]), lambda i: 2*i)
     181            sage: f
     182            Finite family {3: 6, 4: 8, 7: 14}
     183            sage: f.keys()
     184            [3, 4, 7]
     185            sage: f[7]
     186            14
     187            sage: list(f)
     188            [6, 8, 14]
     189            sage: [ x for x in f]
     190            [6, 8, 14]
     191            sage: len(f)
     192            3
     193
     194        TESTS:
     195            sage: f = Family({1:'a', 2:'b', 3:'c'})
     196            sage: f
     197            Finite family {1: 'a', 2: 'b', 3: 'c'}
     198            sage: f[2]
     199            'b'
     200            sage: loads(dumps(f)) == f
     201            True
     202
     203            sage: f = Family(range(1,27), lambda i: chr(i+96))
     204            sage: f
     205                Finite family {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i', 10: 'j', 11: 'k', 12: 'l', 13: 'm', 14: 'n', 15: 'o', 16: 'p', 17: 'q', 18: 'r', 19: 's', 20: 't', 21: 'u', 22: 'v', 23: 'w', 24: 'x', 25: 'y', 26: 'z'}
     206            sage: f[2]
     207            'b'
     208    """
     209    assert(type(hidden_keys) == list)
     210    if function == None and hidden_keys == []:
     211        if type(indices) == list or isinstance(indices, FiniteCombinatorialClass_l) or isinstance(indices, FiniteFamily) or isinstance(indices, LazyFamily):
     212            return indices
     213        if type(indices) == dict:
     214            return FiniteFamily(indices)
     215    else:
     216        if type(indices) == list or isinstance(indices, FiniteCombinatorialClass_l):
     217            if not hidden_keys == []:
     218                if hidden_function is None:
     219                    hidden_function = function
     220                return FiniteFamilyWithHiddenKeys(dict([(i, function(i)) for i in indices]),
     221                                                  hidden_keys, hidden_function)
     222            else:
     223                return FiniteFamily(dict([(i, function(i)) for i in indices]), keys = indices)
     224        elif hidden_keys == [] and hidden_function is None:
     225            return LazyFamily(indices, function)
     226    raise NotImplementedError
     227
     228class AbstractFamily(CombinatorialClass):
     229
     230    def hidden_keys(self):
     231        """
     232        Returns the hidden keys of the family, if any
     233        """
     234        return []
     235
     236    def zip(self, f, other, name = None):
     237        """
     238        Given two families with same index set $I$ (and same hidden keys
     239        if relevant), returns the family $( f(self[i], other[i]) )_{i in I}$
     240
     241        TODO: generalize to any number of families and merge with map?
     242
     243        EXAMPLES:
     244            sage: f = Family({3: 'a', 4: 'b', 7: 'd'})
     245            sage: g = Family({3: '1', 4: '2', 7: '3'})
     246            sage: h = f.zip(lambda x,y: x+y, g)
     247            sage: list(h)
     248            ['a1', 'b2', 'd3']
     249
     250        """
     251        assert(self.keys() == other.keys())
     252        assert(self.hidden_keys() == other.hidden_keys())
     253        return Family(self.keys(), lambda i: f(self[i],other[i]), hidden_keys = self.hidden_keys(), name = name)
     254
     255    def map(self, f, name = None):
     256        """
     257        Returns the family $( f(self[i]) )_{i in I}$, where
     258        $I$ is the index set of self.
     259
     260        TODO: good name?
     261
     262        EXAMPLES:
     263            sage: f = Family({3: 'a', 4: 'b', 7: 'd'})
     264            sage: g = f.map(lambda x: x+'1')
     265            sage: list(g)
     266            ['a1', 'b1', 'd1']
     267        """
     268        return Family(self.keys(), lambda i: f(self[i]), hidden_keys = self.hidden_keys(), name = name)
     269
     270class FiniteFamily(AbstractFamily):
     271    r"""
     272    A FiniteFamily is an associative container which models a finite
     273    family (f_i)_{i in I}. Its elements $f_i$ are therefore its
     274    values. Instances should be created via the Family factory, which
     275    see for further examples and tests.
     276
     277    EXAMPLES:
     278    We define the family (f_i)_{i in \{3,4,7\}} with f_3=a, f_4=b, and f_7=d
     279        sage: f = FiniteFamily({3: 'a', 4: 'b', 7: 'd'})
     280
     281    Individual elements are accessible as in a usual dictionary:
     282        sage: f[7]
     283        'd'
     284
     285    And the other usual dictionary operations are also available:
     286        sage: len(f)
     287        3
     288        sage: f.keys()
     289        [3, 4, 7]
     290
     291    However f behaves as a container for the $f_i$'s:
     292        sage: list(f)
     293        ['a', 'b', 'd']
     294        sage: [ x for x in f ]
     295        ['a', 'b', 'd']
     296        sage: f == loads(dumps(f))
     297        True
     298    """
     299
     300    def __init__(self, dictionary, keys = None):
     301        # TODO: use keys to specify the order of the elements
     302        self.dictionary = dictionary
     303        self.keys = dictionary.keys
     304        self.__iter__ = dictionary.itervalues
     305        self.list = dictionary.values # should not be required
     306        self.values = dictionary.values
     307        self.__getitem__ = dictionary.__getitem__
     308
     309    def __repr__(self):
     310        return "Finite family %s"%self.dictionary
     311
     312    def count(self):
     313        return len(self.dictionary)
     314
     315    # Why isn't it sufficient to set self.__getitem__ as above?
     316    def __getitem__(self, i):
     317        return self.__getitem__(i)
     318
     319    # For the pickle and copy modules
     320    def __getstate__(self):
     321        return {'dictionary': self.dictionary}
     322
     323    def __setstate__(self, state):
     324        self.__init__(state['dictionary'])
     325
     326class FiniteFamilyWithHiddenKeys(FiniteFamily):
     327    r"""
     328    A close variant of FiniteFamily where the family contains some
     329    hidden keys whose corresponding values are computed lazily (and
     330    remembered). Instances should be created via the Family factory,
     331    which see for examples and tests.
     332
     333    Caveat: instances of this class cannot be pickled, because the
     334    hidden_function itself cannot.
     335    """
     336    def __init__(self, dictionary, hidden_keys, hidden_function):
     337        FiniteFamily.__init__(self, dictionary)
     338        self._hidden_keys = hidden_keys
     339        self.hidden_function = hidden_function
     340        self.hidden_dictionary = {}
     341
     342        # would be better to define as usual method
     343        # any better to unset the def of __getitem__ by FiniteFamily?
     344        #self.__getitem__ = lambda i: dictionary[i] if dictionary.has_key(i) else hidden_dictionary[i]
     345
     346    def __getitem__(self, i):
     347        if self.dictionary.has_key(i):
     348            return self.dictionary[i]
     349        if not self.hidden_dictionary.has_key(i):
     350            if not i in self._hidden_keys:
     351                raise KeyError
     352            self.hidden_dictionary[i] = self.hidden_function(i)
     353        return self.hidden_dictionary[i]
     354
     355    def hidden_keys(self):
     356        return self._hidden_keys
     357
     358    def __getstate__(self):
     359        raise NotImplementedError
     360
     361class LazyFamily(AbstractFamily):
     362    r"""
     363    A LazyFamily(I, f) is an associative container which models the
     364    (possibly infinite) family (f(i))_{i in I}.
     365
     366    Instances should be created via the Family factory, which see for
     367    examples and tests.
     368    """
     369
     370    def __init__(self, set, function, name = "f"):
     371        self.set = set
     372        self.name = name
     373        self.__getitem__ = function
     374
     375    def __repr__(self):
     376        return "Lazy family (%s(i))_{i in %s}"%(self.name,self.set)
     377
     378    def keys(self):
     379        return self.set
     380
     381    def __iter__(self):
     382        for i in self.set.__iter__():
     383            yield self[i]
     384
     385    # Should disappear
     386    iterator = __iter__
     387
     388    # Why isn't it sufficient to set self.__getitem__ as above?
     389    def __getitem__(self, i):
     390        return self.__getitem__(i)