From e36dd1e25f12d6ac26ed195b1fcdbf636ff58f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Thu, 7 Feb 2019 01:39:51 +0100 Subject: [PATCH 01/12] Add key parameter to functions in bisect module --- Doc/library/bisect.rst | 46 +++-- Lib/bisect.py | 67 ++++-- Lib/test/test_bisect.py | 10 + .../2019-02-07-15-44-12.bpo-4356.i6h86W.rst | 3 + Modules/_bisectmodule.c | 193 +++++++++++++----- 5 files changed, 237 insertions(+), 82 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst diff --git a/Doc/library/bisect.rst b/Doc/library/bisect.rst index 6bf7814b257f4ab..84f7fba38323d9d 100644 --- a/Doc/library/bisect.rst +++ b/Doc/library/bisect.rst @@ -21,21 +21,27 @@ example of the algorithm (the boundary conditions are already right!). The following functions are provided: -.. function:: bisect_left(a, x, lo=0, hi=len(a)) +.. function:: bisect_left(a, x, lo=0, hi=len(a), key=None) Locate the insertion point for *x* in *a* to maintain sorted order. The parameters *lo* and *hi* may be used to specify a subset of the list - which should be considered; by default the entire list is used. If *x* is - already present in *a*, the insertion point will be before (to the left of) - any existing entries. The return value is suitable for use as the first - parameter to ``list.insert()`` assuming that *a* is already sorted. + which should be considered; by default the entire list is used. The + parameter *key* specifies a function of one argument that is used to + extract a comparison key from each element in *a* (for example, + `key=str.lower`). The default value is `None` (compare the elements + directly). + + If *x* is already present in *a*, the insertion point will be before + (to the left of) any existing entries. The return value is suitable for + use as the first parameter to ``list.insert()`` assuming that *a* is + already sorted. The returned insertion point *i* partitions the array *a* into two halves so that ``all(val < x for val in a[lo:i])`` for the left side and ``all(val >= x for val in a[i:hi])`` for the right side. -.. function:: bisect_right(a, x, lo=0, hi=len(a)) - bisect(a, x, lo=0, hi=len(a)) +.. function:: bisect_right(a, x, lo=0, hi=len(a), key=None) + bisect(a, x, lo=0, hi=len(a), key=None) Similar to :func:`bisect_left`, but returns an insertion point which comes after (to the right of) any existing entries of *x* in *a*. @@ -44,15 +50,15 @@ The following functions are provided: that ``all(val <= x for val in a[lo:i])`` for the left side and ``all(val > x for val in a[i:hi])`` for the right side. -.. function:: insort_left(a, x, lo=0, hi=len(a)) +.. function:: insort_left(a, x, lo=0, hi=len(a), key=None) Insert *x* in *a* in sorted order. This is equivalent to ``a.insert(bisect.bisect_left(a, x, lo, hi), x)`` assuming that *a* is already sorted. Keep in mind that the O(log n) search is dominated by the slow O(n) insertion step. -.. function:: insort_right(a, x, lo=0, hi=len(a)) - insort(a, x, lo=0, hi=len(a)) +.. function:: insort_right(a, x, lo=0, hi=len(a), key=None) + insort(a, x, lo=0, hi=len(a), key=None) Similar to :func:`insort_left`, but inserting *x* in *a* after any existing entries of *x*. @@ -74,37 +80,37 @@ can be tricky or awkward to use for common searching tasks. The following five functions show how to transform them into the standard lookups for sorted lists:: - def index(a, x): + def index(a, x, key=None): 'Locate the leftmost value exactly equal to x' - i = bisect_left(a, x) + i = bisect_left(a, x, key=key) if i != len(a) and a[i] == x: return i raise ValueError - def find_lt(a, x): + def find_lt(a, x, key=None): 'Find rightmost value less than x' - i = bisect_left(a, x) + i = bisect_left(a, x, key=key) if i: return a[i-1] raise ValueError - def find_le(a, x): + def find_le(a, x, key=None): 'Find rightmost value less than or equal to x' - i = bisect_right(a, x) + i = bisect_right(a, x, key=key) if i: return a[i-1] raise ValueError - def find_gt(a, x): + def find_gt(a, x, key=None): 'Find leftmost value greater than x' - i = bisect_right(a, x) + i = bisect_right(a, x, key=key) if i != len(a): return a[i] raise ValueError - def find_ge(a, x): + def find_ge(a, x, key=None): 'Find leftmost item greater than or equal to x' - i = bisect_left(a, x) + i = bisect_left(a, x, key=key) if i != len(a): return a[i] raise ValueError diff --git a/Lib/bisect.py b/Lib/bisect.py index 7732c639e38699d..02b2fe0840dfd40 100644 --- a/Lib/bisect.py +++ b/Lib/bisect.py @@ -1,25 +1,38 @@ """Bisection algorithms.""" -def insort_right(a, x, lo=0, hi=None): +def insort_right(a, x, lo=0, hi=None, key=None): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the right of the rightmost x. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. + + Optional argument key is a function of one argument used to + customize the order. """ + # the python implementation does not + # need to optimize for performance + if key is None: + key = lambda e: e + + # we avoid computing key(x) in each iteration + x_value = key(x) + if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 - if x < a[mid]: hi = mid - else: lo = mid+1 + if x_value < key(a[mid]): + hi = mid + else: + lo = mid+1 a.insert(lo, x) -def bisect_right(a, x, lo=0, hi=None): +def bisect_right(a, x, lo=0, hi=None, key=None): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e <= x, and all e in @@ -28,39 +41,59 @@ def bisect_right(a, x, lo=0, hi=None): Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. + + Optional argument key is a function of one argument used to + customize the order. """ + if key is None: + key = lambda e: e + + x_value = key(x) + if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 - if x < a[mid]: hi = mid - else: lo = mid+1 + if x_value < key(a[mid]): + hi = mid + else: + lo = mid+1 return lo -def insort_left(a, x, lo=0, hi=None): +def insort_left(a, x, lo=0, hi=None, key=None): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the left of the leftmost x. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. + + Optional argument key is a function of one argument used to + customize the order. """ + if key is None: + key = lambda e: e + + x_value = key(x) + if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 - if a[mid] < x: lo = mid+1 - else: hi = mid + if key(a[mid]) < x_value: + lo = mid+1 + else: + hi = mid a.insert(lo, x) -def bisect_left(a, x, lo=0, hi=None): +def bisect_left(a, x, lo=0, hi=None, key=None): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in @@ -69,16 +102,26 @@ def bisect_left(a, x, lo=0, hi=None): Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. + + Optional argument key is a function of one argument used to + customize the order. """ + if key is None: + key = lambda e: e + + x_value = key(x) + if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 - if a[mid] < x: lo = mid+1 - else: hi = mid + if key(a[mid]) < x_value: + lo = mid+1 + else: + hi = mid return lo # Overwrite above definitions with a fast C implementation diff --git a/Lib/test/test_bisect.py b/Lib/test/test_bisect.py index 580a963f627a34d..324129679a7058b 100644 --- a/Lib/test/test_bisect.py +++ b/Lib/test/test_bisect.py @@ -199,6 +199,16 @@ def test_keyword_args(self): self.module.insort(a=data, x=25, lo=1, hi=3) self.assertEqual(data, [10, 20, 25, 25, 25, 30, 40, 50]) + def test_key(self): + data = ["z", "yy", "www"] + self.assertEqual(self.module.bisect_left(data, "xx", key=len), 1) + self.assertEqual(self.module.bisect_right(data, "xx", key=len), 2) + self.assertEqual(self.module.bisect(data, "xx", key=len), 2) + self.module.insort_left(data, "aa", key=len) + self.module.insort_right(data, "bb", key=len) + self.module.insort_right(data, "cc", key=len) + self.assertEqual(data, ['z', 'aa', 'yy', 'bb', 'cc', 'www']) + class TestBisectPython(TestBisect, unittest.TestCase): module = py_bisect diff --git a/Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst b/Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst new file mode 100644 index 000000000000000..dca48d1bbea8fa1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst @@ -0,0 +1,3 @@ +Functions in the `bisect` module now takes a key parameter to specify the +function to call before perfoming the comparison. Contributed by Rémi +Lapeyre. diff --git a/Modules/_bisectmodule.c b/Modules/_bisectmodule.c index 461a11f5099db32..24799ff019e8800 100644 --- a/Modules/_bisectmodule.c +++ b/Modules/_bisectmodule.c @@ -9,20 +9,34 @@ Converted to C by Dmitry Vasiliev (dima at hlabs.spb.ru). _Py_IDENTIFIER(insert); static inline Py_ssize_t -internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi) +internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi, PyObject *key) { PyObject *litem; + PyObject *item_value = NULL; Py_ssize_t mid; int res; + if (key == NULL) { + item_value = item; + Py_INCREF(item); + } else { + PyObject *arglist = Py_BuildValue("(O)", item); + item_value = PyObject_CallObject(key, arglist); + Py_DECREF(arglist); + if (item_value == NULL) { + return -1; + } + } + if (lo < 0) { PyErr_SetString(PyExc_ValueError, "lo must be non-negative"); - return -1; + goto fail; } if (hi == -1) { hi = PySequence_Size(list); - if (hi < 0) - return -1; + if (hi < 0) { + goto fail; + } } while (lo < hi) { /* The (size_t)cast ensures that the addition and subsequent division @@ -30,46 +44,70 @@ internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t signed overflow. (See issue 13496.) */ mid = ((size_t)lo + hi) / 2; litem = PySequence_GetItem(list, mid); - if (litem == NULL) - return -1; - res = PyObject_RichCompareBool(item, litem, Py_LT); + if (litem == NULL) { + goto fail; + } + if (key == NULL) { + res = PyObject_RichCompareBool(item_value, litem, Py_LT); + } + else { + PyObject *arglist = Py_BuildValue("(O)", litem); + PyObject *litem_value = PyObject_CallObject(key, arglist); + Py_DECREF(arglist); + if (litem_value == NULL) { + goto fail; + } + res = PyObject_RichCompareBool(item_value, litem_value, Py_LT); + Py_DECREF(litem_value); + } + Py_DECREF(litem); - if (res < 0) - return -1; - if (res) + if (res < 0) { + goto fail; + } + if (res) { hi = mid; - else + } + else { lo = mid + 1; + } } + Py_DECREF(item_value); return lo; + +fail: + Py_DECREF(item_value); + return -1; } static PyObject * bisect_right(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item; + PyObject *key = NULL; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn:bisect_right", - keywords, &list, &item, &lo, &hi)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:bisect_right", + keywords, &list, &item, &lo, &hi, &key)) return NULL; } - index = internal_bisect_right(list, item, lo, hi); - if (index < 0) + index = internal_bisect_right(list, item, lo, hi, key); + if (index < 0) { return NULL; + } return PyLong_FromSsize_t(index); } PyDoc_STRVAR(bisect_right_doc, -"bisect_right(a, x[, lo[, hi]]) -> index\n\ +"bisect_right(a, x[, lo[, hi[, key]]]) -> index\n\ \n\ Return the index where to insert item x in list a, assuming a is sorted.\n\ \n\ @@ -78,29 +116,34 @@ a[i:] have e > x. So if x already appears in the list, i points just\n\ beyond the rightmost x already there\n\ \n\ Optional args lo (default 0) and hi (default len(a)) bound the\n\ -slice of a to be searched.\n"); +slice of a to be searched.\n\ +\n\ +Optional argument key is a function of one argument used to\n\ +customize the order.\n"); static PyObject * insort_right(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item, *result; + PyObject *key = NULL; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn:insort_right", - keywords, &list, &item, &lo, &hi)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:insort_right", + keywords, &list, &item, &lo, &hi, &key)) return NULL; } - index = internal_bisect_right(list, item, lo, hi); - if (index < 0) + index = internal_bisect_right(list, item, lo, hi, key); + if (index < 0) { return NULL; + } if (PyList_CheckExact(list)) { if (PyList_Insert(list, index, item) < 0) return NULL; @@ -116,30 +159,47 @@ insort_right(PyObject *self, PyObject *args, PyObject *kw) } PyDoc_STRVAR(insort_right_doc, -"insort_right(a, x[, lo[, hi]])\n\ +"insort_right(a, x[, lo[, hi[, key]]])\n\ \n\ Insert item x in list a, and keep it sorted assuming a is sorted.\n\ \n\ If x is already in a, insert it to the right of the rightmost x.\n\ \n\ Optional args lo (default 0) and hi (default len(a)) bound the\n\ -slice of a to be searched.\n"); +slice of a to be searched.\n\ +\n\ +Optional argument key is a function of one argument used to\n\ +customize the order.\n"); static inline Py_ssize_t -internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi) +internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi, PyObject *key) { PyObject *litem; + PyObject *item_value = NULL; Py_ssize_t mid; int res; + if (key == NULL) { + item_value = item; + Py_INCREF(item); + } else { + PyObject *arglist = Py_BuildValue("(O)", item); + item_value = PyObject_CallObject(key, arglist); + Py_DECREF(arglist); + if (item_value == NULL) { + return -1; + } + } + if (lo < 0) { PyErr_SetString(PyExc_ValueError, "lo must be non-negative"); - return -1; + goto fail; } if (hi == -1) { hi = PySequence_Size(list); - if (hi < 0) - return -1; + if (hi < 0) { + goto fail; + } } while (lo < hi) { /* The (size_t)cast ensures that the addition and subsequent division @@ -147,46 +207,69 @@ internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t h signed overflow. (See issue 13496.) */ mid = ((size_t)lo + hi) / 2; litem = PySequence_GetItem(list, mid); - if (litem == NULL) - return -1; - res = PyObject_RichCompareBool(litem, item, Py_LT); + if (litem == NULL) { + goto fail; + } + if (key == NULL) { + res = PyObject_RichCompareBool(litem, item_value, Py_LT); + } + else { + PyObject *arglist = Py_BuildValue("(O)", litem); + PyObject *litem_value = PyObject_CallObject(key, arglist); + Py_DECREF(arglist); + if (litem_value == NULL) { + goto fail; + } + res = PyObject_RichCompareBool(litem_value, item_value, Py_LT); + } + Py_DECREF(litem); - if (res < 0) - return -1; - if (res) + if (res < 0) { + goto fail; + } + if (res) { lo = mid + 1; - else + } + else { hi = mid; + } } + Py_DECREF(item_value); return lo; + +fail: + Py_DECREF(item_value); + return -1; } static PyObject * bisect_left(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item; + PyObject *key = NULL; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn:bisect_left", - keywords, &list, &item, &lo, &hi)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:bisect_left", + keywords, &list, &item, &lo, &hi, &key)) return NULL; } - index = internal_bisect_left(list, item, lo, hi); - if (index < 0) + index = internal_bisect_left(list, item, lo, hi, key); + if (index < 0) { return NULL; + } return PyLong_FromSsize_t(index); } PyDoc_STRVAR(bisect_left_doc, -"bisect_left(a, x[, lo[, hi]]) -> index\n\ +"bisect_left(a, x[, lo[, hi[, key]]]) -> index\n\ \n\ Return the index where to insert item x in list a, assuming a is sorted.\n\ \n\ @@ -195,26 +278,30 @@ a[i:] have e >= x. So if x already appears in the list, i points just\n\ before the leftmost x already there.\n\ \n\ Optional args lo (default 0) and hi (default len(a)) bound the\n\ -slice of a to be searched.\n"); +slice of a to be searched.\n\ +\n\ +Optional argument key is a function of one argument used to\n\ +customize the order.\n"); static PyObject * insort_left(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item, *result; + PyObject *key = NULL; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn:insort_left", - keywords, &list, &item, &lo, &hi)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:insort_left", + keywords, &list, &item, &lo, &hi, &key)) return NULL; } - index = internal_bisect_left(list, item, lo, hi); + index = internal_bisect_left(list, item, lo, hi, key); if (index < 0) return NULL; if (PyList_CheckExact(list)) { @@ -231,14 +318,17 @@ insort_left(PyObject *self, PyObject *args, PyObject *kw) } PyDoc_STRVAR(insort_left_doc, -"insort_left(a, x[, lo[, hi]])\n\ +"insort_left(a, x[, lo[, hi[, key]]])\n\ \n\ Insert item x in list a, and keep it sorted assuming a is sorted.\n\ \n\ If x is already in a, insert it to the left of the leftmost x.\n\ \n\ Optional args lo (default 0) and hi (default len(a)) bound the\n\ -slice of a to be searched.\n"); +slice of a to be searched.\n\ +\n\ +Optional argument key is a function of one argument used to\n\ +customize the order.\n"); static PyMethodDef bisect_methods[] = { {"bisect_right", (PyCFunction)(void(*)(void))bisect_right, @@ -258,7 +348,10 @@ PyDoc_STRVAR(module_doc, This module provides support for maintaining a list in sorted order without\n\ having to sort the list after each insertion. For long lists of items with\n\ expensive comparison operations, this can be an improvement over the more\n\ -common approach.\n"); +common approach.\n\ +\n\ +Optional argument key is a function of one argument used to\n\ +customize the order.\n"); static struct PyModuleDef _bisectmodule = { From d6082a58c54bb5d833de9c74ca43989181821f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Thu, 7 Feb 2019 16:27:31 +0100 Subject: [PATCH 02/12] Support None as argument --- Lib/test/test_bisect.py | 6 ++++++ Modules/_bisectmodule.c | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Lib/test/test_bisect.py b/Lib/test/test_bisect.py index 324129679a7058b..a62864476a867af 100644 --- a/Lib/test/test_bisect.py +++ b/Lib/test/test_bisect.py @@ -209,6 +209,12 @@ def test_key(self): self.module.insort_right(data, "cc", key=len) self.assertEqual(data, ['z', 'aa', 'yy', 'bb', 'cc', 'www']) + # check None is accepted + self.module.insort_right(data, "cc", key=None) + self.module.insort_left(data, "cc", key=None) + self.module.bisect_right(data, "cc", key=None) + self.module.bisect_left(data, "cc", key=None) + class TestBisectPython(TestBisect, unittest.TestCase): module = py_bisect diff --git a/Modules/_bisectmodule.c b/Modules/_bisectmodule.c index 24799ff019e8800..c3e4c9d061cbb5c 100644 --- a/Modules/_bisectmodule.c +++ b/Modules/_bisectmodule.c @@ -99,6 +99,9 @@ bisect_right(PyObject *self, PyObject *args, PyObject *kw) keywords, &list, &item, &lo, &hi, &key)) return NULL; } + if (key == Py_None) { + key = NULL; + } index = internal_bisect_right(list, item, lo, hi, key); if (index < 0) { return NULL; @@ -140,6 +143,9 @@ insort_right(PyObject *self, PyObject *args, PyObject *kw) keywords, &list, &item, &lo, &hi, &key)) return NULL; } + if (key == Py_None) { + key = NULL; + } index = internal_bisect_right(list, item, lo, hi, key); if (index < 0) { return NULL; @@ -261,6 +267,9 @@ bisect_left(PyObject *self, PyObject *args, PyObject *kw) keywords, &list, &item, &lo, &hi, &key)) return NULL; } + if (key == Py_None) { + key = NULL; + } index = internal_bisect_left(list, item, lo, hi, key); if (index < 0) { return NULL; @@ -301,6 +310,9 @@ insort_left(PyObject *self, PyObject *args, PyObject *kw) keywords, &list, &item, &lo, &hi, &key)) return NULL; } + if (key == Py_None) { + key = NULL; + } index = internal_bisect_left(list, item, lo, hi, key); if (index < 0) return NULL; From b7aaa1adade4b6f6273df2b8146fb464fb65de56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Thu, 7 Feb 2019 16:47:14 +0100 Subject: [PATCH 03/12] Fix documentation --- Doc/library/bisect.rst | 2 +- Doc/tools/susp-ignored.csv | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/bisect.rst b/Doc/library/bisect.rst index 84f7fba38323d9d..06a2623f075f63e 100644 --- a/Doc/library/bisect.rst +++ b/Doc/library/bisect.rst @@ -28,7 +28,7 @@ The following functions are provided: which should be considered; by default the entire list is used. The parameter *key* specifies a function of one argument that is used to extract a comparison key from each element in *a* (for example, - `key=str.lower`). The default value is `None` (compare the elements + ``key=str.lower``). The default value is ``None`` (compare the elements directly). If *x* is already present in *a*, the insertion point will be before diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 3c23dc12e3ad6ed..f57f44523a6e45c 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -106,8 +106,8 @@ howto/regex,,::, howto/regex,,:foo,(?:foo) howto/urllib2,,:password,"""joe:password@example.com""" library/audioop,,:ipos,"# factor = audioop.findfactor(in_test[ipos*2:ipos*2+len(out_test)]," -library/bisect,32,:hi,all(val >= x for val in a[i:hi]) -library/bisect,42,:hi,all(val > x for val in a[i:hi]) +library/bisect,42,:hi,all(val >= x for val in a[i:hi]) +library/bisect,52,:hi,all(val > x for val in a[i:hi]) library/configparser,,:home,my_dir: ${Common:home_dir}/twosheds library/configparser,,:option,${section:option} library/configparser,,:path,python_dir: ${Frameworks:path}/Python/Versions/${Frameworks:Python} From 1bc959d8dceca0cadc2519075366ed40d2f0a0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Sun, 17 Mar 2019 18:27:09 +0100 Subject: [PATCH 04/12] Improve documentation --- Doc/library/bisect.rst | 4 ++-- .../next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/bisect.rst b/Doc/library/bisect.rst index 06a2623f075f63e..701c359c372b183 100644 --- a/Doc/library/bisect.rst +++ b/Doc/library/bisect.rst @@ -27,14 +27,14 @@ The following functions are provided: The parameters *lo* and *hi* may be used to specify a subset of the list which should be considered; by default the entire list is used. The parameter *key* specifies a function of one argument that is used to - extract a comparison key from each element in *a* (for example, + extract a comparison key from each element in *a* and from *x* (for example, ``key=str.lower``). The default value is ``None`` (compare the elements directly). If *x* is already present in *a*, the insertion point will be before (to the left of) any existing entries. The return value is suitable for use as the first parameter to ``list.insert()`` assuming that *a* is - already sorted. + already sorted according to *key*. The returned insertion point *i* partitions the array *a* into two halves so that ``all(val < x for val in a[lo:i])`` for the left side and diff --git a/Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst b/Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst index dca48d1bbea8fa1..37c33ab4d64bffb 100644 --- a/Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst +++ b/Misc/NEWS.d/next/Library/2019-02-07-15-44-12.bpo-4356.i6h86W.rst @@ -1,3 +1,3 @@ -Functions in the `bisect` module now takes a key parameter to specify the +Functions in the `bisect` module now take a key parameter to specify the function to call before perfoming the comparison. Contributed by Rémi Lapeyre. From e1ce337bb6d0ed8ed34ec765f22cb68c5785662f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 31 May 2019 17:19:10 +0200 Subject: [PATCH 05/12] Split logic for key=None --- Lib/bisect.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Lib/bisect.py b/Lib/bisect.py index 381c19c8e3d828f..3c45988d91eea91 100644 --- a/Lib/bisect.py +++ b/Lib/bisect.py @@ -29,15 +29,22 @@ def bisect_right(a, x, lo=0, hi=None, key=None): customize the order. """ - if key is None: - key = lambda e: e - - x_value = key(x) - if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) + + if key is None: + while lo < hi: + mid = (lo+hi)//2 + if x < a[mid]: + hi = mid + else: + lo = mid+1 + return lo + + x_value = key(x) + while lo < hi: mid = (lo+hi)//2 if x_value < key(a[mid]): @@ -76,15 +83,22 @@ def bisect_left(a, x, lo=0, hi=None, key=None): customize the order. """ - if key is None: - key = lambda e: e - - x_value = key(x) - if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) + + if key is None: + while lo < hi: + mid = (lo+hi)//2 + if a[mid] < x: + lo = mid+1 + else: + hi = mid + return lo + + x_value = key(x) + while lo < hi: mid = (lo+hi)//2 if key(a[mid]) < x_value: From 4337e734053b30baa0d3e1bfa17c77441ef8131a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 31 May 2019 17:19:51 +0200 Subject: [PATCH 06/12] Test that key is a keyword only argument --- Lib/test/test_bisect.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_bisect.py b/Lib/test/test_bisect.py index a62864476a867af..89ff9a4260d404f 100644 --- a/Lib/test/test_bisect.py +++ b/Lib/test/test_bisect.py @@ -199,6 +199,13 @@ def test_keyword_args(self): self.module.insort(a=data, x=25, lo=1, hi=3) self.assertEqual(data, [10, 20, 25, 25, 25, 30, 40, 50]) + for func in (self.module.bisect, self.module.bisect_right, + self.module.bisect_left, self.module.insort_left, + self.module.insort_right, self.module.insort): + + with self.assertRaises(TypeError): + func(data, 25, 1, 3, lambda e: e) + def test_key(self): data = ["z", "yy", "www"] self.assertEqual(self.module.bisect_left(data, "xx", key=len), 1) From d9022a9ed2d6d2719e035ef27300b197aaec6668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 31 May 2019 17:20:06 +0200 Subject: [PATCH 07/12] Make key a keyword only argument --- Doc/library/bisect.rst | 22 +++++++++++----------- Lib/bisect.py | 8 ++++---- Modules/_bisectmodule.c | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Doc/library/bisect.rst b/Doc/library/bisect.rst index 701c359c372b183..194b39136e1069d 100644 --- a/Doc/library/bisect.rst +++ b/Doc/library/bisect.rst @@ -21,7 +21,7 @@ example of the algorithm (the boundary conditions are already right!). The following functions are provided: -.. function:: bisect_left(a, x, lo=0, hi=len(a), key=None) +.. function:: bisect_left(a, x, lo=0, hi=len(a), *, key=None) Locate the insertion point for *x* in *a* to maintain sorted order. The parameters *lo* and *hi* may be used to specify a subset of the list @@ -40,8 +40,8 @@ The following functions are provided: that ``all(val < x for val in a[lo:i])`` for the left side and ``all(val >= x for val in a[i:hi])`` for the right side. -.. function:: bisect_right(a, x, lo=0, hi=len(a), key=None) - bisect(a, x, lo=0, hi=len(a), key=None) +.. function:: bisect_right(a, x, lo=0, hi=len(a), *, key=None) + bisect(a, x, lo=0, hi=len(a), *, key=None) Similar to :func:`bisect_left`, but returns an insertion point which comes after (to the right of) any existing entries of *x* in *a*. @@ -50,15 +50,15 @@ The following functions are provided: that ``all(val <= x for val in a[lo:i])`` for the left side and ``all(val > x for val in a[i:hi])`` for the right side. -.. function:: insort_left(a, x, lo=0, hi=len(a), key=None) +.. function:: insort_left(a, x, lo=0, hi=len(a), *, key=None) Insert *x* in *a* in sorted order. This is equivalent to ``a.insert(bisect.bisect_left(a, x, lo, hi), x)`` assuming that *a* is already sorted. Keep in mind that the O(log n) search is dominated by the slow O(n) insertion step. -.. function:: insort_right(a, x, lo=0, hi=len(a), key=None) - insort(a, x, lo=0, hi=len(a), key=None) +.. function:: insort_right(a, x, lo=0, hi=len(a), *, key=None) + insort(a, x, lo=0, hi=len(a), *, key=None) Similar to :func:`insort_left`, but inserting *x* in *a* after any existing entries of *x*. @@ -80,35 +80,35 @@ can be tricky or awkward to use for common searching tasks. The following five functions show how to transform them into the standard lookups for sorted lists:: - def index(a, x, key=None): + def index(a, x, *, key=None): 'Locate the leftmost value exactly equal to x' i = bisect_left(a, x, key=key) if i != len(a) and a[i] == x: return i raise ValueError - def find_lt(a, x, key=None): + def find_lt(a, x, *, key=None): 'Find rightmost value less than x' i = bisect_left(a, x, key=key) if i: return a[i-1] raise ValueError - def find_le(a, x, key=None): + def find_le(a, x, *, key=None): 'Find rightmost value less than or equal to x' i = bisect_right(a, x, key=key) if i: return a[i-1] raise ValueError - def find_gt(a, x, key=None): + def find_gt(a, x, *, key=None): 'Find leftmost value greater than x' i = bisect_right(a, x, key=key) if i != len(a): return a[i] raise ValueError - def find_ge(a, x, key=None): + def find_ge(a, x, *, key=None): 'Find leftmost item greater than or equal to x' i = bisect_left(a, x, key=key) if i != len(a): diff --git a/Lib/bisect.py b/Lib/bisect.py index 3c45988d91eea91..3bcbec486066df3 100644 --- a/Lib/bisect.py +++ b/Lib/bisect.py @@ -1,6 +1,6 @@ """Bisection algorithms.""" -def insort_right(a, x, lo=0, hi=None, key=None): +def insort_right(a, x, lo=0, hi=None, *, key=None): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the right of the rightmost x. @@ -15,7 +15,7 @@ def insort_right(a, x, lo=0, hi=None, key=None): lo = bisect_right(a, x, lo, hi, key=key) a.insert(lo, x) -def bisect_right(a, x, lo=0, hi=None, key=None): +def bisect_right(a, x, lo=0, hi=None, *, key=None): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e <= x, and all e in @@ -53,7 +53,7 @@ def bisect_right(a, x, lo=0, hi=None, key=None): lo = mid+1 return lo -def insort_left(a, x, lo=0, hi=None, key=None): +def insort_left(a, x, lo=0, hi=None, *, key=None): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the left of the leftmost x. @@ -69,7 +69,7 @@ def insort_left(a, x, lo=0, hi=None, key=None): a.insert(lo, x) -def bisect_left(a, x, lo=0, hi=None, key=None): +def bisect_left(a, x, lo=0, hi=None, *, key=None): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in diff --git a/Modules/_bisectmodule.c b/Modules/_bisectmodule.c index c3e4c9d061cbb5c..fb269ba876d2554 100644 --- a/Modules/_bisectmodule.c +++ b/Modules/_bisectmodule.c @@ -95,7 +95,7 @@ bisect_right(PyObject *self, PyObject *args, PyObject *kw) item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:bisect_right", + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:bisect_right", keywords, &list, &item, &lo, &hi, &key)) return NULL; } @@ -139,7 +139,7 @@ insort_right(PyObject *self, PyObject *args, PyObject *kw) item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:insort_right", + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:insort_right", keywords, &list, &item, &lo, &hi, &key)) return NULL; } @@ -263,7 +263,7 @@ bisect_left(PyObject *self, PyObject *args, PyObject *kw) item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:bisect_left", + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:bisect_left", keywords, &list, &item, &lo, &hi, &key)) return NULL; } @@ -306,7 +306,7 @@ insort_left(PyObject *self, PyObject *args, PyObject *kw) list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nnO:insort_left", + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:insort_left", keywords, &list, &item, &lo, &hi, &key)) return NULL; } From 84c18beacd31d8cbe06c41a59167254b193f3a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 31 May 2019 17:28:56 +0200 Subject: [PATCH 08/12] Add note in the documentation regarding lru_cache --- Doc/library/bisect.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Doc/library/bisect.rst b/Doc/library/bisect.rst index 194b39136e1069d..dd1bba58542815d 100644 --- a/Doc/library/bisect.rst +++ b/Doc/library/bisect.rst @@ -25,12 +25,18 @@ The following functions are provided: Locate the insertion point for *x* in *a* to maintain sorted order. The parameters *lo* and *hi* may be used to specify a subset of the list - which should be considered; by default the entire list is used. The - parameter *key* specifies a function of one argument that is used to + which should be considered; by default the entire list is used. + + The parameter *key* specifies a function of one argument that is used to extract a comparison key from each element in *a* and from *x* (for example, ``key=str.lower``). The default value is ``None`` (compare the elements directly). + .. note:: + + When specifying a custom *key* function, you should wrap it with + :func:`functools.lru_cache` if the *key* function is not already fast. + If *x* is already present in *a*, the insertion point will be before (to the left of) any existing entries. The return value is suitable for use as the first parameter to ``list.insert()`` assuming that *a* is From 54960ed55b004aadd18afb828bec05ffcac65840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 31 May 2019 17:36:02 +0200 Subject: [PATCH 09/12] Add test for reverse support --- Lib/test/test_bisect.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_bisect.py b/Lib/test/test_bisect.py index 89ff9a4260d404f..12e0f7de459a3d7 100644 --- a/Lib/test/test_bisect.py +++ b/Lib/test/test_bisect.py @@ -222,6 +222,17 @@ def test_key(self): self.module.bisect_right(data, "cc", key=None) self.module.bisect_left(data, "cc", key=None) + def test_reverse(self): + data = [50, 40, 30, 20, 10] + self.assertEqual(self.module.bisect_left(data, 15, reverse=True), 4) + self.assertEqual(self.module.bisect_right(data, 15, reverse=True), 4) + self.assertEqual(self.module.bisect(data, 15, reverse=True), 4) + self.module.insort_left(data, 15, reverse=True) + self.module.insort_right(data, 15, reverse=True) + self.module.insort(data, 15, reverse=True) + self.assertEqual(data, [50, 40, 30, 20, 15, 15, 15, 10]) + + class TestBisectPython(TestBisect, unittest.TestCase): module = py_bisect From ef986581f8fc062e907650320ee0bd11a06ccdab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 31 May 2019 18:07:53 +0200 Subject: [PATCH 10/12] Add reverse support --- Doc/library/bisect.rst | 25 ++++++++++-------- Lib/bisect.py | 12 ++++----- Modules/_bisectmodule.c | 58 +++++++++++++++++++++++++---------------- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/Doc/library/bisect.rst b/Doc/library/bisect.rst index dd1bba58542815d..3cede24bdc82971 100644 --- a/Doc/library/bisect.rst +++ b/Doc/library/bisect.rst @@ -21,7 +21,7 @@ example of the algorithm (the boundary conditions are already right!). The following functions are provided: -.. function:: bisect_left(a, x, lo=0, hi=len(a), *, key=None) +.. function:: bisect_left(a, x, lo=0, hi=len(a), *, key=None, reverse=False) Locate the insertion point for *x* in *a* to maintain sorted order. The parameters *lo* and *hi* may be used to specify a subset of the list @@ -37,6 +37,9 @@ The following functions are provided: When specifying a custom *key* function, you should wrap it with :func:`functools.lru_cache` if the *key* function is not already fast. + The parameter *reverse* is a boolean value. If set to ``True``, the list is + supposed to be sorted in descending order. + If *x* is already present in *a*, the insertion point will be before (to the left of) any existing entries. The return value is suitable for use as the first parameter to ``list.insert()`` assuming that *a* is @@ -46,8 +49,8 @@ The following functions are provided: that ``all(val < x for val in a[lo:i])`` for the left side and ``all(val >= x for val in a[i:hi])`` for the right side. -.. function:: bisect_right(a, x, lo=0, hi=len(a), *, key=None) - bisect(a, x, lo=0, hi=len(a), *, key=None) +.. function:: bisect_right(a, x, lo=0, hi=len(a), *, key=None, reverse=False) + bisect(a, x, lo=0, hi=len(a), *, key=None, reverse=False) Similar to :func:`bisect_left`, but returns an insertion point which comes after (to the right of) any existing entries of *x* in *a*. @@ -56,15 +59,15 @@ The following functions are provided: that ``all(val <= x for val in a[lo:i])`` for the left side and ``all(val > x for val in a[i:hi])`` for the right side. -.. function:: insort_left(a, x, lo=0, hi=len(a), *, key=None) +.. function:: insort_left(a, x, lo=0, hi=len(a), *, key=None, reverse=False) Insert *x* in *a* in sorted order. This is equivalent to ``a.insert(bisect.bisect_left(a, x, lo, hi), x)`` assuming that *a* is already sorted. Keep in mind that the O(log n) search is dominated by the slow O(n) insertion step. -.. function:: insort_right(a, x, lo=0, hi=len(a), *, key=None) - insort(a, x, lo=0, hi=len(a), *, key=None) +.. function:: insort_right(a, x, lo=0, hi=len(a), *, key=None, reverse=False) + insort(a, x, lo=0, hi=len(a), *, key=None, reverse=False) Similar to :func:`insort_left`, but inserting *x* in *a* after any existing entries of *x*. @@ -86,35 +89,35 @@ can be tricky or awkward to use for common searching tasks. The following five functions show how to transform them into the standard lookups for sorted lists:: - def index(a, x, *, key=None): + def index(a, x, *, key=None, reverse=False): 'Locate the leftmost value exactly equal to x' i = bisect_left(a, x, key=key) if i != len(a) and a[i] == x: return i raise ValueError - def find_lt(a, x, *, key=None): + def find_lt(a, x, *, key=None, reverse=False): 'Find rightmost value less than x' i = bisect_left(a, x, key=key) if i: return a[i-1] raise ValueError - def find_le(a, x, *, key=None): + def find_le(a, x, *, key=None, reverse=False): 'Find rightmost value less than or equal to x' i = bisect_right(a, x, key=key) if i: return a[i-1] raise ValueError - def find_gt(a, x, *, key=None): + def find_gt(a, x, *, key=None, reverse=False): 'Find leftmost value greater than x' i = bisect_right(a, x, key=key) if i != len(a): return a[i] raise ValueError - def find_ge(a, x, *, key=None): + def find_ge(a, x, *, key=None, reverse=False): 'Find leftmost item greater than or equal to x' i = bisect_left(a, x, key=key) if i != len(a): diff --git a/Lib/bisect.py b/Lib/bisect.py index 3bcbec486066df3..20b60511bcb586d 100644 --- a/Lib/bisect.py +++ b/Lib/bisect.py @@ -1,6 +1,6 @@ """Bisection algorithms.""" -def insort_right(a, x, lo=0, hi=None, *, key=None): +def insort_right(a, x, lo=0, hi=None, *, key=None, reverse=False): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the right of the rightmost x. @@ -12,10 +12,10 @@ def insort_right(a, x, lo=0, hi=None, *, key=None): customize the order. """ - lo = bisect_right(a, x, lo, hi, key=key) + lo = bisect_right(a, x, lo, hi, key=key, reverse=reverse) a.insert(lo, x) -def bisect_right(a, x, lo=0, hi=None, *, key=None): +def bisect_right(a, x, lo=0, hi=None, *, key=None, reverse=False): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e <= x, and all e in @@ -53,7 +53,7 @@ def bisect_right(a, x, lo=0, hi=None, *, key=None): lo = mid+1 return lo -def insort_left(a, x, lo=0, hi=None, *, key=None): +def insort_left(a, x, lo=0, hi=None, *, key=None, reverse=False): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the left of the leftmost x. @@ -65,11 +65,11 @@ def insort_left(a, x, lo=0, hi=None, *, key=None): customize the order. """ - lo = bisect_left(a, x, lo, hi, key=key) + lo = bisect_left(a, x, lo, hi, key=key, reverse=reverse) a.insert(lo, x) -def bisect_left(a, x, lo=0, hi=None, *, key=None): +def bisect_left(a, x, lo=0, hi=None, *, key=None, reverse=False): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in diff --git a/Modules/_bisectmodule.c b/Modules/_bisectmodule.c index fb269ba876d2554..6ac00eceb70e594 100644 --- a/Modules/_bisectmodule.c +++ b/Modules/_bisectmodule.c @@ -9,7 +9,8 @@ Converted to C by Dmitry Vasiliev (dima at hlabs.spb.ru). _Py_IDENTIFIER(insert); static inline Py_ssize_t -internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi, PyObject *key) +internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi, + PyObject *key, int reverse) { PyObject *litem; PyObject *item_value = NULL; @@ -38,6 +39,8 @@ internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t goto fail; } } + + int comparator = reverse ? Py_GT : Py_LT; while (lo < hi) { /* The (size_t)cast ensures that the addition and subsequent division are performed as unsigned operations, avoiding difficulties from @@ -48,7 +51,7 @@ internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t goto fail; } if (key == NULL) { - res = PyObject_RichCompareBool(item_value, litem, Py_LT); + res = PyObject_RichCompareBool(item_value, litem, comparator); } else { PyObject *arglist = Py_BuildValue("(O)", litem); @@ -57,7 +60,7 @@ internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t if (litem_value == NULL) { goto fail; } - res = PyObject_RichCompareBool(item_value, litem_value, Py_LT); + res = PyObject_RichCompareBool(item_value, litem_value, comparator); Py_DECREF(litem_value); } @@ -85,24 +88,26 @@ bisect_right(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item; PyObject *key = NULL; + int reverse = 0; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", "reverse", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:bisect_right", - keywords, &list, &item, &lo, &hi, &key)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$Op:bisect_right", + keywords, &list, &item, &lo, &hi, + &key, &reverse)) return NULL; } if (key == Py_None) { key = NULL; } - index = internal_bisect_right(list, item, lo, hi, key); + index = internal_bisect_right(list, item, lo, hi, key, reverse); if (index < 0) { return NULL; } @@ -129,24 +134,26 @@ insort_right(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item, *result; PyObject *key = NULL; + int reverse = 0; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", "reverse", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:insort_right", - keywords, &list, &item, &lo, &hi, &key)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$Op:insort_right", + keywords, &list, &item, &lo, &hi, + &key, &reverse)) return NULL; } if (key == Py_None) { key = NULL; } - index = internal_bisect_right(list, item, lo, hi, key); + index = internal_bisect_right(list, item, lo, hi, key, reverse); if (index < 0) { return NULL; } @@ -178,7 +185,8 @@ Optional argument key is a function of one argument used to\n\ customize the order.\n"); static inline Py_ssize_t -internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi, PyObject *key) +internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi, + PyObject *key, int reverse) { PyObject *litem; PyObject *item_value = NULL; @@ -207,6 +215,8 @@ internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t h goto fail; } } + + int comparator = reverse ? Py_GT : Py_LT; while (lo < hi) { /* The (size_t)cast ensures that the addition and subsequent division are performed as unsigned operations, avoiding difficulties from @@ -217,7 +227,7 @@ internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t h goto fail; } if (key == NULL) { - res = PyObject_RichCompareBool(litem, item_value, Py_LT); + res = PyObject_RichCompareBool(litem, item_value, comparator); } else { PyObject *arglist = Py_BuildValue("(O)", litem); @@ -226,7 +236,7 @@ internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t h if (litem_value == NULL) { goto fail; } - res = PyObject_RichCompareBool(litem_value, item_value, Py_LT); + res = PyObject_RichCompareBool(litem_value, item_value, comparator); } Py_DECREF(litem); @@ -253,24 +263,26 @@ bisect_left(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item; PyObject *key = NULL; + int reverse = 0; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", "reverse", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:bisect_left", - keywords, &list, &item, &lo, &hi, &key)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$Op:bisect_left", + keywords, &list, &item, &lo, &hi, + &key, &reverse)) return NULL; } if (key == Py_None) { key = NULL; } - index = internal_bisect_left(list, item, lo, hi, key); + index = internal_bisect_left(list, item, lo, hi, key, reverse); if (index < 0) { return NULL; } @@ -297,23 +309,25 @@ insort_left(PyObject *self, PyObject *args, PyObject *kw) { PyObject *list, *item, *result; PyObject *key = NULL; + int reverse = 0; Py_ssize_t lo = 0; Py_ssize_t hi = -1; Py_ssize_t index; - static char *keywords[] = {"a", "x", "lo", "hi", "key", NULL}; + static char *keywords[] = {"a", "x", "lo", "hi", "key", "reverse", NULL}; if (kw == NULL && PyTuple_GET_SIZE(args) == 2) { list = PyTuple_GET_ITEM(args, 0); item = PyTuple_GET_ITEM(args, 1); } else { - if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$O:insort_left", - keywords, &list, &item, &lo, &hi, &key)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO|nn$Op:insort_left", + keywords, &list, &item, &lo, &hi, + &key, &reverse)) return NULL; } if (key == Py_None) { key = NULL; } - index = internal_bisect_left(list, item, lo, hi, key); + index = internal_bisect_left(list, item, lo, hi, key, reverse); if (index < 0) return NULL; if (PyList_CheckExact(list)) { From 49ef1f7abb5b291ce8dc740bcaa6e67460f4de29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Fri, 31 May 2019 18:46:37 +0200 Subject: [PATCH 11/12] Whitelist suspicious constructs in documentation --- Doc/tools/susp-ignored.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/susp-ignored.csv b/Doc/tools/susp-ignored.csv index 94f8a382f7cfe2f..72fd636ade626ee 100644 --- a/Doc/tools/susp-ignored.csv +++ b/Doc/tools/susp-ignored.csv @@ -106,8 +106,8 @@ howto/regex,,::, howto/regex,,:foo,(?:foo) howto/urllib2,,:password,"""joe:password@example.com""" library/audioop,,:ipos,"# factor = audioop.findfactor(in_test[ipos*2:ipos*2+len(out_test)]," -library/bisect,42,:hi,all(val >= x for val in a[i:hi]) -library/bisect,52,:hi,all(val > x for val in a[i:hi]) +library/bisect,,:hi,all(val >= x for val in a[i:hi]) +library/bisect,,:hi,all(val > x for val in a[i:hi]) library/configparser,,:home,my_dir: ${Common:home_dir}/twosheds library/configparser,,:option,${section:option} library/configparser,,:path,python_dir: ${Frameworks:path}/Python/Versions/${Frameworks:Python} From 6bfe7bce87531cddfdc83542821555de0bb61b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Sat, 8 Jun 2019 16:32:03 +0200 Subject: [PATCH 12/12] Implement reverse bisect in Python --- Lib/bisect.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Lib/bisect.py b/Lib/bisect.py index 20b60511bcb586d..64052f905ce8f7a 100644 --- a/Lib/bisect.py +++ b/Lib/bisect.py @@ -35,6 +35,15 @@ def bisect_right(a, x, lo=0, hi=None, *, key=None, reverse=False): hi = len(a) if key is None: + if reverse: + while lo < hi: + mid = (lo+hi)//2 + if x > a[mid]: + hi = mid + else: + lo = mid+1 + return lo + while lo < hi: mid = (lo+hi)//2 if x < a[mid]: @@ -45,6 +54,15 @@ def bisect_right(a, x, lo=0, hi=None, *, key=None, reverse=False): x_value = key(x) + if reverse: + while lo < hi: + mid = (lo+hi)//2 + if x_value > key(a[mid]): + hi = mid + else: + lo = mid+1 + return lo + while lo < hi: mid = (lo+hi)//2 if x_value < key(a[mid]): @@ -89,6 +107,15 @@ def bisect_left(a, x, lo=0, hi=None, *, key=None, reverse=False): hi = len(a) if key is None: + if reverse: + while lo < hi: + mid = (lo+hi)//2 + if a[mid] > x: + lo = mid+1 + else: + hi = mid + return lo + while lo < hi: mid = (lo+hi)//2 if a[mid] < x: @@ -99,6 +126,15 @@ def bisect_left(a, x, lo=0, hi=None, *, key=None, reverse=False): x_value = key(x) + if reverse: + while lo < hi: + mid = (lo+hi)//2 + if key(a[mid]) > x_value: + lo = mid+1 + else: + hi = mid + return lo + while lo < hi: mid = (lo+hi)//2 if key(a[mid]) < x_value: