Lazy lists¶
A lazy list is an iterator that behaves like a list and possesses a cache mechanism. A lazy list is potentially infinite and speed performances of the cache is comparable with Python lists. One major difference with original Python list is that lazy list are immutable. The advantage is that slices share memory.
EXAMPLES:
sage: from sage.misc.lazy_list import lazy_list
sage: P = lazy_list(Primes())
sage: P[100]
547
sage: P[10:34]
lazy list [31, 37, 41, ...]
sage: P[12:23].list()
[41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83]
sage: f = lazy_list((i**2 - 3*i for i in range(10)))
sage: print(" ".join(str(i) for i in f))
0 -2 -2 0 4 10 18 28 40 54
sage: i1 = iter(f)
sage: i2 = iter(f)
sage: [next(i1), next(i1)]
[0, -2]
sage: [next(i2), next(i2)]
[0, -2]
sage: [next(i1), next(i2)]
[-2, -2]
It is possible to prepend a list to a lazy list:
sage: from itertools import count
sage: l = [3,7] + lazy_list(i**2 for i in count())
sage: l
lazy list [3, 7, 0, ...]
But, naturally, not the other way around:
sage: lazy_list(i-1 for i in count()) + [3,2,5]
Traceback (most recent call last):
...
TypeError: can only add list to lazy_list
You can easily create your own class inheriting from lazy_list_generic
. You
should call the lazy_list_generic
constructor (optionally with some
precomputed values for the cache) and implement the method _new_slice
that
returns a new chunk of data at each call. Here is an example of implementation
of the Thue–Morse word that is obtained as the fixed point of the substitution
\(0 \to 01\) and \(1 \to 10\):
sage: from sage.misc.lazy_list import lazy_list_generic
sage: class MyThueMorseWord(lazy_list_generic):
....: def __init__(self):
....: self.i = 1
....: lazy_list_generic.__init__(self, cache=[0,1])
....: def _new_slice(self):
....: letter = self.get(self.i)
....: self.i += 1
....: return [0,1] if letter == 0 else [1,0]
sage: w = MyThueMorseWord()
sage: w
lazy list [0, 1, 1, ...]
sage: all(w[i] == ZZ(i).popcount()%2 for i in range(100))
True
sage: w[:500].list() == w[:1000:2].list()
True
Alternatively, you can create the lazy list from an update function:
sage: def thue_morse_update(values):
....: n = len(values)
....: if n == 0:
....: letter = 0
....: else:
....: assert n%2 == 0
....: letter = values[n//2]
....: values.append(letter)
....: values.append(1-letter)
sage: w2 = lazy_list(update_function=thue_morse_update)
sage: w2
lazy list [0, 1, 1, ...]
sage: w2[:500].list() == w[:500].list()
True
You can also create user-defined classes (Python) and extension types (Cython)
inheriting from lazy_list_generic
.
In that case you would better implement directly the method
_update_cache_up_to
. See the examples in this file with the classes
lazy_list_from_iterator
and lazy_list_from_function
.
Classes and Methods¶
-
sage.misc.lazy_list.
lazy_list
(data=None, initial_values=None, start=None, stop=None, step=None, update_function=None)¶ Return a lazy list.
INPUT:
data
– data to create a lazy list from. This can be- a (possibly infinite) iterable,
- a function (that takes as input an integer
n
and return then
-th term of the list), - or a standard Python container
list
ortuple
.
initial_values
– the beginning of the sequence that will not be computed from thedata
provided.update_function
– you can also construct a lazy list from a function that takes as input a list of precomputed values and updates it with some more values.
Note
If you want finer tuning of the constructor you can directly instantiate the classes associated to lazy lists that are
lazy_list_generic
,lazy_list_from_iterator
,lazy_list_from_function
.EXAMPLES:
The basic construction of lazy lists.
sage: from sage.misc.lazy_list import lazy_list
Iterators:
sage: from itertools import count sage: lazy_list(count()) lazy list [0, 1, 2, ...]
Functions:
sage: lazy_list(lambda n: (n**2)%17) lazy list [0, 1, 4, ...]
Plain lists:
sage: lazy_list([1,5,7,2]) lazy list [1, 5, 7, ...]
If a function is only defined for large values, you can provide the beginning of the sequence manually:
sage: l = lazy_list(divisors, [None]) sage: l lazy list [None, [1], [1, 2], ...]
Lazy lists behave like lists except that they are immutable:
sage: l[3::5] lazy list [[1, 3], [1, 2, 4, 8], [1, 13], ...]
If your lazy list is finite, you can obtain the underlying list with the method \(.list()\):
sage: l[30:50:5].list() [[1, 2, 3, 5, 6, 10, 15, 30], [1, 5, 7, 35], [1, 2, 4, 5, 8, 10, 20, 40], [1, 3, 5, 9, 15, 45]]
-
sage.misc.lazy_list.
lazy_list_formatter
(L, name='lazy list', separator=', ', more='...', opening_delimiter='[', closing_delimiter=']', preview=3)¶ Return a string representation of
L
.INPUT:
L
– an iterable objectname
– (default:'lazy list'
) a string appearing at first position (i.e., in front of the actual values) in the representationopening_delimiter
– (default:'['
) a string heading the shown entriesclosing_delimiter
– (default:']'
) a string trailing the shown entriesseparator
– (default:', '
) a string appearing between two entriesmore
– (default:'...'
) a string indicating that not all entries of the list are shownpreview
– (default:3
) an integer specifying the number of elements shown in the representation string
OUTPUT:
A string.
EXAMPLES:
sage: from sage.misc.lazy_list import lazy_list_formatter sage: lazy_list_formatter(srange(3, 1000, 5), name='list') 'list [3, 8, 13, ...]'
sage: from sage.misc.lazy_list import lazy_list sage: L = lazy_list(Primes()); L lazy list [2, 3, 5, ...] sage: repr(L) == lazy_list_formatter(L) True sage: lazy_list_formatter(L, name='primes') 'primes [2, 3, 5, ...]' sage: lazy_list_formatter(L, opening_delimiter='(', closing_delimiter=')') 'lazy list (2, 3, 5, ...)' sage: lazy_list_formatter(L, opening_delimiter='', closing_delimiter='') 'lazy list 2, 3, 5, ...' sage: lazy_list_formatter(L, separator='--') 'lazy list [2--3--5--...]' sage: lazy_list_formatter(L, more='and more') 'lazy list [2, 3, 5, and more]' sage: lazy_list_formatter(L, preview=10) 'lazy list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, ...]' sage: lazy_list_formatter(L, name='primes', ....: opening_delimiter='', closing_delimiter='', ....: separator=' ', more='->', preview=7) 'primes 2 3 5 7 11 13 17 ->'
-
class
sage.misc.lazy_list.
lazy_list_from_function
¶ Bases:
sage.misc.lazy_list.lazy_list_generic
INPUT:
function
– a function that mapsn
to the element at positionn
. (This function only needs to be defined for length larger than the length of the cache.)cache
– an optional list to be used as the cache. Be careful that there is no copy.stop
– an optional integer to specify the length of this lazy list. (Otherwise it is considered infinite).
EXAMPLES:
sage: from sage.misc.lazy_list import lazy_list_from_function sage: lazy_list_from_function(euler_phi) lazy list [0, 1, 1, ...] sage: lazy_list_from_function(divisors, [None]) lazy list [None, [1], [1, 2], ...]
-
class
sage.misc.lazy_list.
lazy_list_from_iterator
¶ Bases:
sage.misc.lazy_list.lazy_list_generic
Lazy list built from an iterator.
EXAMPLES:
sage: from sage.misc.lazy_list import lazy_list sage: from itertools import count sage: m = lazy_list(count()); m lazy list [0, 1, 2, ...] sage: m2 = lazy_list(count())[8:20551:2] sage: m2 lazy list [8, 10, 12, ...] sage: x = iter(m) sage: [next(x), next(x), next(x)] [0, 1, 2] sage: y = iter(m) sage: [next(y), next(y), next(y)] [0, 1, 2] sage: [next(x), next(y)] [3, 3] sage: loads(dumps(m)) lazy list [0, 1, 2, ...]
-
class
sage.misc.lazy_list.
lazy_list_from_update_function
¶ Bases:
sage.misc.lazy_list.lazy_list_generic
INPUT:
function
– a function that updates a list of precomputed values. The update function should take as input a list and make it longer (using either the methodsappend
orextend
). If after a call to the update function the list of values is shorter aRuntimeError
will occurr. If no value is added then the lazy list is considered finite.cache
– an optional list to be used as the cache. Be careful that there is no copy.stop
– an optional integer to specify the length of this lazy list (otherwise it is considered infinite)
-
class
sage.misc.lazy_list.
lazy_list_generic
¶ Bases:
object
A lazy list
EXAMPLES:
sage: from sage.misc.lazy_list import lazy_list sage: l = lazy_list(Primes()) sage: l lazy list [2, 3, 5, ...] sage: l[200] 1229
-
get
(i)¶ Return the element at position
i
.If the index is not an integer, then raise a
TypeError
. If the argument is negative then raise aValueError
. Finally, if the argument is beyond the size of that lazy list it raises aIndexError
.EXAMPLES:
sage: from sage.misc.lazy_list import lazy_list sage: from itertools import chain, repeat sage: f = lazy_list(chain(iter([1,2,3]), repeat('a'))) sage: f.get(0) 1 sage: f.get(3) 'a' sage: f.get(0) 1 sage: f.get(4) 'a' sage: g = f[:10] sage: g.get(5) 'a' sage: g.get(10) Traceback (most recent call last): ... IndexError: lazy list index out of range sage: g.get(1/2) Traceback (most recent call last): ... TypeError: unable to convert rational 1/2 to an integer
-
list
()¶ Return the list made of the elements of
self
.Note
If the iterator is sufficiently large, this will build a list of length
PY_SSIZE_T_MAX
which should be beyond the capacity of your RAM!EXAMPLES:
sage: from sage.misc.lazy_list import lazy_list sage: P = lazy_list(Primes()) sage: P[2:143:5].list() [5, 19, 41, 61, 83, 107, 137, 163, 191, 223, 241, 271, 307, 337, 367, 397, 431, 457, 487, 521, 563, 593, 617, 647, 677, 719, 751, 787, 823] sage: P = lazy_list(iter([1,2,3])) sage: P.list() [1, 2, 3] sage: P[:100000].list() [1, 2, 3] sage: P[1:7:2].list() [2]
-
-
sage.misc.lazy_list.
slice_unpickle
(master, start, stop, step)¶ Unpickle helper