Skip to content

Commit

Permalink
Adding support for stack.ArrayStack in C++ backend (#512)
Browse files Browse the repository at this point in the history
Co-authored-by: Gagandeep Singh <[email protected]>
  • Loading branch information
nubol23 and czgdp1807 authored Aug 10, 2022
1 parent 3abc026 commit 8f419fd
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 11 deletions.
3 changes: 2 additions & 1 deletion pydatastructs/miscellaneous_data_structures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
binomial_trees,
queue,
disjoint_set,
sparse_table
sparse_table,
_extensions,
)

from .binomial_trees import (
Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#ifndef MISCELLANEOUS_DATA_STRUCTURES_ARRAYSTACK_HPP
#define MISCELLANEOUS_DATA_STRUCTURES_ARRAYSTACK_HPP

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <cstdlib>
#include <iostream>
#include <structmember.h>
#include "../../../../linear_data_structures/_backend/cpp/arrays/DynamicOneDimensionalArray.hpp"

typedef struct {
PyObject_HEAD
DynamicOneDimensionalArray* _items;
} ArrayStack;

static void ArrayStack_dealloc(ArrayStack *self) {
DynamicOneDimensionalArray_dealloc(self->_items);
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
}

static PyObject* ArrayStack__new__(PyTypeObject *type, PyObject *args, PyObject *kwds) {
ArrayStack *self = reinterpret_cast<ArrayStack*>(type->tp_alloc(type, 0));

static char *kwlist[] = {"items", "dtype", NULL};
PyObject *initial_values = Py_None, *dtype = Py_None;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &initial_values, &dtype)) {
PyErr_SetString(PyExc_ValueError, "Error creating ArrayStack");
return NULL;
}

if (initial_values != Py_None && PyType_Check(initial_values)) {
PyErr_SetString(PyExc_TypeError, "`items` must be an instance of list or tuple, received a type instead\n"
"Did you mean to instantiate an ArrayStack with only the data type? "
"if so, send the type parameter as a named argument "
"for example: dtype=int");
return NULL;
}

PyObject* items = NULL;
PyObject* doda_kwds = Py_BuildValue("{}");
if (initial_values == Py_None) {
// If the only argument is the dtype, redefine the args as a tuple (dtype, 0)
// where 0 is the initial array size
PyObject* extended_args = PyTuple_Pack(2, dtype, PyLong_FromLong(0));

items = DynamicOneDimensionalArray___new__(&DynamicOneDimensionalArrayType, extended_args, doda_kwds);
} else {
// If the user provides dtype and initial values list, let the array initializer handle the checks.
PyObject* doda_args = PyTuple_Pack(2, dtype, initial_values);
items = DynamicOneDimensionalArray___new__(&DynamicOneDimensionalArrayType, doda_args, doda_kwds);
}

if (!items) {
return NULL;
}

DynamicOneDimensionalArray* tmp = self->_items;
self->_items = reinterpret_cast<DynamicOneDimensionalArray*>(items);

return reinterpret_cast<PyObject*>(self);
}

static PyObject* ArrayStack_is_empty(ArrayStack *self) {
bool is_empty = self->_items->_last_pos_filled == -1;

if (is_empty) {
Py_RETURN_TRUE;
}

Py_RETURN_FALSE;
}

static PyObject* ArrayStack_push(ArrayStack *self, PyObject* args) {
size_t len_args = PyObject_Length(args);
if (len_args != 1) {
PyErr_SetString(PyExc_ValueError, "Expected one argument");
return NULL;
}

if (PyObject_IsTrue(ArrayStack_is_empty(self))) {
self->_items->_one_dimensional_array->_dtype = reinterpret_cast<PyObject*>(
Py_TYPE(PyObject_GetItem(args, PyZero))
);
}

DynamicOneDimensionalArray_append(self->_items, args);

Py_RETURN_NONE;
}

static PyObject* ArrayStack_pop(ArrayStack *self) {
if (PyObject_IsTrue(ArrayStack_is_empty(self))) {
PyErr_SetString(PyExc_IndexError, "Stack is empty");
return NULL;
}

PyObject *top_element = DynamicOneDimensionalArray___getitem__(
self->_items, PyLong_FromLong(self->_items->_last_pos_filled)
);

PyObject* last_pos_arg = PyTuple_Pack(1, PyLong_FromLong(self->_items->_last_pos_filled));
DynamicOneDimensionalArray_delete(self->_items, last_pos_arg);
return top_element;
}

