Skip to content

feat: add One dimensional Implicit array #613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pydatastructs/linear_data_structures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
OneDimensionalArray,
DynamicOneDimensionalArray,
MultiDimensionalArray,
ArrayForTrees
ArrayForTrees,
OneDimensionalImplicitArray
)
__all__.extend(arrays.__all__)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#ifndef LINEAR_DATA_STRUCTURES_ONEDIMENSIONALIMPLICITARRAY_HPP
#define LINEAR_DATA_STRUCTURES_ONEDIMENSIONALIMPLICITARRAY_HPP

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
#include <cstdlib>
#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<PyObject *>(self));
}

static PyObject *OneDimensionalImplicitArray___new__(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
OneDimensionalImplicitArray *self;
self = reinterpret_cast<OneDimensionalImplicitArray *>(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<PyObject *>(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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "DynamicArray.hpp"
#include "DynamicOneDimensionalArray.hpp"
#include "ArrayForTrees.hpp"
#include "OneDimensionalImplicitArray.hpp"

static struct PyModuleDef arrays_struct = {
PyModuleDef_HEAD_INIT,
Expand Down Expand Up @@ -47,5 +48,11 @@ PyMODINIT_FUNC PyInit__arrays(void) {
Py_INCREF(&ArrayForTreesType);
PyModule_AddObject(arrays, "ArrayForTrees", reinterpret_cast<PyObject*>(&ArrayForTreesType));

if (PyType_Ready(&OneDimensionalImplicitArrayType) < 0) {
return NULL;
}
Py_INCREF(&OneDimensionalImplicitArrayType);
PyModule_AddObject(arrays, "OneDimensionalImplicitArray", reinterpret_cast<PyObject*>(&OneDimensionalImplicitArrayType));

return arrays;
}
113 changes: 112 additions & 1 deletion pydatastructs/linear_data_structures/arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
__all__ = [
'OneDimensionalArray',
'MultiDimensionalArray',
'DynamicOneDimensionalArray'
'DynamicOneDimensionalArray',
'OneDimensionalImplicitArray',
]

class Array(object):
Expand Down Expand Up @@ -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)]
35 changes: 34 additions & 1 deletion pydatastructs/linear_data_structures/tests/test_arrays.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Loading