Skip to content

Commit 9f0cbf1

Browse files
Issue python#9213: Add index and count methods to range objects, needed to
meet the API of the collections.Sequence ABC.
1 parent e4d6317 commit 9f0cbf1

4 files changed

Lines changed: 206 additions & 51 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,9 +1554,23 @@ looping. The advantage of the :class:`range` type is that an :class:`range`
15541554
object will always take the same amount of memory, no matter the size of the
15551555
range it represents. There are no consistent performance advantages.
15561556

1557-
Range objects have very little behavior: they only support indexing, iteration,
1558-
and the :func:`len` function.
1557+
Range objects have relatively little behavior: they support indexing,
1558+
iteration, the :func:`len` function, and the following methods.
15591559

1560+
.. method:: range.count(x)
1561+
1562+
Return the number of *i*'s for which ``s[i] == x``. Normally the
1563+
result will be 0 or 1, but it could be greater if *x* defines an
1564+
unusual equality function.
1565+
1566+
.. versionadded:: 3.2
1567+
1568+
.. method:: range.index(x)
1569+
1570+
Return the smallest *i* such that ``s[i] == x``. Raises
1571+
:exc:`ValueError` when *x* is not in the range.
1572+
1573+
.. versionadded:: 3.2
15601574

15611575
.. _typesseq-mutable:
15621576

Lib/test/test_builtin.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,60 @@ def __index__(self):
10281028
self.assertRaises(TypeError, range, 0.0, 0.0, 1)
10291029
self.assertRaises(TypeError, range, 0.0, 0.0, 1.0)
10301030

1031+
self.assertEqual(range(3).count(-1), 0)
1032+
self.assertEqual(range(3).count(0), 1)
1033+
self.assertEqual(range(3).count(1), 1)
1034+
self.assertEqual(range(3).count(2), 1)
1035+
self.assertEqual(range(3).count(3), 0)
1036+
1037+
self.assertEqual(range(10**20).count(1), 1)
1038+
self.assertEqual(range(10**20).count(10**20), 0)
1039+
self.assertEqual(range(3).index(1), 1)
1040+
self.assertEqual(range(1, 2**100, 2).count(2**87), 0)
1041+
self.assertEqual(range(1, 2**100, 2).count(2**87+1), 1)
1042+
1043+
self.assertEqual(range(1, 10, 3).index(4), 1)
1044+
self.assertEqual(range(1, -10, -3).index(-5), 2)
1045+
1046+
self.assertEqual(range(10**20).index(1), 1)
1047+
self.assertEqual(range(10**20).index(10**20 - 1), 10**20 - 1)
1048+
1049+
self.assertRaises(ValueError, range(1, 2**100, 2).index, 2**87)
1050+
self.assertEqual(range(1, 2**100, 2).index(2**87+1), 2**86)
1051+
1052+
class AlwaysEqual(object):
1053+
def __eq__(self, other):
1054+
return True
1055+
always_equal = AlwaysEqual()
1056+
self.assertEqual(range(10).count(always_equal), 10)
1057+
self.assertEqual(range(10).index(always_equal), 0)
1058+
1059+
def test_range_index(self):
1060+
u = range(2)
1061+
self.assertEqual(u.index(0), 0)
1062+
self.assertEqual(u.index(1), 1)
1063+
self.assertRaises(ValueError, u.index, 2)
1064+
1065+
u = range(-2, 3)
1066+
self.assertEqual(u.count(0), 1)
1067+
self.assertEqual(u.index(0), 2)
1068+
self.assertRaises(TypeError, u.index)
1069+
1070+
class BadExc(Exception):
1071+
pass
1072+
1073+
class BadCmp:
1074+
def __eq__(self, other):
1075+
if other == 2:
1076+
raise BadExc()
1077+
return False
1078+
1079+
a = range(4)
1080+
self.assertRaises(BadExc, a.index, BadCmp())
1081+
1082+
a = range(-2, 3)
1083+
self.assertEqual(a.index(0), 2)
1084+
10311085
def test_input(self):
10321086
self.write_testfile()
10331087
fp = open(TESTFN, 'r')

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.2 Alpha 3?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #9212: The range type_items now provides index() and count()
14+
methods, to conform to the Sequence ABC. Patch by Daniel Urban and
15+
Daniel Stutzbach.
16+
1317
- Issue #7994: Issue a PendingDeprecationWarning if object.__format__
1418
is called with a non-empty format string. This is an effort to
1519
future-proof user code. If a derived class does not currently

