Issue #9213: Add index and count methods to range objects, needed to

meet the API of the collections.Sequence ABC.
This commit is contained in:
Daniel Stutzbach 2010-09-13 21:16:29 +00:00
parent e4d6317c87
commit 9f0cbf1c72
4 changed files with 207 additions and 52 deletions

View File

@ -1554,9 +1554,23 @@ looping. The advantage of the :class:`range` type is that an :class:`range`
object will always take the same amount of memory, no matter the size of the object will always take the same amount of memory, no matter the size of the
range it represents. There are no consistent performance advantages. range it represents. There are no consistent performance advantages.
Range objects have very little behavior: they only support indexing, iteration, Range objects have relatively little behavior: they support indexing,
and the :func:`len` function. iteration, the :func:`len` function, and the following methods.
.. method:: range.count(x)
Return the number of *i*'s for which ``s[i] == x``. Normally the
result will be 0 or 1, but it could be greater if *x* defines an
unusual equality function.
.. versionadded:: 3.2
.. method:: range.index(x)
Return the smallest *i* such that ``s[i] == x``. Raises
:exc:`ValueError` when *x* is not in the range.
.. versionadded:: 3.2
.. _typesseq-mutable: .. _typesseq-mutable:

View File

@ -1028,6 +1028,60 @@ class BuiltinTest(unittest.TestCase):
self.assertRaises(TypeError, range, 0.0, 0.0, 1) self.assertRaises(TypeError, range, 0.0, 0.0, 1)
self.assertRaises(TypeError, range, 0.0, 0.0, 1.0) self.assertRaises(TypeError, range, 0.0, 0.0, 1.0)
self.assertEqual(range(3).count(-1), 0)
self.assertEqual(range(3).count(0), 1)
self.assertEqual(range(3).count(1), 1)
self.assertEqual(range(3).count(2), 1)
self.assertEqual(range(3).count(3), 0)
self.assertEqual(range(10**20).count(1), 1)
self.assertEqual(range(10**20).count(10**20), 0)
self.assertEqual(range(3).index(1), 1)
self.assertEqual(range(1, 2**100, 2).count(2**87), 0)
self.assertEqual(range(1, 2**100, 2).count(2**87+1), 1)
self.assertEqual(range(1, 10, 3).index(4), 1)
self.assertEqual(range(1, -10, -3).index(-5), 2)
self.assertEqual(range(10**20).index(1), 1)
self.assertEqual(range(10**20).index(10**20 - 1), 10**20 - 1)
self.assertRaises(ValueError, range(1, 2**100, 2).index, 2**87)
self.assertEqual(range(1, 2**100, 2).index(2**87+1), 2**86)
class AlwaysEqual(object):
def __eq__(self, other):
return True
always_equal = AlwaysEqual()
self.assertEqual(range(10).count(always_equal), 10)
self.assertEqual(range(10).index(always_equal), 0)
def test_range_index(self):
u = range(2)
self.assertEqual(u.index(0), 0)
self.assertEqual(u.index(1), 1)
self.assertRaises(ValueError, u.index, 2)
u = range(-2, 3)
self.assertEqual(u.count(0), 1)
self.assertEqual(u.index(0), 2)
self.assertRaises(TypeError, u.index)
class BadExc(Exception):
pass
class BadCmp:
def __eq__(self, other):
if other == 2:
raise BadExc()
return False
a = range(4)
self.assertRaises(BadExc, a.index, BadCmp())
a = range(-2, 3)
self.assertEqual(a.index(0), 2)
def test_input(self): def test_input(self):
self.write_testfile() self.write_testfile()
fp = open(TESTFN, 'r') fp = open(TESTFN, 'r')

View File

@ -10,6 +10,10 @@ What's New in Python 3.2 Alpha 3?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #9212: The range type_items now provides index() and count()
methods, to conform to the Sequence ABC. Patch by Daniel Urban and
Daniel Stutzbach.
- Issue #7994: Issue a PendingDeprecationWarning if object.__format__ - Issue #7994: Issue a PendingDeprecationWarning if object.__format__
is called with a non-empty format string. This is an effort to is called with a non-empty format string. This is an effort to
future-proof user code. If a derived class does not currently future-proof user code. If a derived class does not currently

View File