static PyObject* ArrayStack_peek(ArrayStack *self, void *closure) {
return DynamicOneDimensionalArray___getitem__(
self->_items, PyLong_FromLong(self->_items->_last_pos_filled)
);
}

static Py_ssize_t ArrayStack__len__(ArrayStack *self) {
return self->_items->_num;
}

static PyObject* ArrayStack__str__(ArrayStack* self){
return DynamicOneDimensionalArray___str__(self->_items);
}

static struct PyMethodDef ArrayStack_PyMethodDef[] = {
{"push", (PyCFunction) ArrayStack_push, METH_VARARGS, NULL},
{"pop", (PyCFunction) ArrayStack_pop, METH_VARARGS, NULL},
{NULL}
};

static PyMappingMethods ArrayStack_PyMappingMethods = {
(lenfunc) ArrayStack__len__,
};

static PyGetSetDef ArrayStack_GetterSetters[] = {
{"peek", (getter) ArrayStack_peek, NULL, "peek top value", NULL},
{"is_empty", (getter) ArrayStack_is_empty, NULL, "check if the stack is empty", NULL},
{NULL} /* Sentinel */
};

static PyTypeObject ArrayStackType = {
/* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "ArrayStack",
/* tp_basicsize */ sizeof(ArrayStack),
/* tp_itemsize */ 0,
/* tp_dealloc */ (destructor) ArrayStack_dealloc,
/* tp_print */ 0,
/* tp_getattr */ 0,
/* tp_setattr */ 0,
/* tp_reserved */ 0,
/* tp_repr */ 0,
/* tp_as_number */ 0,
/* tp_as_sequence */ 0,
/* tp_as_mapping */ &ArrayStack_PyMappingMethods,
/* tp_hash */ 0,
/* tp_call */ 0,
/* tp_str */ (reprfunc) ArrayStack__str__,
/* tp_getattro */ 0,
/* tp_setattro */ 0,
/* tp_as_buffer */ 0,
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
/* tp_doc */ 0,
/* tp_traverse */ 0,
/* tp_clear */ 0,
/* tp_richcompare */ 0,
/* tp_weaklistoffset */ 0,
/* tp_iter */ 0,
/* tp_iternext */ 0,
/* tp_methods */ ArrayStack_PyMethodDef,
/* tp_members */ 0,
/* tp_getset */ ArrayStack_GetterSetters,
/* tp_base */ 0,
/* tp_dict */ 0,
/* tp_descr_get */ 0,
/* tp_descr_set */ 0,
/* tp_dictoffset */ 0,
/* tp_init */ 0,
/* tp_alloc */ 0,
/* tp_new */ ArrayStack__new__,
};


#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <Python.h>
#include "ArrayStack.hpp"

static struct PyModuleDef stack_struct = {
PyModuleDef_HEAD_INIT,
"_stack",
0,
-1,
NULL,
};

PyMODINIT_FUNC PyInit__stack(void) {
Py_Initialize();
PyObject *stack = PyModule_Create(&stack_struct);

if (PyType_Ready(&ArrayStackType) < 0) {
return NULL;
}
Py_INCREF(&ArrayStackType);
PyModule_AddObject(stack, "ArrayStack", reinterpret_cast<PyObject*>(&ArrayStackType));

if (PyType_Ready(&ArrayType) < 0) {
return NULL;
}
Py_INCREF(&ArrayType);
PyModule_AddObject(stack, "Array", reinterpret_cast<PyObject*>(&ArrayType));

if (PyType_Ready(&OneDimensionalArrayType) < 0) {
return NULL;
}
Py_INCREF(&OneDimensionalArrayType);
PyModule_AddObject(stack, "OneDimensionalArray", reinterpret_cast<PyObject*>(&OneDimensionalArrayType));

if (PyType_Ready(&DynamicArrayType) < 0) {
return NULL;
}
Py_INCREF(&DynamicArrayType);
PyModule_AddObject(stack, "DynamicArray", reinterpret_cast<PyObject*>(&DynamicArrayType));

if (PyType_Ready(&DynamicOneDimensionalArrayType) < 0) {
return NULL;
}
Py_INCREF(&DynamicOneDimensionalArrayType);
PyModule_AddObject(stack, "DynamicOneDimensionalArray", reinterpret_cast<PyObject*>(&DynamicOneDimensionalArrayType));


return stack;
}
16 changes: 16 additions & 0 deletions pydatastructs/miscellaneous_data_structures/_extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from setuptools import Extension

project = 'pydatastructs'

