Skip to content

Commit 9814cb9

Browse files
authored
Adding C++ backend for linear_data_structures.arrays.OneDimensionalArray (#450)
1 parent 61e0115 commit 9814cb9

File tree

22 files changed

+703
-31
lines changed

22 files changed

+703
-31
lines changed

.github/workflows/ci.yml

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
3737
- name: Build package
3838
run: |
39-
python -m pip install .
39+
python scripts/build/install.py
4040
4141
- name: Run tests
4242
run: |
@@ -89,7 +89,7 @@ jobs:
8989
9090
- name: Build package
9191
run: |
92-
python -m pip install .
92+
python scripts/build/install.py
9393
9494
- name: Run tests
9595
run: |
@@ -99,13 +99,13 @@ jobs:
9999
run: |
100100
sphinx-build -b html docs/source/ docs/build/html
101101
102-
test-macos-windows:
102+
test-macos:
103103
runs-on: ${{matrix.os}}
104104
timeout-minutes: 10
105105
strategy:
106106
fail-fast: false
107107
matrix:
108-
os: [macos-latest, windows-latest]
108+
os: [macos-latest]
109109
python-version:
110110
- "3.8"
111111
- "3.9"
@@ -130,7 +130,55 @@ jobs:
130130
131131
- name: Build package
132132
run: |
133-
python -m pip install .
133+
python scripts/build/install.py
134+
135+
- name: Run tests
136+
run: |
137+
python -m pytest --doctest-modules --cov=./ --cov-report=xml -s
138+
139+
- name: Build Documentation
140+
run: |
141+
sphinx-build -b html docs/source/ docs/build/html
142+
143+
test-windows:
144+
runs-on: ${{matrix.os}}
145+
timeout-minutes: 10
146+
strategy:
147+
fail-fast: false
148+
matrix:
149+
os: [windows-latest]
150+
python-version:
151+
- "3.8"
152+
153+
steps:
154+
- uses: actions/checkout@v2
155+
156+
- name: Set up Python ${{ matrix.python-version }}
157+
uses: actions/setup-python@v2
158+
with:
159+
python-version: ${{ matrix.python-version }}
160+
161+
- name: Setup conda
162+
uses: s-weigand/setup-conda@v1
163+
with:
164+
update-conda: true
165+
python-version: ${{ matrix.python-version }}
166+
conda-channels: anaconda, conda-forge
167+
- run: conda --version
168+
- run: which python
169+
170+
- name: Upgrade pip version
171+
run: |
172+
python -m pip install --upgrade pip
173+
174+
- name: Install requirements
175+
run: |
176+
pip install -r requirements.txt
177+
pip install -r docs/requirements.txt
178+
179+
- name: Build package
180+
run: |
181+
python scripts/build/install.py
134182
135183
- name: Run tests
136184
run: |

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ __pycache__/
6363
################
6464
.idea/
6565
build/
66+
!scripts/build
6667
dist/
6768
venv/
6869
*.gem

pydatastructs/linear_data_structures/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from . import (
44
arrays,
55
linked_lists,
6-
algorithms
6+
algorithms,
7+
_extensions
78
)
89

910
from .arrays import (

pydatastructs/linear_data_structures/_backend/__init__.py

Whitespace-only changes.

pydatastructs/linear_data_structures/_backend/cpp/__init__.py

Whitespace-only changes.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#ifndef LINEAR_DATA_STRUCTURES_ARRAY_HPP
2+
#define LINEAR_DATA_STRUCTURES_ARRAY_HPP
3+
4+
#define PY_SSIZE_T_CLEAN
5+
#include <Python.h>
6+
#include <structmember.h>
7+
8+
typedef struct {
9+
PyObject_HEAD
10+
} Array;
11+
12+
static void Array_dealloc(Array *self) {
13+
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
14+
}
15+
16+
static PyObject* Array___new__(PyTypeObject* type, PyObject *args,
17+
PyObject *kwds) {
18+
Array *self;
19+
self = reinterpret_cast<Array*>(type->tp_alloc(type, 0));
20+
return reinterpret_cast<PyObject*>(self);
21+
}
22+
23+
static PyObject* Array___str__(Array *self) {
24+
PyObject* self__data = PyObject_GetAttrString(reinterpret_cast<PyObject*>(self), "_data");
25+
if( !self__data ) {
26+
return NULL;
27+
}
28+
return PyObject_Str(self__data);
29+
}
30+
31+
static PyTypeObject ArrayType = {
32+
/* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "Array",
33+
/* tp_basicsize */ sizeof(Array),
34+
/* tp_itemsize */ 0,
35+
/* tp_dealloc */ (destructor) Array_dealloc,
36+
/* tp_print */ 0,
37+
/* tp_getattr */ 0,
38+
/* tp_setattr */ 0,
39+
/* tp_reserved */ 0,
40+
/* tp_repr */ 0,
41+
/* tp_as_number */ 0,
42+
/* tp_as_sequence */ 0,
43+
/* tp_as_mapping */ 0,
44+
/* tp_hash */ 0,
45+
/* tp_call */ 0,
46+
/* tp_str */ (reprfunc) Array___str__,
47+
/* tp_getattro */ 0,
48+
/* tp_setattro */ 0,
49+
/* tp_as_buffer */ 0,
50+
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
51+
/* tp_doc */ 0,
52+
/* tp_traverse */ 0,
53+
/* tp_clear */ 0,
54+
/* tp_richcompare */ 0,
55+
/* tp_weaklistoffset */ 0,
56+
/* tp_iter */ 0,
57+
/* tp_iternext */ 0,
58+
/* tp_methods */ 0,
59+
/* tp_members */ 0,
60+
/* tp_getset */ 0,
61+
/* tp_base */ &PyBaseObject_Type,
62+
/* tp_dict */ 0,
63+
/* tp_descr_get */ 0,
64+
/* tp_descr_set */ 0,
65+
/* tp_dictoffset */ 0,
66+
/* tp_init */ 0,
67+
/* tp_alloc */ 0,
68+
/* tp_new */ Array___new__,
69+
};
70+
71+
#endif
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#ifndef LINEAR_DATA_STRUCTURES_ONEDIMENSIONALARRAY_HPP
2+
#define LINEAR_DATA_STRUCTURES_ONEDIMENSIONALARRAY_HPP
3+
4+
#define PY_SSIZE_T_CLEAN
5+
#include <Python.h>
6+
#include <structmember.h>
7+
#include <cstdlib>
8+
#include "../../../../utils/_backend/cpp/utils.hpp"
9+
10+
typedef struct {
11+
PyObject_HEAD
12+
size_t _size;
13+
PyObject** _data;
14+
PyObject* _dtype;
15+
} OneDimensionalArray;
16+
17+
static void OneDimensionalArray_dealloc(OneDimensionalArray *self) {
18+
std::free(self->_data);
19+
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
20+
}
21+
22+
static PyObject* OneDimensionalArray___new__(PyTypeObject* type, PyObject *args,
23+
PyObject *kwds) {
24+
OneDimensionalArray *self;
25+
self = reinterpret_cast<OneDimensionalArray*>(type->tp_alloc(type, 0));
26+
size_t len_args = PyObject_Length(args);
27+
28+
PyObject *dtype = PyObject_GetItem(args, PyZero);
29+
if( dtype == Py_None ) {
30+
PyErr_SetString(PyExc_ValueError,
31+
"Data type is not defined.");
32+
return NULL;
33+
}
34+
self->_dtype = dtype;
35+
36+
if( len_args != 2 && len_args != 3 ) {
37+
PyErr_SetString(PyExc_ValueError,
38+
"Too few arguments to create a 1D array,"
39+
" pass either size of the array"
40+
" or list of elements or both.");
41+
return NULL;
42+
}
43+
44+
if( len_args == 3 ) {
45+
PyObject *args0 = PyObject_GetItem(args, PyOne);
46+
PyObject *args1 = PyObject_GetItem(args, PyTwo);
47+
size_t size;
48+
PyObject *data = NULL;
49+
if( (PyList_Check(args0) || PyTuple_Check(args0)) &&
50+
PyLong_Check(args1) ) {
51+
size = PyLong_AsUnsignedLong(args1);
52+
data = args0;
53+
} else if( (PyList_Check(args1) || PyTuple_Check(args1)) &&
54+
PyLong_Check(args0) ) {
55+
size = PyLong_AsUnsignedLong(args0);
56+
data = args1;
57+
} else {
58+
PyErr_SetString(PyExc_TypeError,
59+
"Expected type of size is int and "
60+
"expected type of data is list/tuple.");
61+
return NULL;
62+
}
63+
size_t len_data = PyObject_Length(data);
64+
if( size != len_data ) {
65+
PyErr_Format(PyExc_ValueError,
66+
"Conflict in the size, %d and length of data, %d",
67+
size, len_data);
68+
return NULL;
69+
}
70+
self->_size = size;
71+
self->_data = reinterpret_cast<PyObject**>(std::malloc(size * sizeof(PyObject*)));
72+
for( size_t i = 0; i < size; i++ ) {
73+
PyObject* value = PyObject_GetItem(data, PyLong_FromSize_t(i));
74+
if( raise_exception_if_dtype_mismatch(value, self->_dtype) ) {
75+
return NULL;
76+
}
77+
self->_data[i] = value;
78+
}
79+
} else if( len_args == 2 ) {
80+
PyObject *args0 = PyObject_GetItem(args, PyOne);
81+
if( PyLong_Check(args0) ) {
82+
self->_size = PyLong_AsSize_t(args0);
83+
PyObject* init = PyObject_GetItem(kwds, PyUnicode_FromString("init"));
84+
if( init == nullptr ) {
85+
PyErr_Clear();
86+
init = Py_None;
87+
} else if( raise_exception_if_dtype_mismatch(init, self->_dtype) ) {
88+
return NULL;
89+
}
90+
self->_data = reinterpret_cast<PyObject**>(std::malloc(self->_size * sizeof(PyObject*)));
91+
for( size_t i = 0; i < self->_size; i++ ) {
92+
self->_data[i] = init;
93+
}
94+
} else if( (PyList_Check(args0) || PyTuple_Check(args0)) ) {
95+
self->_size = PyObject_Length(args0);
96+
self->_data = reinterpret_cast<PyObject**>(std::malloc(self->_size * sizeof(PyObject*)));
97+
for( size_t i = 0; i < self->_size; i++ ) {
98+
PyObject* value = PyObject_GetItem(args0, PyLong_FromSize_t(i));
99+
if( raise_exception_if_dtype_mismatch(value, self->_dtype) ) {
100+
return NULL;
101+
}
102+
self->_data[i] = value;
103+
}
104+
} else {
105+
PyErr_SetString(PyExc_TypeError,
106+
"Expected type of size is int and "
107+
"expected type of data is list/tuple.");
108+
return NULL;
109+
}
110+
}
111+
112+
return reinterpret_cast<PyObject*>(self);
113+
}
114+
115+
static PyObject* OneDimensionalArray___getitem__(OneDimensionalArray *self,
116+
PyObject* arg) {
117+
size_t idx = PyLong_AsUnsignedLong(arg);
118+
if( idx >= self->_size ) {
119+
PyErr_Format(PyExc_IndexError,
120+
"Index, %d, out of range, [%d, %d)",
121+
idx, 0, self->_size);
122+
return NULL;
123+
}
124+
Py_INCREF(self->_data[idx]);
125+
return self->_data[idx];
126+
}
127+
128+
static int OneDimensionalArray___setitem__(OneDimensionalArray *self,
129+
PyObject* arg, PyObject* value) {
130+
size_t idx = PyLong_AsUnsignedLong(arg);
131+
if( value == Py_None ) {
132+
self->_data[idx] = value;
133+
} else if( !set_exception_if_dtype_mismatch(value, self->_dtype) ) {
134+
self->_data[idx] = value;
135+
}
136+
return 0;
137+
}
138+
139+
static PyObject* OneDimensionalArray_fill(OneDimensionalArray *self, PyObject *args) {
140+
PyObject* value = PyObject_GetItem(args, PyZero);
141+
if( raise_exception_if_dtype_mismatch(value, self->_dtype) ) {
142+
return NULL;
143+
}
144+
145+
for( size_t i = 0; i < self->_size; i++ ) {
146+
self->_data[i] = value;
147+
}
148+
149+
Py_RETURN_NONE;
150+
}
151+
152+
static Py_ssize_t OneDimensionalArray___len__(OneDimensionalArray *self) {
153+
return self->_size;
154+
}
155+
156+
static PyObject* OneDimensionalArray___str__(OneDimensionalArray *self) {
157+
PyObject** self__data = self->_data;
158+
if( !self__data ) {
159+
return NULL;
160+
}
161+
return __str__(self__data, self->_size);
162+
}
163+
164+
static PyMappingMethods OneDimensionalArray_PyMappingMethods = {
165+
(lenfunc) OneDimensionalArray___len__,
166+
(binaryfunc) OneDimensionalArray___getitem__,
167+
(objobjargproc) OneDimensionalArray___setitem__,
168+
};
169+
170+
static struct PyMethodDef OneDimensionalArray_PyMethodDef[] = {
171+
{"fill", (PyCFunction) OneDimensionalArray_fill, METH_VARARGS, NULL},
172+
{NULL}
173+
};
174+
175+
static PyTypeObject OneDimensionalArrayType = {
176+
/* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "OneDimensionalArray",
177+
/* tp_basicsize */ sizeof(OneDimensionalArray),
178+
/* tp_itemsize */ 0,
179+
/* tp_dealloc */ (destructor) OneDimensionalArray_dealloc,
180+
/* tp_print */ 0,
181+
/* tp_getattr */ 0,
182+
/* tp_setattr */ 0,
183+
/* tp_reserved */ 0,
184+
/* tp_repr */ 0,
185+
/* tp_as_number */ 0,
186+
/* tp_as_sequence */ 0,
187+
/* tp_as_mapping */ &OneDimensionalArray_PyMappingMethods,
188+
/* tp_hash */ 0,
189+
/* tp_call */ 0,
190+
/* tp_str */ (reprfunc) OneDimensionalArray___str__,
191+
/* tp_getattro */ 0,
192+
/* tp_setattro */ 0,
193+
/* tp_as_buffer */ 0,
194+
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
195+
/* tp_doc */ 0,
196+
/* tp_traverse */ 0,
197+
/* tp_clear */ 0,
198+
/* tp_richcompare */ 0,
199+
/* tp_weaklistoffset */ 0,
200+
/* tp_iter */ 0,
201+
/* tp_iternext */ 0,
202+
/* tp_methods */ OneDimensionalArray_PyMethodDef,
203+
/* tp_members */ 0,
204+
/* tp_getset */ 0,
205+
/* tp_base */ &ArrayType,
206+
/* tp_dict */ 0,
207+
/* tp_descr_get */ 0,
208+
/* tp_descr_set */ 0,
209+
/* tp_dictoffset */ 0,
210+
/* tp_init */ 0,
211+
/* tp_alloc */ 0,
212+
/* tp_new */ OneDimensionalArray___new__,
213+
};
214+
215+
#endif

0 commit comments

Comments
 (0)