diff --git a/pydatastructs/linear_data_structures/__init__.py b/pydatastructs/linear_data_structures/__init__.py index 057adc169..bb9d5719a 100644 --- a/pydatastructs/linear_data_structures/__init__.py +++ b/pydatastructs/linear_data_structures/__init__.py @@ -11,7 +11,8 @@ OneDimensionalArray, DynamicOneDimensionalArray, MultiDimensionalArray, - ArrayForTrees + ArrayForTrees, + OneDimensionalImplicitArray ) __all__.extend(arrays.__all__) diff --git a/pydatastructs/linear_data_structures/_backend/cpp/arrays/OneDimensionalImplicitArray.hpp b/pydatastructs/linear_data_structures/_backend/cpp/arrays/OneDimensionalImplicitArray.hpp new file mode 100644 index 000000000..db54f948f --- /dev/null +++ b/pydatastructs/linear_data_structures/_backend/cpp/arrays/OneDimensionalImplicitArray.hpp @@ -0,0 +1,172 @@ +#ifndef LINEAR_DATA_STRUCTURES_ONEDIMENSIONALIMPLICITARRAY_HPP +#define LINEAR_DATA_STRUCTURES_ONEDIMENSIONALIMPLICITARRAY_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#include "Array.hpp" +#include "../../../../utils/_backend/cpp/utils.hpp" + +typedef struct +{ + PyObject_HEAD + size_t _size; + PyObject *_dtype; + PyObject *_function; +} OneDimensionalImplicitArray; + +static void OneDimensionalImplicitArray_dealloc(OneDimensionalImplicitArray *self) +{ + Py_XDECREF(self->_dtype); + Py_XDECREF(self->_function); + Py_TYPE(self)->tp_free(reinterpret_cast(self)); +} + +static PyObject *OneDimensionalImplicitArray___new__(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + OneDimensionalImplicitArray *self; + self = reinterpret_cast(type->tp_alloc(type, 0)); + size_t len_args = PyObject_Length(args); + + if (len_args < 2) + { + PyErr_SetString(PyExc_ValueError, "Too few arguments to create a 1D implicit array, pass the function of the array and the size of the array"); + return NULL; + } + if (len_args > 2) + { + PyErr_SetString(PyExc_ValueError, "Too many arguments to create an implicit 1D array, pass the function of the array and the size of the array"); + return NULL; + } + + PyObject *dtype = PyObject_GetItem(kwds, PyUnicode_FromString("dtype")); + if (dtype == nullptr) + { + PyErr_SetString(PyExc_ValueError, "Data type is not defined."); + return NULL; + } + self->_dtype = dtype; + Py_INCREF(self->_dtype); + + PyObject *args0 = PyObject_GetItem(args, PyZero); + PyObject *args1 = PyObject_GetItem(args, PyOne); + + if (PyCallable_Check(args0) && PyLong_Check(args1)) + { + self->_function = args0; + Py_INCREF(self->_function); + self->_size = PyLong_AsSize_t(args1); + } + else if (PyCallable_Check(args1) && PyLong_Check(args0)) + { + self->_function = args1; + Py_INCREF(self->_function); + self->_size = PyLong_AsSize_t(args0); + } + else + { + PyErr_SetString(PyExc_TypeError, "Expected one function and one integer for size"); + return NULL; + } + + return reinterpret_cast(self); +} + +static PyObject *OneDimensionalImplicitArray___getitem__(OneDimensionalImplicitArray *self, PyObject *arg) +{ + size_t idx = PyLong_AsUnsignedLong(arg); + if (idx >= self->_size) + { + PyErr_Format(PyExc_IndexError, "Index, %d, out of range, [%d, %d)", idx, 0, self->_size); + return NULL; + } + PyObject *result = PyObject_CallFunctionObjArgs(self->_function, PyLong_FromSize_t(idx), NULL); + if (result == NULL) + { + return NULL; + } + if (raise_exception_if_dtype_mismatch(result, self->_dtype)) + { + return NULL; + } + return result; +} + +static Py_ssize_t OneDimensionalImplicitArray___len__(OneDimensionalImplicitArray *self) +{ + return self->_size; +} + +static PyObject *OneDimensionalImplicitArray_get_data(OneDimensionalImplicitArray *self, void *closure) +{ + PyObject *list = PyList_New(self->_size); + if (!list) + { + return NULL; + } + for (size_t i = 0; i < self->_size; i++) + { + PyObject *item = PyObject_CallFunctionObjArgs(self->_function, PyLong_FromSize_t(i), NULL); + if (item == NULL) + { + Py_DECREF(list); + return NULL; + } + PyList_SET_ITEM(list, i, item); + } + return list; +} + +static PyGetSetDef OneDimensionalImplicitArray_getsetters[] = { + {"_data", (getter)OneDimensionalImplicitArray_get_data, NULL, "data", NULL}, + {NULL} /* Sentinel */ +}; + +static PyMappingMethods OneDimensionalImplicitArray_PyMappingMethods = { + (lenfunc)OneDimensionalImplicitArray___len__, + (binaryfunc)OneDimensionalImplicitArray___getitem__, + (objobjargproc)0, +}; + +static PyTypeObject OneDimensionalImplicitArrayType = { + /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "OneDimensionalImplicitArray", + /* tp_basicsize */ sizeof(OneDimensionalImplicitArray), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)OneDimensionalImplicitArray_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 */ &OneDimensionalImplicitArray_PyMappingMethods, + /* tp_hash */ 0, + /* tp_call */ 0, + /* tp_str */ 0, + /* 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 */ 0, + /* tp_members */ 0, + /* tp_getset */ OneDimensionalImplicitArray_getsetters, + /* tp_base */ &ArrayType, + /* tp_dict */ 0, + /* tp_descr_get */ 0, + /* tp_descr_set */ 0, + /* tp_dictoffset */ 0, + /* tp_init */ 0, + /* tp_alloc */ 0, + /* tp_new */ OneDimensionalImplicitArray___new__, +}; + +#endif diff --git a/pydatastructs/linear_data_structures/_backend/cpp/arrays/arrays.cpp b/pydatastructs/linear_data_structures/_backend/cpp/arrays/arrays.cpp index 974f38b5b..d09af6955 100644 --- a/pydatastructs/linear_data_structures/_backend/cpp/arrays/arrays.cpp +++ b/pydatastructs/linear_data_structures/_backend/cpp/arrays/arrays.cpp @@ -4,6 +4,7 @@ #include "DynamicArray.hpp" #include "DynamicOneDimensionalArray.hpp" #include "ArrayForTrees.hpp" +#include "OneDimensionalImplicitArray.hpp" static struct PyModuleDef arrays_struct = { PyModuleDef_HEAD_INIT, @@ -47,5 +48,11 @@ PyMODINIT_FUNC PyInit__arrays(void) { Py_INCREF(&ArrayForTreesType); PyModule_AddObject(arrays, "ArrayForTrees", reinterpret_cast(&ArrayForTreesType)); + if (PyType_Ready(&OneDimensionalImplicitArrayType) < 0) { + return NULL; + } + Py_INCREF(&OneDimensionalImplicitArrayType); + PyModule_AddObject(arrays, "OneDimensionalImplicitArray", reinterpret_cast(&OneDimensionalImplicitArrayType)); + return arrays; } diff --git a/pydatastructs/linear_data_structures/arrays.py b/pydatastructs/linear_data_structures/arrays.py index 2e0c3fd97..b759ecfae 100644 --- a/pydatastructs/linear_data_structures/arrays.py +++ b/pydatastructs/linear_data_structures/arrays.py @@ -6,7 +6,8 @@ __all__ = [ 'OneDimensionalArray', 'MultiDimensionalArray', - 'DynamicOneDimensionalArray' + 'DynamicOneDimensionalArray', + 'OneDimensionalImplicitArray', ] class Array(object): @@ -471,3 +472,113 @@ def _modify(self): self._size = arr_new._size return new_indices return None + + +class ImplicitArray(Array): + ''' + Abstract class for implicit arrays + ''' + pass + + +class OneDimensionalImplicitArray(ImplicitArray): + """ + Represents one dimensional implicit arrays of fixed size. + + Parameters + ========== + dtype: type + A valid object type. + function: function + A function which takes an integer as input and returns + the value of the element at that index. + size: int + The number of elements in the array. + init: a python type + The initial value with which the element has + to be initialized. By default none, used only + when the data is not given. + backend: pydatastructs.Backend + The backend to be used. + Optional, by default, the best available + backend is used. + + Raises + ====== + + ValueError + When the number of elements in the list do not + match with the size. + More than three parameters are passed as arguments. + Types of arguments is not as mentioned in the docstring. + + Note + ==== + + At least two parameters should be passed as an argument along + with the dtype. + + Examples + ======== + + >>> from pydatastructs import OneDimensionalImplicitArray + >>> arr = OneDimensionalImplicitArray(int, lambda i: i+1, 5) + >>> arr[0] + 1 + >>> arr[1] + 2 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Array_data_structure#One-dimensional_arrays + """ + + __slots__ = ['_size', '_dtype', '_function', '_init'] + + def __new__(cls, dtype=NoneType, *args, **kwargs): + backend = kwargs.get('backend', Backend.PYTHON) + if backend == Backend.CPP: + return _arrays.OneDimensionalImplicitArray(dtype=dtype, *args, **kwargs) + if dtype is NoneType: + raise ValueError("Data type is not defined.") + elif not _check_type(dtype, type): + raise TypeError("Expected type of dtype is type") + if len(args) <= 1: + raise ValueError("Too many arguments to create a implicit 1D array, " + "pass the function of the array " + "and the size of the array") + if len(args) > 2: + raise ValueError("Too many arguments to create a implicit 1D array, " + "pass the function of the array " + "and the size of the array") + + obj = Array.__new__(cls) + obj._dtype = dtype + + if callable(args[0]) and \ + _check_type(args[1], int): + obj._function = args[0] + obj._size = args[1] + elif _check_type(args[0], int) and \ + callable(args[1]): + obj._function = args[1] + obj._size = args[0] + else: + raise TypeError("Expected type of function is function " + "and expected type of size is int") + + return obj + + def __getitem__(self, i): + if i >= self._size or i < 0: + raise IndexError(("Index, {} out of range, " + "[{}, {}).".format(i, 0, self._size))) + return self._function(i) + + def __len__(self): + return self._size + + @property + def _data(self): + return [self._function(i) for i in range(self._size)] diff --git a/pydatastructs/linear_data_structures/tests/test_arrays.py b/pydatastructs/linear_data_structures/tests/test_arrays.py index 886510113..977b2ee82 100644 --- a/pydatastructs/linear_data_structures/tests/test_arrays.py +++ b/pydatastructs/linear_data_structures/tests/test_arrays.py @@ -1,6 +1,6 @@ from pydatastructs.linear_data_structures import ( OneDimensionalArray, DynamicOneDimensionalArray, - MultiDimensionalArray, ArrayForTrees) + MultiDimensionalArray, ArrayForTrees, OneDimensionalImplicitArray) from pydatastructs.utils.misc_util import Backend from pydatastructs.utils.raises_util import raises from pydatastructs.utils import TreeNode @@ -155,3 +155,36 @@ def test_ArrayForTrees(): def test_cpp_ArrayForTrees(): _test_ArrayForTrees(Backend.CPP) + +def _test_OneDimensionalImplicitArray(backend: Backend): + ODIA = OneDimensionalImplicitArray + A = ODIA(int, lambda x: x + 1, 5, backend=backend) + assert A[0] == 1 + assert A[1] == 2 + assert A[2] == 3 + assert A[3] == 4 + assert A[4] == 5 + assert raises(IndexError, lambda: A[5]) + assert raises(IndexError, lambda: A[-1]) + assert str(A) == "[1, 2, 3, 4, 5]" + + A = ODIA(int, lambda x: (x + 2) ** 2, 10, backend=backend) + assert A[0] == 4 + assert A[1] == 9 + assert A[2] == 16 + assert A[3] == 25 + assert A[4] == 36 + assert A[5] == 49 + assert A[6] == 64 + assert A[7] == 81 + assert A[8] == 100 + assert A[9] == 121 + assert raises(IndexError, lambda: A[10]) + assert raises(IndexError, lambda: A[-1]) + assert str(A) == "[4, 9, 16, 25, 36, 49, 64, 81, 100, 121]" + +def test_OneDimensionalImplicitArray(): + _test_OneDimensionalImplicitArray(Backend.PYTHON) + +def test_cpp_OneDimensionalImplicitArray(): + _test_OneDimensionalImplicitArray(Backend.CPP)