Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/sets/set_from_iterator.py
8817 views
1
r"""
2
Enumerated set from iterator
3
4
EXAMPLES:
5
6
We build a set from the iterator ``graphs`` that returns a canonical
7
representative 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
26
The 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 <[email protected]>
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
64
from sage.structure.parent import Parent
65
from sage.categories.enumerated_sets import EnumeratedSets
66
from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
67
from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets
68
from sage.categories.sets_cat import EmptySetError
69
from itertools import izip_longest
70
import os
71
from sage.misc.function_mangling import ArgumentFixer
72
from sage.misc.lazy_list import lazy_list
73
74
class EnumeratedSetFromIterator(Parent):
75
"""
76
A class for enumerated set built from an iterator.
77
78
INPUT:
79
80
- ``f`` -- a function that returns an iterable from which the set is built from
81
82
- ``args`` -- tuple -- arguments to be sent to the function ``f``
83
84
- ``kwds`` -- dictionnary -- keywords to be sent to the function ``f``
85
86
- ``name`` -- an optional name for the set
87
88
- ``category`` -- (default: ``None``) an optional category for that
89
enumerated set. If you know that your iterator will stop after a finite
90
number of steps you should set it as :class:`FiniteEnumeratedSets`, conversly if
91
you know that your iterator will run over and over you should set it as
92
:class:`InfiniteEnumeratedSets`.
93
94
- ``cache`` -- boolean (default: ``False``) -- Whether or not use a cache
95
mechanism for the iterator. If ``True``, then the function ``f`` is called
96
only once.
97
98
99
EXAMPLES::
100
101
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
102
sage: E = EnumeratedSetFromIterator(graphs, args = (7,))
103
sage: E
104
{Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, Graph on 7 vertices, ...}
105
sage: E.category()
106
Category of facade enumerated sets
107
108
The same example with a cache and a custom name::
109
110
sage: E = EnumeratedSetFromIterator(
111
... graphs,
112
... args = (8,),
113
... category = FiniteEnumeratedSets(),
114
... name = "Graphs with 8 vertices",
115
... cache = True)
116
sage: E
117
Graphs with 8 vertices
118
sage: E.unrank(3)
119
Graph on 8 vertices
120
sage: E.category()
121
Category of facade finite enumerated sets
122
123
TESTS:
124
125
The cache is compatible with multiple call to ``__iter__``::
126
127
sage: from itertools import count
128
sage: E = EnumeratedSetFromIterator(count, args=(0,), category=InfiniteEnumeratedSets(), cache=True)
129
sage: e1 = iter(E)
130
sage: e2 = iter(E)
131
sage: e1.next(), e1.next()
132
(0, 1)
133
sage: e2.next(), e2.next(), e2.next()
134
(0, 1, 2)
135
sage: e1.next(), e1.next()
136
(2, 3)
137
sage: e2.next()
138
3
139
sage: TestSuite(E).run()
140
141
sage: E = EnumeratedSetFromIterator(xsrange, args=(10,), category=FiniteEnumeratedSets(), cache=True)
142
sage: TestSuite(E).run()
143
144
.. NOTE::
145
146
In order to make the ``TestSuite`` works, the elements of the set
147
should have parents.
148
"""
149
def __init__(self, f, args=None, kwds=None, name=None, category=None, cache=False):
150
"""
151
TESTS::
152
153
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
154
sage: S = EnumeratedSetFromIterator(xsrange, (1,200,-1), category=FiniteEnumeratedSets())
155
sage: TestSuite(S).run()
156
"""
157
if category is not None:
158
Parent.__init__(self, facade = True, category = category)
159
else:
160
Parent.__init__(self, facade = True, category = EnumeratedSets())
161
162
163
if name is not None:
164
self.rename(name)
165
166
self._func = f
167
168
if args is not None:
169
self._args = args
170
if kwds is not None:
171
self._kwds = kwds
172
173
if cache:
174
self._cache = lazy_list(iter(self._func(
175
*getattr(self, '_args', ()),
176
**getattr(self, '_kwds', {}))))
177
178
def __reduce__(self):
179
r"""
180
Support for pickle.
181
182
TESTS::
183
184
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
185
sage: from sage.graphs.graph_generators import graphs
186
sage: E = EnumeratedSetFromIterator(graphs,
187
... args=(3,),
188
... category=FiniteEnumeratedSets(),
189
... name="Graphs on 3 vertices")
190
sage: E
191
Graphs on 3 vertices
192
sage: F = loads(dumps(E)); F
193
Graphs on 3 vertices
194
sage: E == F
195
True
196
"""
197
return (EnumeratedSetFromIterator,
198
(self._func, # func
199
getattr(self, '_args', None), # args
200
getattr(self, '_kwds', None), # kwds
201
getattr(self, '__custom_name', None), # name
202
self.category(), # category
203
hasattr(self, '_cache')) # cache
204
)
205
206
def _repr_(self):
207
r"""
208
Return a string representation of ``self``.
209
210
TESTS::
211
212
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
213
sage: E = EnumeratedSetFromIterator(Partitions(7,min_part=2).__iter__)
214
sage: repr(E) # indirect doctest
215
'{[7], [5, 2], [4, 3], [3, 2, 2]}'
216
sage: E = EnumeratedSetFromIterator(Partitions(9,min_part=2).__iter__)
217
sage: repr(E) # indirect doctest
218
'{[9], [7, 2], [6, 3], [5, 4], [5, 2, 2], ...}'
219
sage: E = EnumeratedSetFromIterator(Partitions(9,min_part=2).__iter__, name="Some partitions")
220
sage: repr(E) # indirect doctest
221
'Some partitions'
222
"""
223
l = []
224
i = iter(self)
225
for _ in xrange(6):
226
try:
227
l.append(i.next())
228
except StopIteration:
229
break
230
if len(l) < 6:
231
return '{' + ', '.join(repr(x) for x in l) + '}'
232
l.pop(-1)
233
return '{' + ', '.join(repr(x) for x in l) + ', ...}'
234
235
def __contains__(self, x):
236
r"""
237
Test whether ``x`` is in ``self``.
238
239
If the set is infinite, only the answer ``True`` should be expected in
240
finite time.
241
242
EXAMPLES::
243
244
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
245
sage: P = Partitions(12,min_part=2,max_part=5)
246
sage: E = EnumeratedSetFromIterator(P.__iter__)
247
sage: P([5,5,2]) in E
248
True
249
"""
250
return any(x == y for y in self)
251
252
is_parent_of = __contains__
253
254
#TODO: what should we do for comparisons of infinite sets
255
def __eq__(self, other):
256
r"""
257
Equality test.
258
259
The function returns ``True`` if and only if other is an enumerated
260
set and has the same element as ``self``.
261
262
TESTS::
263
264
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
265
sage: E4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
266
sage: F4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
267
sage: E5 = EnumeratedSetFromIterator(graphs, args=(5,), category=FiniteEnumeratedSets())
268
sage: E4 == E4
269
True
270
sage: E4 == F4
271
True
272
sage: E4 == E5
273
False
274
sage: E5 == E4
275
False
276
sage: E5 == E5
277
True
278
"""
279
if isinstance(other, EnumeratedSetFromIterator):
280
# trick to allow equality between infinite sets
281
# this assume that the function does not return randomized data!
282
if (self._func == other._func and
283
getattr(self, '_args', None) == getattr(other, '_args', None) and
284
getattr(self, '_kwds', None) == getattr(other, '_kwds', None)):
285
return True
286
287
if other in EnumeratedSets():
288
#TODO: think about what should be done at that point
289
if self not in FiniteEnumeratedSets() and other not in FiniteEnumeratedSets():
290
import warnings
291
warnings.warn("Testing equality of infinite sets which will not end in case of equality")
292
293
i1 = iter(self)
294
i2 = iter(other)
295
while True:
296
try:
297
x = i1.next()
298
except StopIteration:
299
try:
300
i2.next()
301
return False
302
except StopIteration:
303
return True
304
try:
305
y = i2.next()
306
except StopIteration:
307
return False
308
if x != y:
309
return False
310
311
def __ne__(self,other):
312
r"""
313
Difference test.
314
315
The function calls the ``__eq__`` test.
316
317
TESTS::
318
319
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
320
sage: E4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
321
sage: F4 = EnumeratedSetFromIterator(graphs, args=(4,), category=FiniteEnumeratedSets())
322
sage: E5 = EnumeratedSetFromIterator(graphs, args=(5,), category=FiniteEnumeratedSets())
323
sage: E4 != E4
324
False
325
sage: E4 != F4
326
False
327
sage: E4 != E5
328
True
329
sage: E5 != E4
330
True
331
sage: E5 != E5
332
False
333
"""
334
return not self.__eq__(other)
335
336
def __iter__(self):
337
r"""
338
Returns an iterator over the element of ``self``.
339
340
EXAMPLES::
341
342
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
343
sage: E = EnumeratedSetFromIterator(graphs, args=(8,))
344
sage: g1 = iter(E).next(); g1
345
Graph on 8 vertices
346
sage: E = EnumeratedSetFromIterator(graphs, args=(8,), cache=True)
347
sage: g2 = iter(E).next(); g2
348
Graph on 8 vertices
349
sage: g1 == g2
350
True
351
"""
352
if hasattr(self, '_cache'):
353
return iter(self._cache)
354
return iter(self._func(*getattr(self, '_args', ()), **getattr(self, '_kwds', {})))
355
356
def unrank(self, i):
357
r"""
358
Returns the element at position ``i``.
359
360
EXAMPLES::
361
362
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
363
sage: E = EnumeratedSetFromIterator(graphs, args=(8,), cache=True)
364
sage: F = EnumeratedSetFromIterator(graphs, args=(8,), cache=False)
365
sage: E.unrank(2)
366
Graph on 8 vertices
367
sage: E.unrank(2) == F.unrank(2)
368
True
369
"""
370
if hasattr(self, '_cache'):
371
return self._cache[i]
372
return super(EnumeratedSetFromIterator,self).unrank(i)
373
374
def _element_constructor_(self, el):
375
"""
376
Construct an element from ``el``.
377
378
TESTS::
379
380
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
381
sage: S = EnumeratedSetFromIterator(xrange, args=(1,4))
382
sage: S(1) # indirect doctest
383
1
384
sage: S(0) # indirect doctest
385
Traceback (most recent call last):
386
...
387
ValueError: 0 not in {1, 2, 3}
388
"""
389
if el in self:
390
return el
391
else:
392
raise ValueError("%s not in %s"%(el, self))
393
394
def clear_cache(self):
395
r"""
396
Clear the cache.
397
398
EXAMPLES::
399
400
sage: from itertools import count
401
sage: from sage.sets.set_from_iterator import EnumeratedSetFromIterator
402
sage: E = EnumeratedSetFromIterator(count, args=(1,), cache=True)
403
sage: e1 = E._cache
404
sage: e1
405
lazy list [1, 2, 3, ...]
406
sage: E.clear_cache()
407
sage: E._cache
408
lazy list [1, 2, 3, ...]
409
sage: e1 is E._cache
410
False
411
"""
412
if hasattr(self, '_cache'):
413
self._cache = lazy_list(iter(self._func(
414
*getattr(self, '_args', ()),
415
**getattr(self, '_kwds', {}))))
416
417
#
418
# Decorators
419
#
420
421
#TODO: move it in sage.misc ?
422
class Decorator:
423
r"""
424
Abstract class that manage documentation and sources of the wrapped object.
425
426
The method needs to be stored in the attribute ``self.f``
427
"""
428
def _sage_doc_(self):
429
"""
430
Provide documentation for the wrapped function.
431
432
TESTS::
433
434
sage: from sage.misc.sageinspect import sage_getdoc
435
sage: from sage.sets.set_from_iterator import Decorator
436
sage: d = Decorator()
437
sage: d.f = Integer.is_prime
438
sage: print sage_getdoc(d) # indirect doctest
439
Returns "True" if "self" is prime.
440
...
441
IMPLEMENTATION: Calls the PARI "isprime" function.
442
<BLANKLINE>
443
"""
444
from sage.misc.sageinspect import sage_getsourcelines, sage_getfile
445
f = self.f
446
if hasattr(f, "func_doc"):
447
try:
448
sourcelines = sage_getsourcelines(f)
449
except IOError:
450
sourcelines = None
451
if sourcelines is not None:
452
from sage.env import SAGE_LIB, SAGE_SRC
453
filename = sage_getfile(f)
454
# The following is a heuristics to get
455
# the file name of the cached function
456
# or method
457
if filename.startswith(SAGE_SRC):
458
filename = filename[len(SAGE_SRC):]
459
elif filename.startswith(SAGE_LIB):
460
filename = filename[len(SAGE_LIB):]
461
file_info = "File: %s (starting at line %d)\n"%(filename,sourcelines[1])
462
doc = file_info+(f.func_doc or '')
463
else:
464
doc = f.func_doc
465
else:
466
doc = f.__doc__
467
return doc
468
469
def _sage_src_(self):
470
r"""
471
Returns the source code for the wrapped function.
472
473
TESTS::
474
475
sage: from sage.misc.sageinspect import sage_getsource
476
sage: from sage.sets.set_from_iterator import Decorator
477
sage: d = Decorator()
478
sage: d.f = Rational.is_square
479
sage: print sage_getsource(d.f) # indirect doctest
480
def is_square(self):
481
...
482
return mpq_sgn(self.value) >= 0 and mpz_perfect_square_p(mpq_numref(self.value)) and mpz_perfect_square_p(mpq_denref(self.value))
483
"""
484
from sage.misc.sageinspect import sage_getsource
485
return sage_getsource(self.f)
486
487
def _sage_src_lines_(self):
488
r"""
489
Returns the list of source lines and the first line number
490
of the wrapped function.
491
492
TESTS::
493
494
sage: from sage.misc.sageinspect import sage_getsourcelines
495
sage: from sage.sets.set_from_iterator import Decorator
496
sage: d = Decorator()
497
sage: d.f = MathieuGroup.order
498
sage: S = sage_getsourcelines(d) # indirect doctest
499
sage: S[0][2]
500
' Return the number of elements of this group.\n'
501
sage: S[0][17]
502
' return Integer(1)\n'
503
"""
504
from sage.misc.sageinspect import sage_getsourcelines
505
return sage_getsourcelines(self.f)
506
507
def _sage_argspec_(self):
508
"""
509
Return the argument specification of the wrapped function or method.
510
511
TESTS::
512
513
sage: from sage.misc.sageinspect import sage_getargspec
514
sage: from sage.sets.set_from_iterator import Decorator
515
sage: d = Decorator()
516
sage: d.f = find_local_minimum
517
sage: sage_getargspec(d) # indirect doctest
518
ArgSpec(args=['f', 'a', 'b', 'tol', 'maxfun'], varargs=None, keywords=None, defaults=(1.48e-08, 500))
519
"""
520
from sage.misc.sageinspect import sage_getargspec
521
return sage_getargspec(self.f)
522
523
def __call__(self, *args, **kwds):
524
r"""
525
Call function.
526
527
Needs to be implemented in derived subclass.
528
529
TEST::
530
531
sage: from sage.sets.set_from_iterator import Decorator
532
sage: d = Decorator()
533
sage: d()
534
Traceback (most recent call last):
535
...
536
NotImplementedError
537
"""
538
raise NotImplementedError
539
540
class EnumeratedSetFromIterator_function_decorator(Decorator):
541
r"""
542
Decorator for :class:`EnumeratedSetFromIterator`.
543
544
Name could be string or a function ``(args,kwds) -> string``.
545
546
.. WARNING::
547
548
If you are going to use this with the decorator ``cached_function``,
549
you must place the ``cached_function`` first. See the example below.
550
551
EXAMPLES::
552
553
sage: from sage.sets.set_from_iterator import set_from_function
554
sage: @set_from_function
555
... def f(n):
556
... for i in xrange(n):
557
... yield i**2 + i + 1
558
sage: f(3)
559
{1, 3, 7}
560
sage: f(100)
561
{1, 3, 7, 13, 21, ...}
562
563
To avoid ambiguity, it is always better to use it with a call which
564
provides optional global initialization for the call to
565
:class:`EnumeratedSetFromIterator`::
566
567
sage: @set_from_function(category=InfiniteEnumeratedSets())
568
... def Fibonacci():
569
... a = 1; b = 2
570
... while True:
571
... yield a
572
... a,b = b,a+b
573
sage: F = Fibonacci()
574
sage: F
575
{1, 2, 3, 5, 8, ...}
576
sage: F.cardinality()
577
+Infinity
578
579
A simple example with many options::
580
581
sage: @set_from_function(
582
... name = "From %(m)d to %(n)d",
583
... category = FiniteEnumeratedSets())
584
... def f(m,n): return xsrange(m,n+1)
585
sage: E = f(3,10); E
586
From 3 to 10
587
sage: E.list()
588
[3, 4, 5, 6, 7, 8, 9, 10]
589
sage: E = f(1,100); E
590
From 1 to 100
591
sage: E.cardinality()
592
100
593
sage: f(n=100,m=1) == E
594
True
595
596
An example which mixes together ``set_from_function`` and
597
``cached_method``::
598
599
sage: @cached_function
600
... @set_from_function(
601
... name = "Graphs on %(n)d vertices",
602
... category = FiniteEnumeratedSets(),
603
... cache = True)
604
... def Graphs(n): return graphs(n)
605
sage: Graphs(10)
606
Graphs on 10 vertices
607
sage: Graphs(10).unrank(0)
608
Graph on 10 vertices
609
sage: Graphs(10) is Graphs(10)
610
True
611
612
The ``cached_function`` must go first::
613
614
sage: @set_from_function(
615
... name = "Graphs on %(n)d vertices",
616
... category = FiniteEnumeratedSets(),
617
... cache = True)
618
... @cached_function
619
... def Graphs(n): return graphs(n)
620
sage: Graphs(10)
621
Graphs on 10 vertices
622
sage: Graphs(10).unrank(0)
623
Graph on 10 vertices
624
sage: Graphs(10) is Graphs(10)
625
False
626
"""
627
def __init__(self, f=None, name=None, **options):
628
r"""
629
Initialize ``self``.
630
631
TESTS::
632
633
sage: from sage.sets.set_from_iterator import set_from_function
634
sage: F = set_from_function(category=FiniteEnumeratedSets())(xsrange)
635
sage: TestSuite(F(100)).run()
636
sage: TestSuite(F(1,5,2)).run()
637
sage: TestSuite(F(0)).run()
638
"""
639
if f is not None:
640
self.f = f
641
if hasattr(f, "func_name"):
642
self.__name__ = f.func_name
643
else:
644
self.__name__ = f.__name__
645
self.__module__ = f.__module__
646
self.af = ArgumentFixer(f)
647
if name is not None:
648
self.name = name
649
self.options = options
650
651
def __call__(self, *args, **kwds):
652
r"""
653
Build a new :class:`EnumeratedSet` by calling ``self.f`` with
654
apropriate argument. If ``f`` is ``None``, then returns a new instance
655
of :class:`EnumeratedSetFromIterator`.
656
657
EXAMPLES::
658
659
sage: from sage.sets.set_from_iterator import set_from_function
660
sage: F = set_from_function(category=FiniteEnumeratedSets())(xsrange)
661
sage: F(3)
662
{0, 1, 2}
663
sage: F(end=7,start=3)
664
{3, 4, 5, 6}
665
sage: F(10).cardinality()
666
10
667
"""
668
options = self.options
669
670
if hasattr(self, 'f'): # yet initialized
671
if hasattr(self,'name'):
672
if isinstance(self.name,str):
673
if args or kwds:
674
_,kk = self.af.fix_to_named(*args,**kwds)
675
name = self.name%dict(kk)
676
else:
677
name = self.name
678
else:
679
name = self.name(*args,**kwds)
680
return EnumeratedSetFromIterator(self.f, args, kwds, name=name, **self.options)
681
return EnumeratedSetFromIterator(self.f, args, kwds, **self.options)
682
683
else: # potential global options
684
if args == ():
685
assert len(kwds.keys()) == 1
686
f = kwds.values()[0]
687
else:
688
assert len(args) == 1
689
f = args[0]
690
return EnumeratedSetFromIterator_function_decorator(
691
f,
692
name=getattr(self,'name',None),
693
**self.options)
694
695
set_from_function = EnumeratedSetFromIterator_function_decorator
696
697
class EnumeratedSetFromIterator_method_caller(Decorator):
698
r"""
699
Caller for decorated method in class.
700
701
INPUT:
702
703
- ``inst`` -- an instance of a class
704
705
- ``f`` -- a method of a class of ``inst`` (and not of the instance itself)
706
707
- ``name`` -- optional -- either a string (which may contains substitution
708
rules from argument or a function args,kwds -> string.
709
710
- ``options`` -- any option accepted by :class:`EnumeratedSetFromIterator`
711
"""
712
def __init__(self, inst, f, name=None, **options):
713
r"""
714
Initialize ``self``.
715
716
TESTS::
717
718
sage: from sage.sets.set_from_iterator import DummyExampleForPicklingTest
719
sage: d = DummyExampleForPicklingTest()
720
sage: d.f()
721
{10, 11, 12, 13, 14, ...}
722
723
It is possible to pickle/unpickle the class and the instance::
724
725
sage: loads(dumps(DummyExampleForPicklingTest))().f()
726
{10, 11, 12, 13, 14, ...}
727
sage: loads(dumps(d)).f()
728
{10, 11, 12, 13, 14, ...}
729
730
But not the enumerated set::
731
732
sage: loads(dumps(d.f()))
733
Traceback (most recent call last):
734
...
735
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed
736
"""
737
self.inst = inst
738
self.f = f
739
self.af = ArgumentFixer(self.f)
740
if hasattr(f, "func_name"):
741
self.__name__ = f.func_name
742
else:
743
self.__name__ = f.__name__
744
self.__module__ = f.__module__
745
746
self.name = name
747
self.options = options
748
749
def __call__(self,*args,**kwds):
750
r"""
751
Returns an instance of :class:`EnumeratedSetFromIterator` with
752
proper argument.
753
754
TESTS::
755
756
sage: from sage.sets.set_from_iterator import set_from_method
757
sage: class A:
758
... @set_from_method(name = lambda self,n: str(self)*n)
759
... def f(self,n):
760
... return xsrange(n)
761
... def __repr__(self):
762
... return "A"
763
sage: a = A()
764
sage: a.f(3) # indirect doctest
765
AAA
766
sage: A.f(a,3) # indirect doctest
767
AAA
768
sage: [x for x in a.f(6)] # indirect doctest
769
[0, 1, 2, 3, 4, 5]
770
"""
771
if self.inst is not None:
772
args = (self.inst,) + args
773
if self.name:
774
if isinstance(self.name,str):
775
aa,kk = self.af.fix_to_named(*args,**kwds)
776
name = self.name%dict(kk)
777
else:
778
name = self.name(*args, **kwds)
779
return EnumeratedSetFromIterator(self.f, args, kwds, name, **self.options)
780
return EnumeratedSetFromIterator(self.f, args, kwds, **self.options)
781
782
def __get__(self, inst, cls):
783
r"""
784
Get a :class:`EnumeratedSetFromIterator_method_caller` bound to a
785
specific instance of the class of the cached method.
786
787
.. NOTE::
788
789
:class:`EnumeratedSetFromIterator_method_caller` has a separate
790
``__get__`` because of the special behavior of category framework
791
for element classes which are not of extension type (see
792
:meth:`sage.structure.element.Element.__get__`).
793
794
TESTS::
795
796
sage: from sage.sets.set_from_iterator import set_from_method
797
sage: from sage.structure.misc import getattr_from_other_class
798
sage: class A:
799
... stop = 10000
800
... @set_from_method
801
... def f(self,start):
802
... return xsrange(start,self.stop)
803
sage: a = A()
804
sage: getattr_from_other_class(a, A, 'f')(4)
805
{4, 5, 6, 7, 8, ...}
806
807
sage: class B:
808
... stop = 10000
809
... @set_from_method(category=FiniteEnumeratedSets())
810
... def f(self,start):
811
... return xsrange(start,self.stop)
812
sage: b = B()
813
sage: getattr_from_other_class(b, B, 'f')(2)
814
{2, 3, 4, 5, 6, ...}
815
"""
816
return EnumeratedSetFromIterator_method_caller(
817
inst, self.f,
818
self.name,
819
**self.options)
820
821
class EnumeratedSetFromIterator_method_decorator(object):
822
r"""
823
Decorator for enumerated set built from a method.
824
825
INPUT:
826
827
- ``f`` -- Optional function from which are built the enumerated sets at
828
each call
829
830
- ``name`` -- Optional string (which may contains substitution rules from
831
argument) or a function ``(args,kwds) -> string``.
832
833
- any option accepted by :class:`EnumeratedSetFromIterator`.
834
835
EXAMPLES::
836
837
sage: from sage.sets.set_from_iterator import set_from_method
838
sage: class A():
839
... def n(self): return 12
840
... @set_from_method
841
... def f(self): return xsrange(self.n())
842
sage: a = A()
843
sage: print a.f.__class__
844
sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
845
sage: a.f()
846
{0, 1, 2, 3, 4, ...}
847
sage: A.f(a)
848
{0, 1, 2, 3, 4, ...}
849
850
A more complicated example with a parametrized name::
851
852
sage: class B():
853
... @set_from_method(
854
... name = "Graphs(%(n)d)",
855
... category = FiniteEnumeratedSets())
856
... def graphs(self, n): return graphs(n)
857
sage: b = B()
858
sage: G3 = b.graphs(3)
859
sage: G3
860
Graphs(3)
861
sage: G3.cardinality()
862
4
863
sage: G3.category()
864
Category of facade finite enumerated sets
865
sage: B.graphs(b,3)
866
Graphs(3)
867
868
And a last example with a name parametrized by a function::
869
870
sage: class D():
871
... def __init__(self, name): self.name = str(name)
872
... def __str__(self): return self.name
873
... @set_from_method(
874
... name = lambda self,n: str(self)*n,
875
... category = FiniteEnumeratedSets())
876
... def subset(self, n):
877
... return xsrange(n)
878
sage: d = D('a')
879
sage: E = d.subset(3); E
880
aaa
881
sage: E.list()
882
[0, 1, 2]
883
sage: F = d.subset(n=10); F
884
aaaaaaaaaa
885
sage: F.list()
886
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
887
888
.. TODO::
889
890
It is not yet possible to use ``set_from_method`` in conjunction with
891
``cached_method``.
892
"""
893
def __init__(self, f=None, **options):
894
r"""
895
Initialize ``self``.
896
897
TESTS:
898
899
We test if pickling works correctly on the Permutation class (in
900
:mod:`sage.combinat.permutation`) because its method ``bruhat_succ``
901
and ``bruhat_pred`` are decorated with ``set_from_method``::
902
903
sage: from sage.combinat.permutation import Permutation
904
sage: loads(dumps(Permutation))
905
<class 'sage.combinat.permutation.Permutation'>
906
sage: p = Permutation([3,2,1])
907
sage: loads(dumps(p)) == p
908
True
909
"""
910
if f is not None:
911
import types
912
self.f = f
913
if hasattr(f,"func_name"):
914
self.__name__ = f.func_name
915
self.__module__ = f.__module__
916
917
else:
918
if hasattr(f, '__module__'):
919
self.__module__ = f.__module__
920
elif hasattr(f, 'im_func'):
921
self.__module__ = f.im_func.__module__
922
923
if hasattr(f, '__name__'):
924
self.__name__ = f.__name__
925
elif hasattr(f, 'im_func'):
926
self.__name__ = f.im_func.__name__
927
928
self.options = options
929
930
def __call__(self, f):
931
r"""
932
Trick if :class:`EnumeratedSetFromIterator_method` was created with
933
some options and is called with a function as argument.
934
935
TESTS::
936
937
sage: from sage.sets.set_from_iterator import set_from_method
938
sage: class A:
939
... @set_from_method() # indirect doctest
940
... def f(self):
941
... return xsrange(3)
942
sage: a = A()
943
sage: a.f()
944
{0, 1, 2}
945
"""
946
return EnumeratedSetFromIterator_method_decorator(f,**self.options)
947
948
def __get__(self, inst, cls):
949
r"""
950
TESTS::
951
952
sage: from sage.sets.set_from_iterator import set_from_method
953
sage: class A():
954
... def n(self): return 12
955
... @set_from_method
956
... def f(self): return xsrange(self.n())
957
sage: a = A()
958
sage: print A.f.__class__
959
sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
960
sage: print a.f.__class__
961
sage.sets.set_from_iterator.EnumeratedSetFromIterator_method_caller
962
"""
963
# You would hardly ever see an instance of this class alive.
964
return EnumeratedSetFromIterator_method_caller(inst, self.f, **self.options)
965
966
set_from_method = EnumeratedSetFromIterator_method_decorator
967
968
class DummyExampleForPicklingTest:
969
r"""
970
Class example to test pickling with the decorator :class:`set_from_method`.
971
972
.. WARNING::
973
974
This class is intended to be used in doctest only.
975
976
EXAMPLES::
977
978
sage: from sage.sets.set_from_iterator import DummyExampleForPicklingTest
979
sage: DummyExampleForPicklingTest().f()
980
{10, 11, 12, 13, 14, ...}
981
"""
982
start = 10
983
stop = 100
984
@set_from_method
985
def f(self):
986
r"""
987
Returns the set between ``self.start`` and ``self.stop``.
988
989
EXAMPLES::
990
991
sage: from sage.sets.set_from_iterator import DummyExampleForPicklingTest
992
sage: d = DummyExampleForPicklingTest()
993
sage: d.f()
994
{10, 11, 12, 13, 14, ...}
995
sage: d.start = 4
996
sage: d.stop = 200
997
sage: d.f()
998
{4, 5, 6, 7, 8, ...}
999
"""
1000
from sage.misc.misc import xsrange
1001
return xsrange(self.start, self.stop)
1002
1003