Index: Objects/rangeobject.c =================================================================== --- Objects/rangeobject.c (revision 83036) +++ Objects/rangeobject.c (working copy) @@ -327,6 +327,167 @@ PY_ITERSEARCH_CONTAINS); } +static PyObject * +range_count(rangeobject *r, PyObject *ob) +{ + int contains; + + contains = range_contains(r, ob); + if (contains == -1) { + return NULL; + } + + if (contains) { + return PyLong_FromLong(1); + } + + return PyLong_FromLong(0); +} + +/* Helper function to handle negative indices. + * If n < 0, add the length of the range object. + * Always return a new reference. */ +static PyObject * +_normalize_idx(rangeobject *r, PyObject *n) +{ + int cmp; + PyObject *zero, *len, *result; + + zero = PyLong_FromLong(0); + if (zero == NULL) { + return NULL; + } + + /* cmp = n < 0 */ + cmp = PyObject_RichCompareBool(n, zero, Py_LT); + Py_DECREF(zero); + if (cmp == -1) { + return NULL; + } + + if (cmp == 1) { + /* n < 0, so we add the length: */ + len = range_length_obj(r); + if (len == NULL) { + return NULL; + } + + /* result = n + len */ + result = PyNumber_Add(n, len); + Py_DECREF(len); + if (result == NULL) { + return NULL; + } + return result; + } + + /* the index is nonnegative, return it: */ + Py_INCREF(n); /* we have to return a new reference */ + return n; +} + +static PyObject * +range_index(rangeobject *r, PyObject *args) +{ + PyObject *ob; + PyObject *start=NULL; + PyObject *stop=NULL; + PyObject *idx, *tmp, *zero, *len; + int contains, cmp1, cmp2; + PyObject *format_tuple, *err_string; + static PyObject *err_format = NULL; + + if (!PyArg_ParseTuple(args, "O|O!O!:index", &ob, + &PyLong_Type, &start, + &PyLong_Type, &stop)) + return NULL; + + contains = range_contains(r, ob); + if (contains == -1) { + return NULL; + } + + if (contains) { + 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); + if (idx == NULL) { + return NULL; + } + + /* We have the index, now check if start <= idx < stop: */ + zero = PyLong_FromLong(0); + if (zero == NULL) { + Py_DECREF(idx); + return NULL; + } + if (start == NULL) { + start = zero; + } + + len = range_length_obj(r); + if (len == NULL) { + Py_DECREF(idx); + Py_DECREF(zero); + return NULL; + } + if (stop == NULL) { + stop = len; + } + + start = _normalize_idx(r, start); + Py_DECREF(zero); + if (start == NULL) { + Py_DECREF(idx); + Py_DECREF(len); + return NULL; + } + + stop = _normalize_idx(r, stop); + Py_DECREF(len); + if (stop == NULL) { + Py_DECREF(idx); + Py_DECREF(start); + return NULL; + } + + cmp1 = PyObject_RichCompareBool(start, idx, Py_LE); + cmp2 = PyObject_RichCompareBool(idx, stop, Py_LT); + Py_DECREF(start); + Py_DECREF(stop); + if (cmp1 == -1 || cmp2 == -1) { + Py_DECREF(idx); + return NULL; + } + + if (cmp1 == 1 && cmp2 == 1) { + /* start <= idx < stop */ + return idx; + } + } + + /* else: 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 = { (lenfunc)range_length, /* sq_length */ 0, /* sq_concat */ @@ -344,10 +505,21 @@ PyDoc_STRVAR(reverse_doc, "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[] = { {"__reversed__", (PyCFunction)range_reverse, METH_NOARGS, reverse_doc}, {"__reduce__", (PyCFunction)range_reduce, METH_VARARGS}, + {"count", (PyCFunction)range_count, METH_O, + count_doc}, + {"index", (PyCFunction)range_index, METH_VARARGS, + index_doc}, {NULL, NULL} /* sentinel */ }; Index: Lib/test/test_builtin.py =================================================================== --- Lib/test/test_builtin.py (revision 83036) +++ Lib/test/test_builtin.py (working copy) @@ -1006,6 +1006,23 @@ self.assertRaises(TypeError, range, 0.0, 0.0, 1) self.assertRaises(TypeError, range, 0.0, 0.0, 1.0) + # issue 9213: test range.count and range.index: + 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(3).index(1), 1) + self.assertEqual(range(3).index(1, 1), 1) + self.assertEqual(range(3).index(1, 1, -1), 1) + self.assertRaises(ValueError, range(3).index, 1, 2) + self.assertRaises(ValueError, range(3).index, 1, 2, -1) + self.assertRaises(ValueError, range(3).index, 1, 1, -2) + + self.assertEqual(range(1, 10, 3).index(4), 1) + self.assertEqual(range(1, -10, -3).index(-5), 2) + def test_input(self): self.write_testfile() fp = open(TESTFN, 'r')