diff --git a/.idea/PyFastUtil.iml b/.idea/PyFastUtil.iml
index e897a98..4160845 100644
--- a/.idea/PyFastUtil.iml
+++ b/.idea/PyFastUtil.iml
@@ -319,6 +319,10 @@
+
+
+
+
diff --git a/pyfastutil/ints.pyi b/pyfastutil/ints.pyi
index d66480c..1cf2ebe 100644
--- a/pyfastutil/ints.pyi
+++ b/pyfastutil/ints.pyi
@@ -1,4 +1,4 @@
-from typing import overload, Iterable, SupportsIndex, final
+from typing import overload, Iterable, SupportsIndex, final, Iterator, _T_co
@final
@@ -133,3 +133,46 @@ class IntArrayList(list[int]):
[1, 2]
"""
pass
+
+
+@final
+class IntArrayListIter(Iterator[int]):
+ """
+ Iterator for IntArrayList.
+
+ This class provides an iterator over an `IntArrayList`, allowing you to iterate over the elements
+ of the list one by one.
+
+ Note:
+ This class cannot be directly instantiated by users. It is designed to be used internally by
+ `IntArrayList` and can only be obtained by calling the `__iter__` method on an `IntArrayList` object.
+
+ Example:
+ >>> int_list = IntArrayList([1, 2, 3])
+ >>> iter_obj = iter(int_list) # This returns an IntArrayListIter instance
+ >>> next(iter_obj)
+ 1
+ >>> next(iter_obj)
+ 2
+ >>> next(iter_obj)
+ 3
+ >>> next(iter_obj) # Raises StopIteration
+
+ Raises:
+ TypeError: If attempted to be instantiated directly.
+ """
+
+ def __next__(self) -> int:
+ """
+ Return the next element in the iteration.
+
+ This method retrieves the next element from the `IntArrayList` that this iterator is associated with.
+ If all elements have been iterated over, it raises a `StopIteration` exception.
+
+ Returns:
+ int: The next integer in the `IntArrayList`.
+
+ Raises:
+ StopIteration: If there are no more elements to iterate over.
+ """
+ pass
diff --git a/pyfastutil/src/PyFastUtil.cpp b/pyfastutil/src/PyFastUtil.cpp
index cd5c8b5..a28b299 100644
--- a/pyfastutil/src/PyFastUtil.cpp
+++ b/pyfastutil/src/PyFastUtil.cpp
@@ -5,6 +5,7 @@
#include "PyFastUtil.h"
#include "utils/simd/BitonicSort.h"
#include "ints/IntArrayList.h"
+#include "ints/IntArrayListIter.h"
#include "unsafe/Unsafe.h"
static struct PyModuleDef pyfastutilModule = {
@@ -26,6 +27,8 @@ PyMODINIT_FUNC PyInit___pyfastutil() {
return nullptr;
PyModule_AddObject(parent, "IntArrayList", PyInit_IntArrayList());
+ PyModule_AddObject(parent, "IntArrayListIter", PyInit_IntArrayListIter());
+
PyModule_AddObject(parent, "Unsafe", PyInit_Unsafe());
return parent;
diff --git a/pyfastutil/src/ints/IntArrayList.cpp b/pyfastutil/src/ints/IntArrayList.cpp
index 8a891a9..705500f 100644
--- a/pyfastutil/src/ints/IntArrayList.cpp
+++ b/pyfastutil/src/ints/IntArrayList.cpp
@@ -6,18 +6,14 @@
#include
#include
#include
+#include
#include "utils/PythonUtils.h"
-#include "utils/ParseUtils.h"
#include "utils/TimSort.h"
#include "utils/simd/BitonicSort.h"
#include "utils/memory/AlignedAllocator.h"
+#include "ints/IntArrayListIter.h"
extern "C" {
-typedef struct IntArrayList {
- PyObject_HEAD;
- // we use 64 bytes memory aligned to support faster SIMD, suggestion by ChatGPT.
- std::vector> vector;
-} IntArrayList;
static PyTypeObject IntArrayListType = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
@@ -454,35 +450,54 @@ static PyObject *IntArrayList_sort(PyObject *pySelf, PyObject *args, PyObject *k
Py_RETURN_NONE;
}
+Py_ssize_t IntArrayList_len(PyObject *pySelf) {
+ auto *self = reinterpret_cast(pySelf);
+
+ return static_cast(self->vector.size());
+}
+
+
+PyObject *IntArrayList_iter(PyObject *pySelf) {
+ auto *self = reinterpret_cast(pySelf);
+
+ return reinterpret_cast(IntArrayListIter_create(self));
+}
+
static PyMethodDef IntArrayList_methods[] = {
{"from_range", (PyCFunction) IntArrayList_from_range, METH_VARARGS | METH_STATIC},
- {"resize", (PyCFunction) IntArrayList_resize, METH_O},
- {"copy", (PyCFunction) IntArrayList_copy, METH_NOARGS},
- {"append", (PyCFunction) IntArrayList_append, METH_O},
- {"extend", (PyCFunction) IntArrayList_extend, METH_O},
- {"pop", (PyCFunction) IntArrayList_pop, METH_VARARGS},
- {"index", (PyCFunction) IntArrayList_index, METH_VARARGS},
- {"count", (PyCFunction) IntArrayList_count, METH_O},
- {"insert", (PyCFunction) IntArrayList_insert, METH_VARARGS},
- {"remove", (PyCFunction) IntArrayList_remove, METH_O},
- {"sort", (PyCFunction) IntArrayList_sort, METH_VARARGS | METH_KEYWORDS},
+ {"resize", (PyCFunction) IntArrayList_resize, METH_O},
+ {"copy", (PyCFunction) IntArrayList_copy, METH_NOARGS},
+ {"append", (PyCFunction) IntArrayList_append, METH_O},
+ {"extend", (PyCFunction) IntArrayList_extend, METH_O},
+ {"pop", (PyCFunction) IntArrayList_pop, METH_VARARGS},
+ {"index", (PyCFunction) IntArrayList_index, METH_VARARGS},
+ {"count", (PyCFunction) IntArrayList_count, METH_O},
+ {"insert", (PyCFunction) IntArrayList_insert, METH_VARARGS},
+ {"remove", (PyCFunction) IntArrayList_remove, METH_O},
+ {"sort", (PyCFunction) IntArrayList_sort, METH_VARARGS | METH_KEYWORDS},
{nullptr}
};
static struct PyModuleDef IntArrayList_module = {
PyModuleDef_HEAD_INIT,
"__pyfastutil.IntArrayList",
- "A IntArrayList_module that creates an IntArrayList",
+ "An IntArrayList_module that creates an IntArrayList",
-1,
nullptr, nullptr, nullptr, nullptr, nullptr
};
+static PySequenceMethods IntArrayList_asSequence = {
+ .sq_length = (lenfunc) IntArrayList_len
+};
+
void initializeIntArrayListType(PyTypeObject &type) {
type.tp_name = "IntArrayList";
type.tp_basicsize = sizeof(IntArrayList);
type.tp_itemsize = 0;
type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ type.tp_as_sequence = &IntArrayList_asSequence;
+ type.tp_iter = IntArrayList_iter;
type.tp_methods = IntArrayList_methods;
type.tp_init = (initproc) IntArrayList_init;
type.tp_new = PyType_GenericNew;
diff --git a/pyfastutil/src/ints/IntArrayList.h b/pyfastutil/src/ints/IntArrayList.h
index 8d61de2..e98989b 100644
--- a/pyfastutil/src/ints/IntArrayList.h
+++ b/pyfastutil/src/ints/IntArrayList.h
@@ -6,6 +6,16 @@
#define PYFASTUTIL_INTARRAYLIST_H
#include "utils/PythonPCH.h"
+#include "utils/memory/AlignedAllocator.h"
+#include
+
+extern "C" {
+typedef struct IntArrayList {
+ PyObject_HEAD;
+ // we use 64 bytes memory aligned to support faster SIMD, suggestion by ChatGPT.
+ std::vector> vector;
+} IntArrayList;
+}
PyMODINIT_FUNC PyInit_IntArrayList();
diff --git a/pyfastutil/src/ints/IntArrayListIter.cpp b/pyfastutil/src/ints/IntArrayListIter.cpp
new file mode 100644
index 0000000..624e697
--- /dev/null
+++ b/pyfastutil/src/ints/IntArrayListIter.cpp
@@ -0,0 +1,91 @@
+//
+// Created by xia__mc on 2024/11/13.
+//
+
+#include "IntArrayListIter.h"
+#include "utils/PythonUtils.h"
+
+extern "C" {
+
+static PyTypeObject IntArrayListIterType = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+};
+
+IntArrayListIter *IntArrayListIter_create(IntArrayList *list) {
+ auto *instance = Py_CreateObjNoInit(IntArrayListIterType);
+
+ Py_INCREF(list);
+ instance->container = list;
+ instance->index = 0;
+
+ return instance;
+}
+
+static void IntArrayListIter_dealloc(IntArrayListIter *self) {
+ SAFE_DECREF(self->container);
+ Py_TYPE(self)->tp_free((PyObject *) self);
+}
+
+static PyObject *IntArrayListIter_next(PyObject *pySelf) {
+ auto *self = reinterpret_cast(pySelf);
+
+ if (self->index >= self->container->vector.size()) {
+ PyErr_SetNone(PyExc_StopIteration);
+ return nullptr;
+ }
+
+ auto element = self->container->vector[self->index];
+ self->index++;
+ return PyLong_FromLong(static_cast(element));
+}
+
+static PyObject *IntArrayListIter_iter(PyObject *pySelf) {
+ return pySelf;
+}
+
+static PyMethodDef IntArrayListIter_methods[] = {
+ {nullptr}
+};
+
+static struct PyModuleDef IntArrayListIter_module = {
+ PyModuleDef_HEAD_INIT,
+ "__pyfastutil.IntArrayListIter",
+ "An IntArrayListIter_module that creates an IntArrayList",
+ -1,
+ nullptr, nullptr, nullptr, nullptr, nullptr
+};
+
+void initializeIntArrayListIterType(PyTypeObject &type) {
+ type.tp_name = "IntArrayListIter";
+ type.tp_basicsize = sizeof(IntArrayListIter);
+ type.tp_itemsize = 0;
+ type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ type.tp_iter = IntArrayListIter_iter;
+ type.tp_iternext = IntArrayListIter_next;
+ type.tp_methods = IntArrayListIter_methods;
+ type.tp_dealloc = (destructor) IntArrayListIter_dealloc;
+ type.tp_alloc = PyType_GenericAlloc;
+ type.tp_free = PyObject_Del;
+}
+
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
+PyMODINIT_FUNC PyInit_IntArrayListIter() {
+ initializeIntArrayListIterType(IntArrayListIterType);
+
+ PyObject *object = PyModule_Create(&IntArrayListIter_module);
+ if (object == nullptr)
+ return nullptr;
+
+ Py_INCREF(&IntArrayListIterType);
+ if (PyModule_AddObject(object, "IntArrayList", (PyObject *) &IntArrayListIterType) < 0) {
+ Py_DECREF(&IntArrayListIterType);
+ Py_DECREF(object);
+ return nullptr;
+ }
+
+ return object;
+}
+#pragma clang diagnostic pop
+
+}
diff --git a/pyfastutil/src/ints/IntArrayListIter.h b/pyfastutil/src/ints/IntArrayListIter.h
new file mode 100644
index 0000000..dbfc6e3
--- /dev/null
+++ b/pyfastutil/src/ints/IntArrayListIter.h
@@ -0,0 +1,24 @@
+//
+// Created by xia__mc on 2024/11/13.
+//
+
+#ifndef PYFASTUTIL_INTARRAYLISTITER_H
+#define PYFASTUTIL_INTARRAYLISTITER_H
+
+#include "utils/PythonPCH.h"
+#include "IntArrayList.h"
+
+extern "C" {
+typedef struct IntArrayListIter {
+ PyObject_HEAD;
+ IntArrayList *container;
+ size_t index;
+} IntArrayListIter;
+
+IntArrayListIter *IntArrayListIter_create(IntArrayList *list);
+
+}
+
+PyMODINIT_FUNC PyInit_IntArrayListIter();
+
+#endif //PYFASTUTIL_INTARRAYLISTITER_H
diff --git a/pyfastutil/src/unsafe/Unsafe.cpp b/pyfastutil/src/unsafe/Unsafe.cpp
index bcbcbd6..f0b00e0 100644
--- a/pyfastutil/src/unsafe/Unsafe.cpp
+++ b/pyfastutil/src/unsafe/Unsafe.cpp
@@ -6,9 +6,6 @@
#include "mutex"
extern "C" {
-typedef struct Unsafe {
- PyObject_HEAD;
-} Unsafe;
static PyTypeObject UnsafeType = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
@@ -159,6 +156,10 @@ static PyObject *Unsafe_decref([[maybe_unused]] PyObject *self, PyObject *pyObje
Py_RETURN_NONE;
}
+static PyObject *Unsafe_refcnt([[maybe_unused]] PyObject *self, PyObject *pyObject) {
+ return PyLong_FromSsize_t(Py_REFCNT(pyObject));
+}
+
static PyObject *Unsafe_fputs([[maybe_unused]] PyObject *pySelf, PyObject *pyStr) {
if (!PyUnicode_Check(pyStr)) {
PyErr_SetString(PyExc_TypeError, "Argument must be a string.");
@@ -217,6 +218,7 @@ static PyMethodDef Unsafe_methods[] = {
{"memcpy", (PyCFunction) Unsafe_memcpy, METH_VARARGS, nullptr},
{"incref", (PyCFunction) Unsafe_incref, METH_O, nullptr},
{"decref", (PyCFunction) Unsafe_decref, METH_O, nullptr},
+ {"refcnt", (PyCFunction) Unsafe_refcnt, METH_O, nullptr},
{"fputs", (PyCFunction) Unsafe_fputs, METH_O, nullptr},
{"fflush", (PyCFunction) Unsafe_fflush, METH_NOARGS, nullptr},
{"fgets", (PyCFunction) Unsafe_fgets, METH_O, nullptr},
diff --git a/pyfastutil/src/unsafe/Unsafe.h b/pyfastutil/src/unsafe/Unsafe.h
index f66a473..8de0b7e 100644
--- a/pyfastutil/src/unsafe/Unsafe.h
+++ b/pyfastutil/src/unsafe/Unsafe.h
@@ -7,6 +7,12 @@
#include "utils/PythonPCH.h"
+extern "C" {
+typedef struct Unsafe {
+ PyObject_HEAD;
+} Unsafe;
+}
+
PyMODINIT_FUNC PyInit_Unsafe();
#endif //PYFASTUTIL_UNSAFE_H
diff --git a/pyfastutil/src/utils/ParseUtils.h b/pyfastutil/src/utils/ParseUtils.h
deleted file mode 100644
index 094ebc8..0000000
--- a/pyfastutil/src/utils/ParseUtils.h
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Created by xia__mc on 2024/11/10.
-//
-
-#ifndef PYFASTUTIL_PARSEUTILS_H
-#define PYFASTUTIL_PARSEUTILS_H
-
-#include "PythonPCH.h"
-#include "Compat.h"
-
-/**
- * Try to parse args like built-in range function
- * If not successful, function will raise python exception.
- * @return if successful
- */
-static __forceinline bool PyParse_EvalRange(PyObject *&args, Py_ssize_t &start, Py_ssize_t &stop, Py_ssize_t &step) {
- Py_ssize_t arg1 = PY_SSIZE_T_MAX;
- Py_ssize_t arg2 = PY_SSIZE_T_MAX;
- Py_ssize_t arg3 = PY_SSIZE_T_MAX;
-
- if (!PyArg_ParseTuple(args, "n|nn", &arg1, &arg2, &arg3)) {
- return false;
- }
-
- if (arg2 == PY_SSIZE_T_MAX) {
- start = 0;
- stop = arg1;
- step = 1;
- } else if (arg3 == PY_SSIZE_T_MAX) {
- start = arg1;
- stop = arg2;
- step = 1;
- } else if (arg3 != 0) {
- start = arg1;
- stop = arg2;
- step = arg3;
- } else {
- PyErr_SetString(PyExc_ValueError, "Arg 3 must not be zero.");
- return false;
- }
-
- return true;
-}
-
-#endif //PYFASTUTIL_PARSEUTILS_H
diff --git a/pyfastutil/src/utils/PythonPCH.h b/pyfastutil/src/utils/PythonPCH.h
index 6bcfdd6..40612b7 100644
--- a/pyfastutil/src/utils/PythonPCH.h
+++ b/pyfastutil/src/utils/PythonPCH.h
@@ -11,4 +11,6 @@
#pragma warning(pop)
+#include "Compat.h"
+
#endif //PYFASTUTIL_PYTHONPCH_H
diff --git a/pyfastutil/src/utils/PythonUtils.h b/pyfastutil/src/utils/PythonUtils.h
index 03a4096..a0a2709 100644
--- a/pyfastutil/src/utils/PythonUtils.h
+++ b/pyfastutil/src/utils/PythonUtils.h
@@ -8,7 +8,8 @@
#include "PythonPCH.h"
#include "Compat.h"
-static __forceinline void SAFE_DECREF(PyObject *&object) {
+template
+static __forceinline void SAFE_DECREF(T *&object) {
if (object == nullptr)
return;
Py_DECREF(object);
@@ -25,4 +26,38 @@ static __forceinline T *Py_CreateObjNoInit(PyTypeObject &typeObj) {
return reinterpret_cast(_PyObject_New(&typeObj));
}
+/**
+ * Try to parse args like built-in range function
+ * If not successful, function will raise python exception.
+ * @return if successful
+ */
+static __forceinline bool PyParse_EvalRange(PyObject *&args, Py_ssize_t &start, Py_ssize_t &stop, Py_ssize_t &step) {
+ Py_ssize_t arg1 = PY_SSIZE_T_MAX;
+ Py_ssize_t arg2 = PY_SSIZE_T_MAX;
+ Py_ssize_t arg3 = PY_SSIZE_T_MAX;
+
+ if (!PyArg_ParseTuple(args, "n|nn", &arg1, &arg2, &arg3)) {
+ return false;
+ }
+
+ if (arg2 == PY_SSIZE_T_MAX) {
+ start = 0;
+ stop = arg1;
+ step = 1;
+ } else if (arg3 == PY_SSIZE_T_MAX) {
+ start = arg1;
+ stop = arg2;
+ step = 1;
+ } else if (arg3 != 0) {
+ start = arg1;
+ stop = arg2;
+ step = arg3;
+ } else {
+ PyErr_SetString(PyExc_ValueError, "Arg 3 must not be zero.");
+ return false;
+ }
+
+ return true;
+}
+
#endif //PYFASTUTIL_PYTHONUTILS_H
diff --git a/pyfastutil/src/utils/TimSort.h b/pyfastutil/src/utils/TimSort.h
index d02d4a3..6d5a7ba 100644
--- a/pyfastutil/src/utils/TimSort.h
+++ b/pyfastutil/src/utils/TimSort.h
@@ -710,7 +710,7 @@ namespace gfx {
>
requires std::sortable, Compare, Projection>
[[maybe_unused]] auto timmerge(Range &&range, std::ranges::iterator_t middle,
- Compare comp = {}, Projection proj = {})
+ Compare comp = {}, Projection proj = {})
-> std::ranges::borrowed_iterator_t {
return gfx::timmerge(std::begin(range), middle, std::end(range), comp, proj);
}
diff --git a/pyfastutil/src/utils/memory/AlignedAllocator.h b/pyfastutil/src/utils/memory/AlignedAllocator.h
index 9b0f38d..d203d2f 100644
--- a/pyfastutil/src/utils/memory/AlignedAllocator.h
+++ b/pyfastutil/src/utils/memory/AlignedAllocator.h
@@ -6,6 +6,7 @@
#define PYFASTUTIL_ALIGNEDALLOCATOR_H
#include
+#include "stdexcept"
#include "Compat.h"
template
diff --git a/pyfastutil/src/utils/memory/PreFetch.h b/pyfastutil/src/utils/memory/PreFetch.h
index 2f3bfdf..8d3eb84 100644
--- a/pyfastutil/src/utils/memory/PreFetch.h
+++ b/pyfastutil/src/utils/memory/PreFetch.h
@@ -13,17 +13,17 @@
#elif defined(__aarch64__) || defined(__arm__)
// ARM platform (32-bit or 64-bit)
// Using __builtin_prefetch for ARM platforms
- #define _MM_HINT_T0 0 // Prefetch to L1
- #define _MM_HINT_T1 0 // Prefetch to L2 (no direct equivalent, use L1)
- #define _MM_HINT_T2 0 // Prefetch to L3 (no direct equivalent, use L1)
- #define _MM_HINT_NTA 0 // Non-temporal prefetch (no direct equivalent, fallback to L1)
+#define _MM_HINT_T0 0 // Prefetch to L1
+#define _MM_HINT_T1 0 // Prefetch to L2 (no direct equivalent, use L1)
+#define _MM_HINT_T2 0 // Prefetch to L3 (no direct equivalent, use L1)
+#define _MM_HINT_NTA 0 // Non-temporal prefetch (no direct equivalent, fallback to L1)
// Define _mm_prefetch to use __builtin_prefetch for ARM
- #define _mm_prefetch(p, hint) __builtin_prefetch(p, 0, hint)
+#define _mm_prefetch(p, hint) __builtin_prefetch(p, 0, hint)
#else
// Other platforms: No prefetch available
- #define _mm_prefetch(p, hint) ((void)0) // No-op for unsupported platforms
+#define _mm_prefetch(p, hint) ((void)0) // No-op for unsupported platforms
#endif
__forceinline void prefetchL1(const void *pointer) {
diff --git a/pyfastutil/unsafe.pyi b/pyfastutil/unsafe.pyi
index 49cf1e9..d3f9122 100644
--- a/pyfastutil/unsafe.pyi
+++ b/pyfastutil/unsafe.pyi
@@ -169,6 +169,14 @@ class Unsafe:
"""
pass
+ def refcnt(self, __object: object) -> int:
+ """
+ Get the reference count of a Python object.
+
+ :param __object: The Python object.
+ """
+ pass
+
def fputs(self, __str: str) -> None:
"""
Writes a string to a low-level output stream.
diff --git a/setup.py b/setup.py
index db033ca..4e6dfad 100644
--- a/setup.py
+++ b/setup.py
@@ -24,7 +24,7 @@
"-fno-tree-vectorize"
# "-Wno-error=unguarded-availability-new" # already handle in Compat.h, and this option doesn't exist on gcc
]
- EXTRA_LINK_ARG = ["-shared"]
+ EXTRA_LINK_ARG = ["-bundle"]
if __name__ == "__main__":
if os.path.exists("./build"):
diff --git a/tests/test_int_array_list.py b/tests/test_int_array_list.py
index 5373ad6..809b1ee 100644
--- a/tests/test_int_array_list.py
+++ b/tests/test_int_array_list.py
@@ -5,7 +5,18 @@
class MyTestCase(unittest.TestCase):
def test_index(self):
lst = IntArrayList([1, 2, 3])
- self.assertEqual(lst.index(3), 2) # add assertion here
+ self.assertEqual(lst.index(1), 0)
+
+ def test_len(self):
+ lst = IntArrayList([1, 2, 3])
+ self.assertEqual(len(lst), 3)
+
+ def test_iter(self):
+ lst = [1, 2, 3]
+ newLst = []
+ for i in IntArrayList(lst):
+ newLst.append(i)
+ self.assertEqual(lst, newLst)
if __name__ == '__main__':