module = 'miscellaneous_data_structures'

backend = '_backend'

cpp = 'cpp'

stack = '.'.join([project, module, backend, cpp, '_stack'])
stack_sources = ['/'.join([project, module, backend, cpp, 'stack', 'stack.cpp'])]

extensions = [
Extension(stack, sources=stack_sources),
]
19 changes: 12 additions & 7 deletions pydatastructs/miscellaneous_data_structures/stack.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pydatastructs.linear_data_structures import DynamicOneDimensionalArray, SinglyLinkedList
from pydatastructs.miscellaneous_data_structures._backend.cpp import _stack
from pydatastructs.utils.misc_util import (
_check_type, NoneType, Backend,
raise_if_backend_is_not_python)
Expand Down Expand Up @@ -53,18 +54,22 @@ class Stack(object):
"""

def __new__(cls, implementation='array', **kwargs):
raise_if_backend_is_not_python(
cls, kwargs.get('backend', Backend.PYTHON))
backend = kwargs.get('backend', Backend.PYTHON)
if implementation == 'array':
return ArrayStack(
kwargs.get('items', None),
kwargs.get('dtype', int))
items = kwargs.get('items', None)
dtype = kwargs.get('dtype', int)
if backend == Backend.CPP:
return _stack.ArrayStack(items, dtype)

return ArrayStack(items, dtype)
if implementation == 'linked_list':
raise_if_backend_is_not_python(cls, backend)

return LinkedListStack(
kwargs.get('items',None)
kwargs.get('items', None)
)
raise NotImplementedError(
"%s hasn't been implemented yet."%(implementation))
"%s hasn't been implemented yet."%(implementation))

@classmethod
def methods(cls):
Expand Down
25 changes: 24 additions & 1 deletion pydatastructs/miscellaneous_data_structures/tests/test_stack.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from pydatastructs.miscellaneous_data_structures import Stack
from pydatastructs.miscellaneous_data_structures.stack import ArrayStack, LinkedListStack
from pydatastructs.miscellaneous_data_structures._backend.cpp import _stack
from pydatastructs.utils.raises_util import raises
from pydatastructs.utils.misc_util import _check_type
from pydatastructs.utils.misc_util import _check_type, Backend


def test_Stack():
s = Stack(implementation='array')
Expand All @@ -12,6 +14,11 @@ def test_Stack():
assert _check_type(s2, LinkedListStack) is True
assert raises(NotImplementedError, lambda: Stack(implementation=''))

s3 = Stack(backend=Backend.CPP)
assert _check_type(s3, _stack.ArrayStack) is True
s4 = Stack(implementation="array", backend=Backend.CPP)
assert _check_type(s4, _stack.ArrayStack) is True

def test_ArrayStack():
s = Stack(implementation='array')
s.push(1)
Expand All @@ -28,6 +35,22 @@ def test_ArrayStack():
assert str(_s) == '[1, 2, 3]'
assert len(_s) == 3

# Cpp test
s1 = Stack(implementation="array", backend=Backend.CPP)
s1.push(1)
s1.push(2)
s1.push(3)
assert s1.peek == 3
assert str(s1) == "['1', '2', '3']"
assert s1.pop() == 3
assert s1.pop() == 2
assert s1.pop() == 1
assert s1.is_empty is True
assert raises(IndexError, lambda : s1.pop())
_s1 = Stack(items=[1, 2, 3], backend=Backend.CPP)
assert str(_s1) == "['1', '2', '3']"
assert len(_s1) == 3

def test_LinkedListStack():
s = Stack(implementation='linked_list')
s.push(1)
Expand Down
4 changes: 2 additions & 2 deletions scripts/build/dummy_submodules_data.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
project = 'pydatastructs'

modules = ['linear_data_structures']
modules = ['linear_data_structures', 'miscellaneous_data_structures']

backend = '_backend'

cpp = 'cpp'

dummy_submodules_list = [('_arrays.py', '_algorithms.py')]
dummy_submodules_list = [('_arrays.py', '_algorithms.py'), ('_stack.py',)]
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import setuptools
from pydatastructs import linear_data_structures
from pydatastructs import miscellaneous_data_structures

with open("README.md", "r") as fh:
long_description = fh.read()

extensions = []

extensions.extend(linear_data_structures._extensions.extensions)
extensions.extend(miscellaneous_data_structures._extensions.extensions)

setuptools.setup(
name="cz-pydatastructs",
Expand Down

0 comments on commit 8f419fd

Please sign in to comment.