Objects/rangeobject.c

Lines changed: 132 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -273,58 +273,133 @@ range_reduce(rangeobject *r, PyObject *args)
273273
r->start, r->stop, r->step);
274274
}
275275

276+
/* Assumes (PyLong_CheckExact(ob) || PyBool_Check(ob)) */
277+
static int
278+
range_contains_long(rangeobject *r, PyObject *ob)
279+
{
280+
int cmp1, cmp2, cmp3;
281+
PyObject *tmp1 = NULL;
282+
PyObject *tmp2 = NULL;
283+
PyObject *zero = NULL;
284+
int result = -1;
285+
286+
zero = PyLong_FromLong(0);
287+
if (zero == NULL) /* MemoryError in int(0) */
288+
goto end;
289+
290+
/* Check if the value can possibly be in the range. */
291+
292+
cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
293+
if (cmp1 == -1)
294+
goto end;
295+
if (cmp1 == 1) { /* positive steps: start <= ob < stop */
296+
cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
297+
cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
298+
}
299+
else { /* negative steps: stop < ob <= start */
300+
cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
301+
cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
302+
}
303+
304+
if (cmp2 == -1 || cmp3 == -1) /* TypeError */
305+
goto end;
306+
if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
307+
result = 0;
308+
goto end;
309+
}
310+
311+
/* Check that the stride does not invalidate ob's membership. */
312+
tmp1 = PyNumber_Subtract(ob, r->start);
313+
if (tmp1 == NULL)
314+
goto end;
315+
tmp2 = PyNumber_Remainder(tmp1, r->step);
316+
if (tmp2 == NULL)
317+
goto end;
318+
/* result = (int(ob) - start % step) == 0 */
319+
result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
320+
end:
321+
Py_XDECREF(tmp1);
322+
Py_XDECREF(tmp2);
323+
Py_XDECREF(zero);
324+
return result;
325+
}
326+
276327
static int
277328
range_contains(rangeobject *r, PyObject *ob) {
329+
if (PyLong_CheckExact(ob) || PyBool_Check(ob))
330+
return range_contains_long(r, ob);
331+
332+
return (int)_PySequence_IterSearch((PyObject*)r, ob,
333+
PY_ITERSEARCH_CONTAINS);
334+
}
335+
336+
static PyObject *
337+
range_count(rangeobject *r, PyObject *ob)
338+
{
278339
if (PyLong_CheckExact(ob) || PyBool_Check(ob)) {
279-
int cmp1, cmp2, cmp3;
280-
PyObject *tmp1 = NULL;
281-
PyObject *tmp2 = NULL;
282-
PyObject *zero = NULL;
283-
int result = -1;
284-
285-
zero = PyLong_FromLong(0);
286-
if (zero == NULL) /* MemoryError in int(0) */
287-
goto end;
288-
289-
/* Check if the value can possibly be in the range. */
290-
291-
cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
292-
if (cmp1 == -1)
293-
goto end;
294-
if (cmp1 == 1) { /* positive steps: start <= ob < stop */
295-
cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
296-
cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
297-
}
298-
else { /* negative steps: stop < ob <= start */
299-
cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
300-
cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
301-
}
340+
if (range_contains_long(r, ob))
341+
Py_RETURN_TRUE;
342+
else
343+
Py_RETURN_FALSE;
344+
} else {
345+
Py_ssize_t count;
346+
count = _PySequence_IterSearch((PyObject*)r, ob, PY_ITERSEARCH_COUNT);
347+
if (count == -1)
348+
return NULL;
349+
return PyLong_FromSsize_t(count);
350+
}
351+
}
302352

