Skip to content
This repository was archived by the owner on Jan 30, 2023. It is now read-only.

Commit 8542826

Browse files
committed
Unify implementatiion of some_tuples and tester.some_elements; improve implementation for large repeat
1 parent 5fc43c7 commit 8542826

File tree

2 files changed

+66
-26
lines changed

2 files changed

+66
-26
lines changed

src/sage/misc/misc.py

+56-8
Original file line numberDiff line numberDiff line change
@@ -1174,11 +1174,30 @@ def random_sublist(X, s):
11741174
return [a for a in X if random.random() <= s]
11751175

11761176

1177-
def some_tuples(elements, repeat, bound):
1177+
def some_tuples(elements, repeat, bound, max_samples=None):
11781178
r"""
11791179
Return an iterator over at most ``bound`` number of ``repeat``-tuples of
11801180
``elements``.
11811181
1182+
INPUT:
1183+
1184+
- ``elements`` -- an iterable
1185+
- ``repeat`` -- integer (default ``None``), the length of the tuples to be returned.
1186+
If ``None``, just returns entries from ``elements``.
1187+
- ``bound`` -- the maximum number of tuples returned (ignored if ``max_samples`` given)
1188+
- ``max_samples`` -- non-negative integer (default ``None``). If given,
1189+
then a sample of the possible tuples will be returned,
1190+
instead of the first few in the standard order.
1191+
1192+
OUTPUT:
1193+
1194+
If ``max_samples`` is not provided, an iterator over the first
1195+
``bound`` tuples of length ``repeat``, in the standard nested-for-loop order.
1196+
1197+
If ``max_samples is provided, a list of at most ``max_samples`` tuples,
1198+
sampled uniformly from the possibilities. In this case, ``elements``
1199+
must be finite.
1200+
11821201
TESTS::
11831202
11841203
sage: from sage.misc.misc import some_tuples
@@ -1192,15 +1211,44 @@ def some_tuples(elements, repeat, bound):
11921211
sage: len(list(l))
11931212
10
11941213
1195-
.. TODO::
1214+
sage: l = some_tuples(range(3), 2, None, max_samples=10)
1215+
sage: len(list(l))
1216+
9
1217+
"""
1218+
if max_samples is None:
1219+
from itertools import islice, product
1220+
P = elements if repeat is None else product(elements, repeat=repeat)
1221+
return islice(P, bound)
1222+
else:
1223+
if not (hasattr(elements, '__len__') and hasattr(elements, '__getitem__')):
1224+
elements = list(elements)
1225+
n = len(elements)
1226+
N = n if repeat is None else n**repeat
1227+
if N <= max_samples:
1228+
from itertools import product
1229+
return elements if repeat is None else product(elements, repeat=repeat)
1230+
return _some_tuples_sampling(elements, repeat, max_samples, n)
11961231

1197-
Currently, this only return an iterator over the first element of the
1198-
Cartesian product. It would be smarter to return something more
1199-
"random like" as it is used in tests. However, this should remain
1200-
deterministic.
1232+
def _some_tuples_sampling(elements, repeat, max_samples, n):
12011233
"""
1202-
from itertools import islice, product
1203-
return islice(product(elements, repeat=repeat), bound)
1234+
Internal function for :func:`some_tuples`.
1235+
1236+
TESTS::
1237+
1238+
sage: from sage.misc.misc import _some_tuples_sampling
1239+
sage: list(_some_tuples_sampling(range(3), 3, 2, 3))
1240+
[(0, 1, 0), (1, 1, 1)]
1241+
sage: list(_some_tuples_sampling(range(20), None, 4, 20))
1242+
[0, 6, 9, 3]
1243+
"""
1244+
from sage.rings.integer import Integer
1245+
N = n if repeat is None else n**repeat
1246+
# We sample on range(N) and create tuples manually since we don't want to create the list of all possible tuples in memory
1247+
for a in random.sample(range(N), max_samples):
1248+
if repeat is None:
1249+
yield elements[a]
1250+
else:
1251+
yield tuple(elements[j] for j in Integer(a).digits(n, padto=repeat))
12041252

12051253
def powerset(X):
12061254
r"""

src/sage/misc/sage_unittest.py

+10-18
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ def __repr__(self):
450450
return "Testing utilities for %s"%self._instance
451451

452452

453-
def some_elements(self, S=None, repeat=1):
453+
def some_elements(self, S=None, repeat=None):
454454
"""
455455
Returns a list (or iterable) of elements of the instance on which
456456
the tests should be run. This is only meaningful for container
@@ -463,7 +463,7 @@ def some_elements(self, S=None, repeat=1):
463463
time, or the result of :meth:`.some_elements` if no elements
464464
were specified.
465465
466-
- ``repeat`` -- integer (default: 1). If given, instead returns
466+
- ``repeat`` -- integer (default: None). If given, instead returns
467467
a list of tuples of length ``repeat`` from ``S``.
468468
469469
OUTPUT:
@@ -528,14 +528,16 @@ def some_elements(self, S=None, repeat=1):
528528
529529
The ``repeat`` keyword can give pairs or triples from ``S``::
530530
531-
sage: list(tester.some_elements(repeat=2)
531+
sage: list(tester.some_elements(repeat=2))
532532
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)]
533533
534534
You can use ``max_samples`` to sample at random, instead of in order::
535535
536-
sage: tester = InstanceTester(ZZ, elements = srange(8), max_samples = 3)
536+
sage: tester = InstanceTester(ZZ, elements = srange(8), max_samples = 4)
537537
sage: list(tester.some_elements())
538-
[2, 1, 4]
538+
[0, 3, 7, 1]
539+
sage: list(tester.some_elements(repeat=2))
540+
[(1, 4), (3, 1), (4, 5), (5, 0)]
539541
540542
Test for :trac:`15919`, :trac:`16244`::
541543
@@ -555,19 +557,9 @@ def some_elements(self, S=None, repeat=1):
555557
sage: list(tester.some_elements())
556558
[(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)]
557559
"""
558-
if S is None:
559-
if self._elements is None:
560-
S = self._instance.some_elements()
561-
else:
562-
S = self._elements
563-
import itertools
564-
if repeat != 1:
565-
S = itertools.product(S, repeat=repeat)
566-
if self._max_samples is None:
567-
return list(itertools.islice(S, self._max_runs))
568-
else:
569-
return list(random.sample(S, self._max_samples))
570-
560+
S = S or self._elements or self._instance.some_elements()
561+
from sage.misc.misc import some_tuples
562+
return list(some_tuples(S, repeat, self._max_runs, self._max_samples))
571563

572564
class PythonObjectWithTests(object):
573565
"""

0 commit comments

Comments
 (0)