@ -273,60 +273,135 @@ range_reduce(rangeobject *r, PyObject *args)
r->start, r->stop, r->step); r->start, r->stop, r->step);
} }
/* Assumes (PyLong_CheckExact(ob) || PyBool_Check(ob)) */
static int
range_contains_long(rangeobject *r, PyObject *ob)
{
int cmp1, cmp2, cmp3;
PyObject *tmp1 = NULL;
PyObject *tmp2 = NULL;
PyObject *zero = NULL;
int result = -1;
zero = PyLong_FromLong(0);
if (zero == NULL) /* MemoryError in int(0) */
goto end;
/* Check if the value can possibly be in the range. */
cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
if (cmp1 == -1)
goto end;
if (cmp1 == 1) { /* positive steps: start <= ob < stop */
cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
}
else { /* negative steps: stop < ob <= start */
cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
}
if (cmp2 == -1 || cmp3 == -1) /* TypeError */
goto end;
if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
result = 0;
goto end;
}
/* Check that the stride does not invalidate ob's membership. */
tmp1 = PyNumber_Subtract(ob, r->start);
if (tmp1 == NULL)
goto end;
tmp2 = PyNumber_Remainder(tmp1, r->step);
if (tmp2 == NULL)
goto end;
/* result = (int(ob) - start % step) == 0 */
result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
end:
Py_XDECREF(tmp1);
Py_XDECREF(tmp2);
Py_XDECREF(zero);
return result;
}
static int static int
range_contains(rangeobject *r, PyObject *ob) { range_contains(rangeobject *r, PyObject *ob) {
if (PyLong_CheckExact(ob) || PyBool_Check(ob)) { if (PyLong_CheckExact(ob) || PyBool_Check(ob))
int cmp1, cmp2, cmp3; return range_contains_long(r, ob);
PyObject *tmp1 = NULL;
PyObject *tmp2 = NULL;
PyObject *zero = NULL;
int result = -1;
zero = PyLong_FromLong(0);
if (zero == NULL) /* MemoryError in int(0) */
goto end;
/* Check if the value can possibly be in the range. */
cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
if (cmp1 == -1)
goto end;
if (cmp1 == 1) { /* positive steps: start <= ob < stop */
cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
}
else { /* negative steps: stop < ob <= start */
cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
}
if (cmp2 == -1 || cmp3 == -1) /* TypeError */
goto end;
if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
result = 0;
goto end;
}
/* Check that the stride does not invalidate ob's membership. */
tmp1 = PyNumber_Subtract(ob, r->start);
if (tmp1 == NULL)
goto end;
tmp2 = PyNumber_Remainder(tmp1, r->step);
if (tmp2 == NULL)
goto end;
/* result = (int(ob) - start % step) == 0 */
result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
end:
Py_XDECREF(tmp1);
Py_XDECREF(tmp2);
Py_XDECREF(zero);
return result;
}
/* Fall back to iterative search. */
return (int)_PySequence_IterSearch((PyObject*)r, ob, return (int)_PySequence_IterSearch((PyObject*)r, ob,
PY_ITERSEARCH_CONTAINS); PY_ITERSEARCH_CONTAINS);
} }
static PyObject *
range_count(rangeobject *r, PyObject *ob)
{
if (PyLong_CheckExact(ob) || PyBool_Check(ob)) {
if (range_contains_long(r, ob))
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
} else {
Py_ssize_t count;
count = _PySequence_IterSearch((PyObject*)r, ob, PY_ITERSEARCH_COUNT);
if (count == -1)
return NULL;
return PyLong_FromSsize_t(count);
}
}
static PyObject *
range_index(rangeobject *r, PyObject *ob)
{
PyObject *idx, *tmp;
int contains;
PyObject *format_tuple, *err_string;
static PyObject *err_format = NULL;
if (!PyLong_CheckExact(ob) && !PyBool_Check(ob)) {
Py_ssize_t index;
index = _PySequence_IterSearch((PyObject*)r, ob, PY_ITERSEARCH_INDEX);
if (index == -1)
return NULL;
return PyLong_FromSsize_t(index);
}
contains = range_contains_long(r, ob);
if (contains == -1)
return NULL;
if (!contains)
goto value_error;
tmp = PyNumber_Subtract(ob, r->start);
if (tmp == NULL)
return NULL;
/* idx = (ob - r.start) // r.step */
idx = PyNumber_FloorDivide(tmp, r->step);
Py_DECREF(tmp);
return idx;
value_error:
/* object is not in the range */
if (err_format == NULL) {
err_format = PyUnicode_FromString("%r is not in range");
if (err_format == NULL)
return NULL;
}
format_tuple = PyTuple_Pack(1, ob);
if (format_tuple == NULL)
return NULL;
err_string = PyUnicode_Format(err_format, format_tuple);
Py_DECREF(format_tuple);
if (err_string == NULL)
return NULL;
PyErr_SetObject(PyExc_ValueError, err_string);
Py_DECREF(err_string);
return NULL;
}
static PySequenceMethods range_as_sequence = { static PySequenceMethods range_as_sequence = {
(lenfunc)range_length, /* sq_length */ (lenfunc)range_length, /* sq_length */
0, /* sq_concat */ 0, /* sq_concat */
@ -344,10 +419,18 @@ static PyObject * range_reverse(PyObject *seq);
PyDoc_STRVAR(reverse_doc, PyDoc_STRVAR(reverse_doc,
"Returns a reverse iterator."); "Returns a reverse iterator.");
PyDoc_STRVAR(count_doc,
"rangeobject.count(value) -> integer -- return number of occurrences of value");
PyDoc_STRVAR(index_doc,
"rangeobject.index(value, [start, [stop]]) -> integer -- return index of value.\n"
"Raises ValueError if the value is not present.");
static PyMethodDef range_methods[] = { static PyMethodDef range_methods[] = {
{"__reversed__", (PyCFunction)range_reverse, METH_NOARGS, {"__reversed__", (PyCFunction)range_reverse, METH_NOARGS, reverse_doc},
reverse_doc}, {"__reduce__", (PyCFunction)range_reduce, METH_VARARGS},
{"__reduce__", (PyCFunction)range_reduce, METH_VARARGS}, {"count", (PyCFunction)range_count, METH_O, count_doc},
{"index", (PyCFunction)range_index, METH_O, index_doc},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };