Ticket #12518: trac_12518-enumerated_set_from_iterator.patch

File trac_12518-enumerated_set_from_iterator.patch, 34.8 KB (added by Vincent Delecroix, 10 years ago)
  • doc/en/reference/structure.rst

    # HG changeset patch
    # User Vincent Delecroix <20100.delecroix at gmail.com>
    # Date 1354256463 -3600
    # Node ID c364129a599663161191b6e9e514d73958b6b3a7
    # Parent  fe587eb09b0381dc3d815a6363e61504b12f250a
    Added a decorator which will automatically return a combinatorial class from a function which provides an interator.
    * * *
    trac 12518
    Implementation of enumerated set from a function that returns an iterator. This patch improved
    the previous implementation of CombinatorialClassFromIterator in sage.combinat.combinat and fit
    with the category framework.
    
    diff --git a/doc/en/reference/structure.rst b/doc/en/reference/structure.rst
    a b  
    2626   sage/sets/set
    2727   sage/sets/disjoint_set
    2828   sage/sets/disjoint_union_enumerated_sets
     29   sage/sets/set_from_iterator
    2930   sage/sets/finite_enumerated_set
    3031   sage/sets/finite_set_maps
    3132   sage/sets/finite_set_map_cy
  • sage/combinat/combinat.py

    diff --git a/sage/combinat/combinat.py b/sage/combinat/combinat.py
    a b  
    15551555        """
    15561556        return MapCombinatorialClass(self, f, name)
    15571557
     1558def CombinatorialClassFromIterator(name, f, args, kwds):
     1559    from sage.sets.enumerated_set_from_iterator import EnumeratedSetFromIterator
     1560    from sage.misc.misc import deprecation
     1561    deprecation("sage.combinat.combinat.CombinatorialClassFromIterator has been replaced by sage.sets.enumerated_set_from_iterator.EnumeratedSetFromIterator")
     1562    return EnumeratedSetFromIterator(f,args,kwds,name=name)
     1563
     1564def combinatorial_class_from_iterator(name):
     1565    from sage.sets.set_from_iterator import set_from_method
     1566    from sage.misc.misc import deprecation
     1567    deprecation("sage.combinat.combinat.combinatorial_class_from_iterator has been replaced by sage.sets.enumerated_set_from_method")
     1568    return set_from_method(name=name)
     1569
    15581570class FilteredCombinatorialClass(CombinatorialClass):
    15591571    def __init__(self, combinatorial_class, f, name=None):
    15601572        """
  • sage/combinat/permutation.py

    diff --git a/sage/combinat/permutation.py b/sage/combinat/permutation.py
    a b  
    4545import itertools
    4646import __builtin__
    4747from combinat import CombinatorialClass, CombinatorialObject, catalan_number, InfiniteAbstractCombinatorialClass
     48from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
     49from sage.sets.set_from_iterator import set_from_method
    4850import copy
    4951from necklace import Necklaces
    5052from sage.misc.misc import uniq
     
    21602162        """
    21612163        return __builtin__.list(self.bruhat_inversions_iterator())
    21622164
     2165
    21632166    def bruhat_inversions_iterator(self):
    21642167        """
    21652168        Returns the iterator for the inversions of p such that the
     
    21872190                    if ok:
    21882191                        yield [i,j]
    21892192
    2190 
     2193    @set_from_method(
     2194            name="Successors of %(self)s in the Bruhat order",
     2195            category=FiniteEnumeratedSets())
    21912196    def bruhat_succ(self):
    2192         r"""
    2193         Returns a list of the permutations strictly greater than p in the
    2194         Bruhat order such that there is no permutation between one of those
    2195         and p.
    2196        
     2197        """
     2198        The set of permutations that are strictly greater than this in
     2199        the Bruhat order such that there is no permutation between one of
     2200        those and p.
     2201
    21972202        EXAMPLES::
    21982203       
    2199             sage: Permutation([6,1,4,5,2,3]).bruhat_succ()
    2200             [[6, 4, 1, 5, 2, 3],
    2201              [6, 2, 4, 5, 1, 3],
    2202              [6, 1, 5, 4, 2, 3],
    2203              [6, 1, 4, 5, 3, 2]]
    2204         """
    2205         return __builtin__.list(self.bruhat_succ_iterator())
    2206 
    2207     def bruhat_succ_iterator(self):
    2208         """
    2209         An iterator for the permutations that are strictly greater than p
    2210         in the Bruhat order such that there is no permutation between one
    2211         of those and p.
    2212        
    2213         EXAMPLES::
    2214        
    2215             sage: [x for x in Permutation([6,1,4,5,2,3]).bruhat_succ_iterator()]
     2204            sage: f = Permutation([6,1,4,5,2,3]).bruhat_succ(); f
     2205            Successors of [6, 1, 4, 5, 2, 3] in the Bruhat order
     2206
     2207            sage: f.list()
    22162208            [[6, 4, 1, 5, 2, 3],
    22172209             [6, 2, 4, 5, 1, 3],
    22182210             [6, 1, 5, 4, 2, 3],
     
    22272219            pp[z[1]] = p[z[0]]
    22282220            yield Permutation(pp)
    22292221
    2230 
    2231 
     2222    @set_from_method(
     2223            name="Predecessors of %(self)s in the Bruhat order",
     2224            category=FiniteEnumeratedSets())
    22322225    def bruhat_pred(self):
    2233         r"""
    2234         Returns a list of the permutations strictly smaller than p in the
     2226        """
     2227        Returns the set of permutations strictly smaller than p in the
    22352228        Bruhat order such that there is no permutation between one of those
    22362229        and p.
    22372230       
    22382231        EXAMPLES::
    22392232       
    2240             sage: Permutation([6,1,4,5,2,3]).bruhat_pred()
    2241             [[1, 6, 4, 5, 2, 3],
    2242              [4, 1, 6, 5, 2, 3],
    2243              [5, 1, 4, 6, 2, 3],
    2244              [6, 1, 2, 5, 4, 3],
    2245              [6, 1, 3, 5, 2, 4],
    2246              [6, 1, 4, 2, 5, 3],
    2247              [6, 1, 4, 3, 2, 5]]
    2248         """
    2249         return __builtin__.list(self.bruhat_pred_iterator())
    2250 
    2251     def bruhat_pred_iterator(self):
    2252         """
    2253         An iterator for the permutations strictly smaller than p in the
    2254         Bruhat order such that there is no permutation between one of those
    2255         and p.
    2256        
    2257         EXAMPLES::
    2258        
    2259             sage: [x for x in Permutation([6,1,4,5,2,3]).bruhat_pred_iterator()]
     2233            sage: f = Permutation([6,1,4,5,2,3]).bruhat_pred(); f
     2234            Predecessors of [6, 1, 4, 5, 2, 3] in the Bruhat order
     2235            sage: f.list()
    22602236            [[1, 6, 4, 5, 2, 3],
    22612237             [4, 1, 6, 5, 2, 3],
    22622238             [5, 1, 4, 6, 2, 3],
  • new file sage/sets/set_from_iterator.py

    diff --git a/sage/sets/set_from_iterator.py b/sage/sets/set_from_iterator.py
    new file mode 100644
    - +  
     1r"""
     2Enumerated set from iterator
     3
     4EXAMPLES:
     5
     6We build a set from the iterator ``graphs`` that returns a canonical
     7representative for each isomorphism class of graphs::
     8
     9    sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     10    sage: E = EnumeratedSetFromIterator(
     11    ...     graphs,
     12    ...     name = "Graphs",
     13    ...     category = InfiniteEnumeratedSets(),
     14    ...     cache = True)
     15    sage: E
     16    Graphs
     17    sage: E.unrank(0)
     18    Graph on 0 vertices
     19    sage: E.unrank(4)
     20    Graph on 3 vertices
     21    sage: E.cardinality()
     22    +Infinity
     23    sage: E.category()
     24    Category of facade infinite enumerated sets
     25
     26The module also provides decorator for functions and methods::
     27
     28    sage: from sage.sets.set_from_iterator import set_from_function
     29    sage: @set_from_function
     30    ... def f(n): return xsrange(n)
     31    sage: f(3)
     32    {0, 1, 2}
     33    sage: f(5)
     34    {0, 1, 2, 3, 4}
     35    sage: f(100)
     36    {0, 1, 2, 3, 4, ...}
     37
     38    sage: from sage.sets.set_from_iterator import set_from_method
     39    sage: class A:
     40    ...    @set_from_method
     41    ...    def f(self,n):
     42    ...        return xsrange(n)
     43    sage: a = A()
     44    sage: a.f(3)
     45    {0, 1, 2}
     46    sage: f(100)
     47    {0, 1, 2, 3, 4, ...}
     48"""
     49#*****************************************************************************
     50#  Copyright (C) 2012 Vincent Delecroix <vincent.delecroix@gmail.com>
     51#
     52#  Distributed under the terms of the GNU General Public License (GPL)
     53#
     54#    This code is distributed in the hope that it will be useful,
     55#    but WITHOUT ANY WARRANTY; without even the implied warranty of
     56#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     57#    General Public License for more details.
     58#
     59#  The full text of the GPL is available at:
     60#
     61#                  http://www.gnu.org/licenses/
     62#******************************************************************************
     63
     64from sage.structure.parent import Parent
     65from sage.categories.enumerated_sets import EnumeratedSets
     66from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
     67from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets
     68from sage.categories.sets_cat import EmptySetError
     69from itertools import izip_longest
     70import os
     71from sage.misc.function_mangling import ArgumentFixer
     72
     73from sage.misc.lazy_list import lazy_list
     74
     75class EnumeratedSetFromIterator(Parent):
     76    """
     77    A class for enumerated set built from an iterator.
     78
     79    INPUT:
     80
     81    - ``f`` - a function that returns an iterable from which the set is built from.
     82
     83    - ``args`` - tuple - the argument that are sent to the function ``f``.
     84
     85    - ``kwds`` - dictionnary - keywords that are sent to the function ``f``.
     86
     87    - ``name`` - string - an optional name for the set.
     88
     89    - ``category`` - None or category (default: None) - a category for that
     90      enumerated set. If you know that your iterator will stop after a finite
     91      number of steps you should set it as FiniteEnumeratedSets(), conversly if
     92      you know that your iterator will run over and over you should set it as
     93      InfiniteEnumeratedSets().
     94
     95    - ``cache`` - boolean (default: ``False``) - Whether or not use a cache
     96      mechanism for the iterator. If ``True``, then the function ``f`` is called
     97      only once.
     98
     99
     100    EXAMPLES::
     101
     102        sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     103        sage: E = EnumeratedSetFromIterator(graphs, args = (7,))
     104        sage: E
     105        {Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, ...}
     106        sage: E.category()
     107        Category of facade enumerated sets
     108
     109    The same example with a cache and a custom name::
     110
     111        sage: E = EnumeratedSetFromIterator(
     112        ...      graphs,
     113        ...      args = (8,),
     114        ...      category = FiniteEnumeratedSets(),
     115        ...      name = "Graphs with 8 vertices",
     116        ...      cache = True)
     117        sage: E
     118        Graphs with 8 vertices
     119        sage: E.unrank(3)
     120        Graph on 8 vertices
     121        sage: E.category()
     122        Category of facade finite enumerated sets
     123
     124    TESTS:
     125
     126    The cache is compatible with multiple call to __iter__::
     127
     128        sage: from itertools import count
     129        sage: E = EnumeratedSetFromIterator(count, args=(0,), category=InfiniteEnumeratedSets(), cache=True)
     130        sage: e1 = iter(E)
     131        sage: e2 = iter(E)
     132        sage: e1.next(), e1.next()
     133        (0, 1)
     134        sage: e2.next(), e2.next(), e2.next()
     135        (0, 1, 2)
     136        sage: e1.next(), e1.next()
     137        (2, 3)
     138        sage: e2.next()
     139        3
     140        sage: TestSuite(E).run()
     141
     142        sage: E = EnumeratedSetFromIterator(xsrange, args=(10,), category=FiniteEnumeratedSets(), cache=True)
     143        sage: TestSuite(E).run()
     144
     145    Remark that in order to make the TestSuite works, the elements of the set
     146    should have parents.
     147    """
     148    def __init__(self, f, args=None, kwds=None, name=None, category=None, cache=False):
     149        """
     150        TESTS::
     151
     152            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     153            sage: S = EnumeratedSetFromIterator(xsrange, (1,200,-1), category=FiniteEnumeratedSets())
     154            sage: TestSuite(S).run()
     155        """
     156        if category is not None:
     157            Parent.__init__(self, facade = True, category = category)
     158        else:
     159            Parent.__init__(self, facade = True, category = EnumeratedSets())
     160
     161
     162        if name is not None:
     163            self.rename(name)
     164
     165        self._func = f
     166
     167        if args is not None:
     168            self._args = args
     169        if kwds is not None:
     170            self._kwds = kwds
     171
     172        if cache:
     173            self._cache = lazy_list(iter(self._func(
     174                                         *getattr(self, '_args', ()),
     175                                        **getattr(self, '_kwds', {}))))
     176
     177    def __reduce__(self):
     178        r"""
     179        Support for pickle.
     180
     181        TESTS::
     182
     183            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     184            sage: from sage.graphs.graph_generators import graphs
     185            sage: E = EnumeratedSetFromIterator(graphs,
     186            ...      args=(3,),
     187            ...      category=FiniteEnumeratedSets(),
     188            ...      name="Graphs on 3 vertices")
     189            sage: E
     190            Graphs on 3 vertices
     191            sage: F = loads(dumps(E)); F
     192            Graphs on 3 vertices
     193            sage: E == F
     194            True
     195        """
     196        return (EnumeratedSetFromIterator,
     197                (self._func,                           # func
     198                 getattr(self, '_args', None),         # args
     199                 getattr(self, '_kwds', None),         # kwds
     200                 getattr(self, '__custom_name', None), # name
     201                 self.category(),                      # category
     202                 hasattr(self, '_cache'))              # cache
     203                )
     204
     205    def _repr_(self):
     206        r"""
     207        TESTS::
     208
     209            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     210            sage: E = EnumeratedSetFromIterator(Partitions(7,min_part=2).__iter__)
     211            sage: repr(E)    # indirect doctest
     212            '{[7], [5, 2], [4, 3], [3, 2, 2]}'
     213            sage: E = EnumeratedSetFromIterator(Partitions(9,min_part=2).__iter__)
     214            sage: repr(E)    # indirect doctest
     215            '{[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], ...}'
     216            sage: E = EnumeratedSetFromIterator(Partitions(9,min_part=2).__iter__, name="Some partitions")
     217            sage: repr(E)    # indirect doctest
     218            'Some partitions'
     219        """
     220        l = []
     221        i = iter(self)
     222        for _ in xrange(6):
     223            try:
     224                l.append(i.next())
     225            except StopIteration:
     226                break
     227        if len(l) < 6:
     228            return '{' + ', '.join(str(x) for x in l) + '}'
     229        l.pop(-1)
     230        return '{' + ', '.join(str(x) for x in l) + ', ...}'
     231
     232    def __contains__(self, x):
     233        r"""
     234        Test whether ``x`` is in ``self``.
     235
     236        If the set is infinite, only the answer ``True`` should be expected in
     237        finite time.
     238
     239        EXAMPLES::
     240
     241            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     242            sage: P = Partitions(12,min_part=2,max_part=5)
     243            sage: E = EnumeratedSetFromIterator(P.__iter__)
     244            sage: P([5,5,2]) in E
     245            True
     246        """
     247        return any(x == y for y in self)
     248
     249    is_parent_of = __contains__
     250
     251    #TODO: what should we do for comparisons of infinite sets
     252    def __eq__(self, other):
     253        r"""
     254        Equality test.
     255
     256        The function returns ``True`` if and only if other is an enumerated set and
     257        has the same element as self.
     258
     259        TESTS::
     260
     261            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     262            sage: E4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     263            sage: F4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     264            sage: E5 = EnumeratedSetFromIterator(graphs, args=(5,), category=FiniteEnumeratedSets())
     265            sage: E4 == E4
     266            True
     267            sage: E4 == F4
     268            True
     269            sage: E4 == E5
     270            False
     271            sage: E5 == E4
     272            False
     273            sage: E5 == E5
     274            True
     275        """
     276        if isinstance(other, EnumeratedSetFromIterator):
     277            # trick to allow equality between infinite sets
     278            # this assume that the function does not return randomized data!
     279            if (self._func == other._func and
     280                getattr(self, '_args', None) == getattr(other, '_args', None) and
     281                getattr(self, '_kwds', None) == getattr(other, '_kwds', None)):
     282                return True
     283
     284        if other in EnumeratedSets():
     285            #TODO: think about what should be done at that point
     286            if self not in FiniteEnumeratedSets() and other not in FiniteEnumeratedSets():
     287                import warnings
     288                warnings.warn("Testing equality of infinite sets which will not end in case of equality")
     289
     290            i1 = iter(self)
     291            i2 = iter(other)
     292            while True:
     293                try:
     294                    x = i1.next()
     295                except StopIteration:
     296                    try:
     297                        i2.next()
     298                        return False
     299                    except StopIteration:
     300                        return True
     301                try:
     302                    y = i2.next()
     303                except StopIteration:
     304                    return False
     305                if x != y:
     306                    return False
     307
     308    def __ne__(self,other):
     309        r"""
     310        Difference test.
     311
     312        The function calls the __eq__ test.
     313
     314        TESTS::
     315
     316            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     317            sage: E4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     318            sage: F4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
     319            sage: E5 = EnumeratedSetFromIterator(graphs, args=(5,), category=FiniteEnumeratedSets())
     320            sage: E4 != E4
     321            False
     322            sage: E4 != F4
     323            False
     324            sage: E4 != E5
     325            True
     326            sage: E5 != E4
     327            True
     328            sage: E5 != E5
     329            False
     330        """
     331        return not self.__eq__(other)
     332
     333    def __iter__(self):
     334        r"""
     335        Returns an iterator over the element of self.
     336
     337        EXAMPLES::
     338
     339            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     340            sage: E = EnumeratedSetFromIterator(graphs, args=(8,))
     341            sage: g1 = iter(E).next(); g1
     342            Graph on 8 vertices
     343            sage: E = EnumeratedSetFromIterator(graphs, args=(8,), cache=True)
     344            sage: g2 = iter(E).next(); g2
     345            Graph on 8 vertices
     346            sage: g1 == g2
     347            True
     348        """
     349        if hasattr(self, '_cache'):
     350            return iter(self._cache)
     351        return iter(self._func(*getattr(self, '_args', ()), **getattr(self, '_kwds', {})))
     352
     353    def unrank(self, i):
     354        r"""
     355        Returns the element at position ``i``.
     356
     357        EXAMPLES::
     358
     359            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     360            sage: E = EnumeratedSetFromIterator(graphs, args=(8,), cache=True)
     361            sage: F = EnumeratedSetFromIterator(graphs, args=(8,), cache=False)
     362            sage: E.unrank(2)
     363            Graph on 8 vertices
     364            sage: E.unrank(2) == F.unrank(2)
     365            True
     366        """
     367        if hasattr(self, '_cache'):
     368            return self._cache[i]
     369        return super(EnumeratedSetFromIterator,self).unrank(i)
     370
     371    def _element_constructor_(self, el):
     372        """
     373        TESTS::
     374
     375            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     376            sage: S = EnumeratedSetFromIterator(xrange, args=(1,4))
     377            sage: S(1)  # indirect doctest
     378            1
     379            sage: S(0)  # indirect doctest
     380            Traceback (most recent call last):
     381            ...
     382            ValueError: 0 not in {1, 2, 3}
     383        """
     384        if el in self:
     385            return el
     386        else:
     387            raise ValueError("%s not in %s"%(el, self))
     388
     389    def clear_cache(self):
     390        r"""
     391        Clear the cache.
     392
     393        EXAMPLES::
     394
     395            sage: from itertools import count
     396            sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
     397            sage: E = EnumeratedSetFromIterator(count, args=(1,), cache=True)
     398            sage: E._cache
     399            lazy list [1, 2, 3, ...]
     400        """
     401        if hasattr(self, '_cache'):
     402            self._cache = self._func(
     403                     *getattr(self, '_args', ()),
     404                    **getattr(self, '_kwds', {}))
     405
     406#
     407# Decorators
     408#
     409
     410#TODO: move it in sage.misc ?
     411class Decorator:
     412    r"""
     413    Abstract class that manage documentation and sources of the wrapped object.
     414    """
     415    def _sage_doc_(self):
     416        """
     417        Provide documentation for the wrapped function.
     418
     419        TESTS::
     420
     421            sage: from sage.misc.sageinspect import sage_getdoc
     422            sage: p = Permutation([4, 1, 3, 2])
     423            sage: sage_getdoc(p.bruhat_succ)[:35] # indirect doctest
     424            '   The set of permutations that are'
     425        """
     426        from sage.misc.sageinspect import sage_getsourcelines, sage_getfile
     427        f = self.f
     428        if hasattr(f, "func_doc"):
     429            try:
     430                sourcelines = sage_getsourcelines(f)
     431            except IOError:
     432                sourcelines = None
     433            if sourcelines is not None:
     434                import os
     435                SAGE_ROOT = os.environ['SAGE_ROOT']
     436                filename = sage_getfile(f)
     437                # The following is a heuristics to get
     438                # the file name of the cached function
     439                # or method
     440                if filename.startswith(SAGE_ROOT+'/devel/sage/'):
     441                    filename = filename[len(SAGE_ROOT+'/devel/sage/'):]
     442                elif 'site-packages/' in filename:
     443                    filename = filename.split('site-packages/',1)[1]
     444                file_info = "File: %s (starting at line %d)\n"%(filename,sourcelines[1])
     445                doc = file_info+(f.func_doc or '')
     446            else:
     447                doc = f.func_doc
     448        else:
     449            doc = f.__doc__
     450        return doc
     451
     452    def _sage_src_(self):
     453        """
     454        Returns the source code for the wrapped function.
     455
     456        TESTS::
     457
     458            sage: from sage.misc.sageinspect import sage_getsource
     459            sage: p = Permutation([4, 1, 3, 2])
     460            sage: print sage_getsource(p.bruhat_succ)[:156] # indirect doctest
     461                @set_from_method(
     462                        name="Successors of %(self)s in the Bruhat order",
     463                        category=FiniteEnumeratedSets())
     464                def bruhat_succ(self):
     465        """
     466        from sage.misc.sageinspect import sage_getsource
     467        return sage_getsource(self.f)
     468
     469    def _sage_src_lines_(self):
     470        r"""
     471        Returns the list of source lines and the first line number
     472        of the wrapped function.
     473
     474        TESTS::
     475
     476            sage: from sage.misc.sageinspect import sage_getsourcelines
     477            sage: p = Permutation([4, 1, 3, 2])
     478            sage: S = sage_getsourcelines(p.bruhat_succ)[0] # indirect doctest
     479            sage: print S[0]
     480                @set_from_method(
     481            sage: print S[1]
     482                        name="Successors of %(self)s in the Bruhat order",
     483            sage: print S[2]
     484                        category=FiniteEnumeratedSets())
     485            sage: print S[3]
     486                def bruhat_succ(self):
     487        """
     488        from sage.misc.sageinspect import sage_getsourcelines
     489        return sage_getsourcelines(self.f)
     490
     491    def _sage_argspec_(self):
     492        """
     493        Return the argspec of the wrapped function or method.
     494
     495        TESTS::
     496
     497            sage: from sage.misc.sageinspect import sage_getargspec
     498            sage: p = Permutation([3,1,4,2])
     499            sage: sage_getargspec(p.bruhat_succ) # indirect doctest
     500            ArgSpec(args=['self'], varargs=None, keywords=None, defaults=None)
     501        """
     502        from sage.misc.sageinspect import sage_getargspec
     503        return sage_getargspec(self.f)
     504
     505class EnumeratedSetFromIterator_function_decorator(Decorator):
     506    r"""
     507    Decorator for EnumeratedSetFromIterator.
     508
     509    Name could be string or a function (args,kwds) -> string
     510
     511    EXAMPLES::
     512
     513        sage: from sage.sets.set_from_iterator import set_from_function
     514        sage: @set_from_function
     515        ... def f(n):
     516        ...    for i in xrange(n):
     517        ...        yield i**2 + i + 1
     518        sage: f(3)
     519        {1, 3, 7}
     520        sage: f(100)
     521        {1, 3, 7, 13, 21, ...}
     522
     523    To avoid ambiguity, it is always better to use it with a call which provides
     524    optional global initialization for the call to EnumeratedSetFromIterator::
     525
     526        sage: @set_from_function(category=InfiniteEnumeratedSets())
     527        ... def Fibonacci():
     528        ...    a = 1; b = 2
     529        ...    while True:
     530        ...       yield a
     531        ...       a,b = b,a+b
     532        sage: F = Fibonacci()
     533        sage: F
     534        {1, 2, 3, 5, 8, ...}
     535        sage: F.cardinality()
     536        +Infinity
     537
     538    A simple example with many options::
     539
     540        sage: @set_from_function(
     541        ...        name = "From %(m)d to %(n)d",
     542        ...        category = FiniteEnumeratedSets())
     543        ... def f(m,n): return xsrange(m,n+1)
     544        sage: E = f(3,10); E
     545        From 3 to 10
     546        sage: E.list()
     547        [3, 4, 5, 6, 7, 8, 9, 10]
     548        sage: E = f(1,100); E
     549        From 1 to 100
     550        sage: E.cardinality()
     551        100
     552        sage: f(n=100,m=1) == E
     553        True
     554
     555    An example which mixes together set_from_function and cached_method::
     556
     557        sage: @cached_function
     558        ... @set_from_function(
     559        ...    name = "Graphs on %(n)d vertices",
     560        ...    category = FiniteEnumeratedSets(),
     561        ...    cache = True)
     562        ... def Graphs(n): return graphs(n)
     563        sage: Graphs(10)
     564        Graphs on 10 vertices
     565        sage: Graphs(10).unrank(0)
     566        Graph on 10 vertices
     567        sage: Graphs(10) is Graphs(10)
     568        True
     569
     570    """
     571    def __init__(self, f=None, name=None, **options):
     572        r"""
     573        TESTS::
     574
     575            sage: from sage.sets.set_from_iterator import set_from_function
     576            sage: F = set_from_function(category=FiniteEnumeratedSets())(xsrange)
     577            sage: TestSuite(F(100)).run()
     578            sage: TestSuite(F(1,5,2)).run()
     579            sage: TestSuite(F(0)).run()
     580        """
     581        if f is not None:
     582            self.f = f
     583            if hasattr(f, "func_name"):
     584                self.__name__ = f.func_name
     585            else:
     586                self.__name__ = f.__name__
     587            self.__module__ = f.__module__
     588            self.af = ArgumentFixer(f)
     589        if name is not None:
     590            self.name = name
     591        self.options = options
     592
     593    def __call__(self, *args, **kwds):
     594        r"""
     595        Build a new EnumeratedSet by calling self.f with apropriate argument. If
     596        f is None, then returns a new instance of EnumeratedSetFromIterator
     597
     598        EXAMPLES::
     599
     600            sage: from sage.sets.set_from_iterator import set_from_function
     601            sage: F = set_from_function(category=FiniteEnumeratedSets())(xsrange)
     602            sage: F(3)
     603            {0, 1, 2}
     604            sage: F(end=7,start=3)
     605            {3, 4, 5, 6}
     606            sage: F(10).cardinality()
     607            10
     608        """
     609        options = self.options
     610
     611        if hasattr(self, 'f'): # yet initialized
     612            if hasattr(self,'name'):
     613                if isinstance(self.name,str):
     614                    if args or kwds:
     615                        _,kk = self.af.fix_to_named(*args,**kwds)
     616                        name = self.name%dict(kk)
     617                    else:
     618                        name = self.name
     619                else:
     620                    name = self.name(*args,**kwds)
     621                return EnumeratedSetFromIterator(self.f, args, kwds, name=name, **self.options)
     622            return EnumeratedSetFromIterator(self.f, args, kwds, **self.options)
     623
     624        else: # potential global options
     625            if args == ():
     626                assert len(kwds.keys()) == 1
     627                f = kwds.values()[0]
     628            else:
     629                assert len(args) == 1
     630                f = args[0]
     631            return EnumeratedSetFromIterator_function_decorator(
     632                f,
     633                name=getattr(self,'name',None),
     634                **self.options)
     635
     636set_from_function = EnumeratedSetFromIterator_function_decorator
     637
     638class EnumeratedSetFromIterator_method_caller(Decorator):
     639    r"""
     640    Caller for decorated method in class.
     641
     642    INPUT:
     643
     644    - ``inst`` - an instance of a class
     645
     646    - ``f`` - a method of a class of ``inst`` (and not of the instance itself)
     647
     648    - ``name`` - optional - either a string (which may contains substitution
     649      rules from argument or a function args,kwds -> string.
     650
     651    - ``options`` - options to be sent to EnumeratedSetFromIterator
     652    """
     653    def __init__(self, inst, f, name=None, **options):
     654        r"""
     655        TESTS:
     656
     657        We test if pickling works correctly on the Permutation class (in
     658        sage.combinat.permutation) because its method bruhat_succ and
     659        bruhat_pred are decorated with set_from_method::
     660
     661            sage: from sage.combinat.permutation import Permutation_class
     662            sage: loads(dumps(Permutation_class))
     663            <class 'sage.combinat.permutation.Permutation_class'>
     664            sage: p = Permutation([3,2,1])
     665            sage: loads(dumps(p)) == p
     666            True
     667        """
     668        self.inst = inst
     669        self.f = f
     670        self.af = ArgumentFixer(self.f)
     671        if hasattr(f, "func_name"):
     672            self.__name__ = f.func_name
     673        else:
     674            self.__name__ = f.__name__
     675        self.__module__ = f.__module__
     676
     677        if name is not None:
     678            self.name = name
     679        self.options = options
     680
     681    def __call__(self,*args,**kwds):
     682        r"""
     683        Returns an instance of EnumeratedSetFromIterator with proper argument.
     684
     685        TESTS::
     686
     687            sage: from sage.sets.set_from_iterator import set_from_method
     688            sage: class A:
     689            ...    @set_from_method(name = lambda self,n: str(self)*n)
     690            ...    def f(self,n):
     691            ...        return xsrange(n)
     692            ...    def __repr__(self):
     693            ...        return "A"
     694            sage: a = A()
     695            sage: a.f(3)                         # indirect doctest
     696            AAA
     697            sage: [x for x in a.f(6)]            # indirect doctest
     698            [0, 1, 2, 3, 4, 5]
     699        """
     700        args = (self.inst,) + args
     701        if hasattr(self, 'name'):
     702            if isinstance(self.name,str):
     703                aa,kk = self.af.fix_to_named(*args,**kwds)
     704                name = self.name%dict(kk)
     705            else:
     706                name = self.name(*args, **kwds)
     707            return EnumeratedSetFromIterator(self.f, args, kwds, name, **self.options)
     708        return EnumeratedSetFromIterator(self.f, args, kwds, **self.options)
     709
     710class EnumeratedSetFromIterator_method_decorator(object):
     711    r"""
     712    Decorator for enumerated set built from a method.
     713
     714    INPUT:
     715
     716    - ``f`` - function (optional) - the function from which are built the
     717      enumerated sets at each call
     718
     719    - ``name`` - optional - either a string (which may contains substitution
     720      rules from argument or a function args,kwds -> string.
     721
     722    - any option accepted by EnumeratedSetFromIterator.
     723
     724    EXAMPLES::
     725
     726        sage: from sage.sets.set_from_iterator import set_from_method
     727        sage: class A():
     728        ...    def n(self): return 12
     729        ...    @set_from_method
     730        ...    def f(self): return xsrange(self.n())
     731        sage: a = A()
     732        sage: print a.f.__class__
     733        sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
     734        sage: a.f()
     735        {0, 1, 2, 3, 4, ...}
     736
     737    A more complicated example with a parametrized name::
     738
     739        sage: class B():
     740        ...    @set_from_method(
     741        ...        name = "Graphs(%(n)d)",
     742        ...        category = FiniteEnumeratedSets())
     743        ...    def graphs(self, n): return graphs(n)
     744        sage: b = B()
     745        sage: G3 = b.graphs(3)
     746        sage: G3
     747        Graphs(3)
     748        sage: G3.cardinality()
     749        4
     750        sage: G3.category()
     751        Category of facade finite enumerated sets
     752
     753    And a last example with a name parametrized by a function::
     754
     755        sage: class D():
     756        ...    def __init__(self, name): self.name = str(name)
     757        ...    def __str__(self): return self.name
     758        ...    @set_from_method(
     759        ...        name = lambda self,n: str(self)*n,
     760        ...        category = FiniteEnumeratedSets())
     761        ...    def subset(self, n):
     762        ...        return xsrange(n)
     763        sage: d = D('a')
     764        sage: E = d.subset(3); E
     765        aaa
     766        sage: E.list()
     767        [0, 1, 2]
     768        sage: F = d.subset(n=10); F
     769        aaaaaaaaaa
     770        sage: F.list()
     771        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     772
     773    TODO: It is not yet possible to use set_from_method in conjunction with
     774    cached_method.
     775    """
     776    def __init__(self, f=None, **options):
     777        r"""
     778        TESTS:
     779
     780        We test if pickling works correctly on the Permutation class (in
     781        sage.combinat.permutation) because its method bruhat_succ and
     782        bruhat_pred are decorated with set_from_method::
     783
     784            sage: from sage.combinat.permutation import Permutation_class
     785            sage: loads(dumps(Permutation_class))
     786            <class 'sage.combinat.permutation.Permutation_class'>
     787            sage: p = Permutation([3,2,1])
     788            sage: loads(dumps(p)) == p
     789            True
     790        """
     791        if f is not None:
     792            import types
     793            self.f = f
     794            if hasattr(f,"func_name"):
     795                self.__name__ = f.func_name
     796                self.__module__ = f.__module__
     797
     798            else:
     799                if hasattr(f, '__module__'):
     800                    self.__module__ = f.__module__
     801                elif hasattr(f, 'im_func'):
     802                    self.__module__ = f.im_func.__module__
     803
     804                if hasattr(f, '__name__'):
     805                    self.__name__ = f.__name__
     806                elif hasattr(f, 'im_func'):
     807                    self.__name__ = f.im_func.__name__
     808
     809        self.options = options
     810
     811    def __call__(self, f):
     812        r"""
     813        Trick if EnumeratedSetFromIterator_method was created with some options
     814        and is called with a function as argument.
     815
     816        TEST::
     817
     818            sage: from sage.sets.set_from_iterator import set_from_method
     819            sage: class A:
     820            ...    @set_from_method()    # indirect doctest
     821            ...    def f(self):
     822            ...        return xsrange(3)
     823            sage: a = A()
     824            sage: a.f()
     825            {0, 1, 2}
     826        """
     827        return EnumeratedSetFromIterator_method_decorator(f,**self.options)
     828
     829    def __get__(self, inst, cls):
     830        r"""
     831        TESTS::
     832
     833            sage: from sage.sets.set_from_iterator import set_from_method
     834            sage: class A():
     835            ...    def n(self): return 12
     836            ...    @set_from_method
     837            ...    def f(self): return xsrange(self.n())
     838            sage: a = A()
     839            sage: print a.f.__class__
     840            sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
     841
     842        You would hardly ever see an instance of this class alive.
     843        """
     844        return EnumeratedSetFromIterator_method_caller(inst, self.f, **self.options)
     845
     846set_from_method = EnumeratedSetFromIterator_method_decorator
     847