303-
if (cmp2 == -1 || cmp3 == -1) /* TypeError */
304-
goto end;
305-
if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
306-
result = 0;
307-
goto end;
308-
}
353+
static PyObject *
354+
range_index(rangeobject *r, PyObject *ob)
355+
{
356+
PyObject *idx, *tmp;
357+
int contains;
358+
PyObject *format_tuple, *err_string;
359+
static PyObject *err_format = NULL;
360+
361+
if (!PyLong_CheckExact(ob) && !PyBool_Check(ob)) {
362+
Py_ssize_t index;
363+
index = _PySequence_IterSearch((PyObject*)r, ob, PY_ITERSEARCH_INDEX);
364+
if (index == -1)
365+
return NULL;
366+
return PyLong_FromSsize_t(index);
367+
}
368+
369+
contains = range_contains_long(r, ob);
370+
if (contains == -1)
371+
return NULL;
372+
373+
if (!contains)
374+
goto value_error;
375+
376+
tmp = PyNumber_Subtract(ob, r->start);
377+
if (tmp == NULL)
378+
return NULL;
379+
380+
/* idx = (ob - r.start) // r.step */
381+
idx = PyNumber_FloorDivide(tmp, r->step);
382+
Py_DECREF(tmp);
383+
return idx;
384+
385+
value_error:
309386

310-
/* Check that the stride does not invalidate ob's membership. */
311-
tmp1 = PyNumber_Subtract(ob, r->start);
312-
if (tmp1 == NULL)
313-
goto end;
314-
tmp2 = PyNumber_Remainder(tmp1, r->step);
315-
if (tmp2 == NULL)
316-
goto end;
317-
/* result = (int(ob) - start % step) == 0 */
318-
result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
319-
end:
320-
Py_XDECREF(tmp1);
321-
Py_XDECREF(tmp2);
322-
Py_XDECREF(zero);
323-
return result;
387+
/* object is not in the range */
388+
if (err_format == NULL) {
389+
err_format = PyUnicode_FromString("%r is not in range");
390+
if (err_format == NULL)
391+
return NULL;
324392
}
325-
/* Fall back to iterative search. */
326-
return (int)_PySequence_IterSearch((PyObject*)r, ob,
327-
PY_ITERSEARCH_CONTAINS);
393+
format_tuple = PyTuple_Pack(1, ob);
394+
if (format_tuple == NULL)
395+
return NULL;
396+
err_string = PyUnicode_Format(err_format, format_tuple);
397+
Py_DECREF(format_tuple);
398+
if (err_string == NULL)
399+
return NULL;
400+
PyErr_SetObject(PyExc_ValueError, err_string);
401+
Py_DECREF(err_string);
402+
return NULL;
328403
}
329404

330405
static PySequenceMethods range_as_sequence = {
@@ -344,10 +419,18 @@ static PyObject * range_reverse(PyObject *seq);
344419
PyDoc_STRVAR(reverse_doc,
345420
"Returns a reverse iterator.");
346421

422+
PyDoc_STRVAR(count_doc,
423+
"rangeobject.count(value) -> integer -- return number of occurrences of value");
424+
425+
PyDoc_STRVAR(index_doc,
426+
"rangeobject.index(value, [start, [stop]]) -> integer -- return index of value.\n"
427+
"Raises ValueError if the value is not present.");
428+
347429
static PyMethodDef range_methods[] = {
348-
{"__reversed__", (PyCFunction)range_reverse, METH_NOARGS,
349-
reverse_doc},
350-
{"__reduce__", (PyCFunction)range_reduce, METH_VARARGS},
430+
{"__reversed__", (PyCFunction)range_reverse, METH_NOARGS, reverse_doc},
431+
{"__reduce__", (PyCFunction)range_reduce, METH_VARARGS},
432+
{"count", (PyCFunction)range_count, METH_O, count_doc},
433+
{"index", (PyCFunction)range_index, METH_O, index_doc},
351434
{NULL, NULL} /* sentinel */
352435
};
353436

0 commit comments

Comments
 (0)