]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-125420: implement `Sequence.count` API on `memoryview` objects (#125443)
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>
Tue, 10 Dec 2024 10:12:33 +0000 (11:12 +0100)
committerGitHub <noreply@github.com>
Tue, 10 Dec 2024 10:12:33 +0000 (10:12 +0000)
Doc/library/stdtypes.rst
Lib/test/test_memoryview.py
Misc/NEWS.d/next/Core_and_Builtins/2024-10-14-12-34-51.gh-issue-125420.jABXoZ.rst [new file with mode: 0644]
Objects/clinic/memoryobject.c.h
Objects/memoryobject.c

index f4e635dd61e3dc60a7894ba0ed9957ae926a3de9..d5f7714ced6873d340cc5226673ff4b75c72b791 100644 (file)
@@ -4149,7 +4149,13 @@ copying.
       .. versionchanged:: 3.5
          The source format is no longer restricted when casting to a byte view.
 
-   .. method:: index(value, start=0, stop=sys.maxsize, /)
+   .. method:: count(value, /)
+
+      Count the number of occurrences of *value*.
+
+      .. versionadded:: next
+
+  .. method:: index(value, start=0, stop=sys.maxsize, /)
 
       Return the index of the first occurrence of *value* (at or after
       index *start* and before index *stop*).
index 8f07828c19790db34fd81623a7996680e4c2de9b..96320ebef6467aa7675923182198755683a001b6 100644 (file)
@@ -90,6 +90,22 @@ class AbstractMemoryTests:
             m = self._view(b)
             self.assertEqual(list(m), [m[i] for i in range(len(m))])
 
+    def test_count(self):
+        for tp in self._types:
+            b = tp(self._source)
+            m = self._view(b)
+            l = m.tolist()
+            for ch in list(m):
+                self.assertEqual(m.count(ch), l.count(ch))
+
+            b = tp((b'a' * 5) + (b'c' * 3))
+            m = self._view(b)  # may be sliced
+            l = m.tolist()
+            with self.subTest('count', buffer=b):
+                self.assertEqual(m.count(ord('a')), l.count(ord('a')))
+                self.assertEqual(m.count(ord('b')), l.count(ord('b')))
+                self.assertEqual(m.count(ord('c')), l.count(ord('c')))
+
     def test_setitem_readonly(self):
         if not self.ro_type:
             self.skipTest("no read-only type to test")
@@ -464,6 +480,18 @@ class BaseMemoryviewTests:
     def _check_contents(self, tp, obj, contents):
         self.assertEqual(obj, tp(contents))
 
+    def test_count(self):
+        super().test_count()
+        for tp in self._types:
+            b = tp((b'a' * 5) + (b'c' * 3))
+            m = self._view(b)  # should not be sliced
+            self.assertEqual(len(b), len(m))
+            with self.subTest('count', buffer=b):
+                self.assertEqual(m.count(ord('a')), 5)
+                self.assertEqual(m.count(ord('b')), 0)
+                self.assertEqual(m.count(ord('c')), 3)
+
+
 class BaseMemorySliceTests:
     source_bytes = b"XabcdefY"
 
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-14-12-34-51.gh-issue-125420.jABXoZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-14-12-34-51.gh-issue-125420.jABXoZ.rst
new file mode 100644 (file)
index 0000000..ef12080
--- /dev/null
@@ -0,0 +1,2 @@
+Add :meth:`memoryview.count` to :class:`memoryview` objects. Patch by
+Bénédikt Tran.
index 9b8a23317f0da02340269fb031c0e25630f587fc..a6cf1f431a15b021ae5542aa0dd34222ce85b98c 100644 (file)
@@ -419,6 +419,15 @@ exit:
     return return_value;
 }
 
+PyDoc_STRVAR(memoryview_count__doc__,
+"count($self, value, /)\n"
+"--\n"
+"\n"
+"Count the number of occurrences of a value.");
+
+#define MEMORYVIEW_COUNT_METHODDEF    \
+    {"count", (PyCFunction)memoryview_count, METH_O, memoryview_count__doc__},
+
 PyDoc_STRVAR(memoryview_index__doc__,
 "index($self, value, start=0, stop=sys.maxsize, /)\n"
 "--\n"
@@ -464,4 +473,4 @@ skip_optional:
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=2742d371dba7314f input=a9049054013a1b77]*/
+/*[clinic end generated code: output=132893ef5f67ad73 input=a9049054013a1b77]*/
index 345aa79c3281252fa200d2988f674385f2d70ebf..afe2dcb127adb4ad8c81bf570ff40a8c9f356893 100644 (file)
@@ -2748,6 +2748,55 @@ static PySequenceMethods memory_as_sequence = {
 };
 
 
+/****************************************************************************/
+/*                              Counting                                    */
+/****************************************************************************/
+
+/*[clinic input]
+memoryview.count
+
+    value: object
+    /
+
+Count the number of occurrences of a value.
+[clinic start generated code]*/
+
+static PyObject *
+memoryview_count(PyMemoryViewObject *self, PyObject *value)
+/*[clinic end generated code: output=e2c255a8d54eaa12 input=e3036ce1ed7d1823]*/
+{
+    PyObject *iter = PyObject_GetIter(_PyObject_CAST(self));
+    if (iter == NULL) {
+        return NULL;
+    }
+
+    Py_ssize_t count = 0;
+    PyObject *item = NULL;
+    while (PyIter_NextItem(iter, &item)) {
+        if (item == NULL) {
+            Py_DECREF(iter);
+            return NULL;
+        }
+        if (item == value) {
+            Py_DECREF(item);
+            count++;  // no overflow since count <= len(mv) <= PY_SSIZE_T_MAX
+            continue;
+        }
+        int contained = PyObject_RichCompareBool(item, value, Py_EQ);
+        Py_DECREF(item);
+        if (contained > 0) { // more likely than 'contained < 0'
+            count++;  // no overflow since count <= len(mv) <= PY_SSIZE_T_MAX
+        }
+        else if (contained < 0) {
+            Py_DECREF(iter);
+            return NULL;
+        }
+    }
+    Py_DECREF(iter);
+    return PyLong_FromSsize_t(count);
+}
+
+
 /**************************************************************************/
 /*                             Lookup                                     */
 /**************************************************************************/
@@ -3370,6 +3419,7 @@ static PyMethodDef memory_methods[] = {
     MEMORYVIEW_CAST_METHODDEF
     MEMORYVIEW_TOREADONLY_METHODDEF
     MEMORYVIEW__FROM_FLAGS_METHODDEF
+    MEMORYVIEW_COUNT_METHODDEF
     MEMORYVIEW_INDEX_METHODDEF
     {"__enter__",   memory_enter, METH_NOARGS, NULL},
     {"__exit__",    memory_exit, METH_VARARGS, memory_exit_doc},