Skip to content

Commit 8cc3746

Browse files
author
Release Manager
committed
gh-39626: Fixed issue the caused random element in for Tuples to stall by adding unrank method. Fixes #39534. There was an issue in Tuples causing random element to stall. It was stalling because Tuples did not have an efficient method of getting the i-th element without iterating through the entire set. To fix this an unrank method was added to Tuples. This method was based on the unrank method found in src\sage\categories\finite_enumerated_sets.py. This unrank method creates each tuple in a similar way to how numbers in some base are created. Also added documentation, an example, and various tests to make sure the function is working correctly. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [X] The title is concise and informative. - [X] The description explains in detail what this PR is about. - [X] I have linked a relevant issue or discussion. - [X] I have created tests covering the changes. - [X] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies URL: #39626 Reported by: Noel-Roemmele Reviewer(s): Travis Scrimshaw
2 parents b9ad86c + 835bb2f commit 8cc3746

File tree

3 files changed

+156
-3
lines changed

3 files changed

+156
-3
lines changed

src/sage/categories/enumerated_sets.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ def __getitem__(self, i):
432432
sage: P[-1]
433433
Traceback (most recent call last):
434434
...
435-
NotImplementedError: cannot list an infinite set
435+
ValueError: infinite list
436436
437437
::
438438
@@ -457,11 +457,28 @@ def __getitem__(self, i):
457457
[1]
458458
sage: F[1::2]
459459
[2]
460+
461+
TESTS:
462+
463+
Verify that an infinite index raises an error::
464+
465+
sage: F = FiniteEnumeratedSet([1,2,3,4,5])
466+
sage: F[oo]
467+
Traceback (most recent call last):
468+
...
469+
TypeError: unable to coerce <class 'sage.rings.infinity.PlusInfinity'>
470+
to an integer
460471
"""
472+
from sage.rings.infinity import Infinity
461473
if isinstance(i, slice):
462474
return self.unrank_range(i.start, i.stop, i.step)
475+
i = Integer(i)
476+
if i < 0:
477+
i += self.cardinality()
463478
if i < 0:
464-
return self.list()[i]
479+
raise IndexError("index out of range")
480+
if i is Infinity:
481+
raise ValueError("infinite list")
465482
return self.unrank(i)
466483

467484
def __len__(self):

src/sage/combinat/tuple.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,142 @@ def __repr__(self):
9090
"""
9191
return "Tuples of %s of length %s" % (self.S, self.k)
9292

93+
def unrank(self, i):
94+
"""
95+
Return the `i`-th element of the set of Tuples.
96+
97+
INPUT:
98+
99+
- ``i`` -- integer between `0` and `n-1`, where `n` is the cardinality
100+
of this set
101+
102+
EXAMPLES::
103+
104+
sage: T = Tuples(range(7), 6)
105+
sage: T[73451]
106+
(0, 0, 1, 4, 2, 4)
107+
108+
TESTS:
109+
110+
Verify that :meth:`unrank` is giving the correct result::
111+
112+
sage: T = Tuples(range(4), 5)
113+
sage: all(T[i] == x for i,x in enumerate(T))
114+
True
115+
116+
Verify that :meth:`unrank` is fast for large inputs::
117+
118+
sage: Tuples(range(3), 30)[10^12]
119+
(1, 0, 0, 1, 1, 1, 2, 0, 1, 2, 0, 1, 1, 0, 2, 1, 1, 0, 1, 2, 1, 2,
120+
1, 1, 0, 1, 0, 0, 0, 0)
121+
122+
Verify that :meth:`unrank` normalizes ``i``::
123+
124+
sage: T = Tuples(range(3), 2)
125+
sage: T[QQ(4/2)]
126+
(2, 0)
127+
sage: T[QQ(1/2)]
128+
Traceback (most recent call last):
129+
...
130+
TypeError: no conversion of this rational to integer
131+
132+
Verify that :meth:`unrank` throws an error when ``i`` is out of bounds::
133+
134+
sage: T = Tuples(range(3), 3)
135+
sage: T[27]
136+
Traceback (most recent call last):
137+
...
138+
IndexError: index i (=27) is greater than or equal to the cardinality
139+
sage: T[-28]
140+
Traceback (most recent call last):
141+
...
142+
IndexError: index out of range
143+
144+
Verify that :meth:`unrank` works correctly for Tuples where `k = 1`::
145+
146+
sage: T = Tuples(range(6), 1)
147+
sage: T[5]
148+
(5,)
149+
150+
Verify that :meth:`unrank` works when called directly::
151+
152+
sage: T = Tuples(range(4), 3)
153+
sage: T.unrank(19)
154+
(3, 0, 1)
155+
sage: T.unrank(-1)
156+
Traceback (most recent call last):
157+
...
158+
IndexError: index out of range
159+
160+
Verify that :meth:`unrank` works with non-integer sets::
161+
162+
sage: T = Tuples(['a', 'b', 'c'], 3)
163+
sage: T[15]
164+
('a', 'c', 'b')
165+
sage: T = Tuples([None, 1, ZZ, GF(2)], 5)
166+
sage: T[30]
167+
(Integer Ring, Finite Field of size 2, 1, None, None)
168+
169+
Verify that :meth:`unrank` gives the correct answer when `|S| < 1`::
170+
171+
sage: T = Tuples([],5)
172+
sage: T[0]
173+
Traceback (most recent call last):
174+
...
175+
IndexError: index i (=0) is greater than or equal to the cardinality
176+
sage: list(T)
177+
[]
178+
179+
Verify that :meth:`unrank` gives the correct answer when `|S| = 1`::
180+
181+
sage: T = Tuples([1],5)
182+
sage: T[0]
183+
(1, 1, 1, 1, 1)
184+
185+
Verify that :meth:`unrank` gives the correct answer when `k = 0`::
186+
187+
sage: T = Tuples(range(6), 0)
188+
sage: list(T)
189+
[()]
190+
sage: T[0]
191+
()
192+
sage: T[1]
193+
Traceback (most recent call last):
194+
...
195+
IndexError: index i (=1) is greater than or equal to the cardinality
196+
197+
Verify that :meth:`unrank` gives the correct answer when `|S| < 1` and
198+
`k = 0`::
199+
200+
sage: T = Tuples(range(0), 0)
201+
sage: T[0]
202+
()
203+
sage: T[-1]
204+
()
205+
sage: T[1]
206+
Traceback (most recent call last):
207+
...
208+
IndexError: index i (=1) is greater than or equal to the cardinality
209+
210+
Verify that :issue:`39534` has been fixed::
211+
212+
sage: T = Tuples(range(3), 30).random_element()
213+
sage: all(v in range(3) for v in T)
214+
True
215+
sage: len(T)
216+
30
217+
"""
218+
i = ZZ(i)
219+
if i < 0:
220+
raise IndexError("index out of range")
221+
if i >= self.cardinality():
222+
raise IndexError("index i (={}) is greater than or equal to the cardinality"
223+
.format(i))
224+
ts = len(self.S)
225+
if ts <= 1:
226+
return tuple(self.S[0] for _ in range(self.k))
227+
return tuple(i.digits(ts, self.S, self.k))
228+
93229
def __iter__(self):
94230
"""
95231
EXAMPLES::

src/sage/sets/finite_enumerated_set.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def unrank(self, i):
288288
sage: S[-4]
289289
Traceback (most recent call last):
290290
...
291-
IndexError: list index out of range
291+
IndexError: index out of range
292292
"""
293293
return self._elements[i]
294294

0 commit comments

Comments
 (0)