3
\$\begingroup\$

I've recently found myself in need of several numpy functions in C++. I've decided to write my own header-only implementation of the numpy.array, including functions that I most commonly use in my projects. The biggest piece of advice I'm looking for is memory management. I create a lot of temporary arrays to swap axes among other numpy functions, and while I free the memory afterwards, I feel there's a better way to accomplish what I want.

This is specifically compiled in c++17 so I can use the auto keyword when unpacking elements from the tuple.

numpp.hpp

/**
 * Num++ Header File.
 * @author Ben Antonellis
**/

// https://numpy.org/doc/stable/reference/arrays.ndarray.html

#include <iostream>
#include <fstream>
#include <tuple>

namespace numpp {

    enum datatype { int8, int16, int32, int64 };
    enum op { add, sub, mul, div };

    template <class T>
    class array {       

        public:

            T** arr;
            int rows;
            int cols;
            numpp::datatype type;

            /**
             * Constructor for an numpp array.
             * 
             * @param arr A two dimensional array of type T.
             * @param type A numpp datatype representing the data stored in the array.
             * 
             * @return void.
            **/
            template <int rows, int cols>
            array(T (&arr)[rows][cols], const numpp::datatype& type) {
                this->rows = rows;
                this->cols = cols;
                this->type = type;
                this->arr = new T*[this->rows * this->cols];
                for (int i = 0; i < this->rows; i++) {
                    this->arr[i] = new T[this->cols];
                }
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] = arr[i][j];
                    }
                }
            }

            array(const int& width, const int& height) {
                this->rows = width;
                this->cols = height;
                this->type = type;
                this->arr = new T*[this->rows * this->cols];
                for (int i = 0; i < this->rows; i++) {
                    this->arr[i] = new T[this->cols];
                }
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] = 0;
                    }
                }
            }

            /**
             * Destructor. Remember to delete ALL information.
            **/
            ~array() {
                for (int i = 0; i < rows; i++) {
                    delete this->arr[i];
                }
                delete this->arr;
            }

            /**
             * Prints the entire numpp array to the console.
            **/
            void print(void) const {

                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        std::cout << this->arr[i][j] << " ";
                    }
                    std::cout << std::endl;
                }

            }

            // Memory Layout //

            /**
             * Returns a tuple of array dimensions.
            **/
            std::tuple<int, int> shape(void) const { return {this->rows, this->cols}; }

            /**
             * Returns a tuple of bytes to step in each dimension when traversing the array.
            **/
            std::tuple<int, int> strides(void) const { return { sizeof(this->arr[0][0]), sizeof(this->arr[0][0]) * this->rows }; }

            /**
             * Returns the number of array dimensions.
            **/
            int ndim(void) const { return this->rows; }

            /**
             * Returns the number of elements in the array.
            **/
            int size(void) const { return this->rows * this->cols; }

            /**
             * Returns the length of one array element in bytes.
            **/
            int itemsize(void) const { return sizeof(this->arr[0][0]); }

            /**
             * Returns the total amount of bytes consumed by the elements of the array.
            **/
            int nbytes(void) const { return sizeof(this->arr[0][0]) * this->size(); }

            // Array Conversion //

            /**
             * Copies an element of an array to a standard value and returns it.
             * 
             * @param index N-th index of the two dimensional array.
            **/
            int item(const int& index) const {
                int pos = 0;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        if (pos == index) return this->arr[i][j];
                        pos++;
                    }
                }
                return -1;
            }

            /**
             * Copies an element of an array to a standard scalar and returns it.
             * 
             * @param x Row of array.
             * @param y Column of array.
            **/
            int item(const int& x, const int& y) const { return this->arr[x][y]; }

            /**
             * Modifies the passed array as an n-dimension level deep nested list of values.
             * 
             * @param destination Array to insert values into.
            **/
            void tolist(T* destination[]) const {
                *destination = new T[this->size()];
                for (int i = 0; i < this->size(); i++) {
                    (*destination)[i] = this->item(i);
                }
            }

            /**
             * Insert value into array at given index.
             * 
             * @param index N-th index of array.
             * @param value Value to place at index.
            **/
            void itemset(const int& index, const T& value) {
                int pos = 0;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        if (pos == index) {
                            this->arr[i][j] = value;
                            return;
                        }
                        pos++;
                    }
                }
            }

            /**
             * Insert value into array at given index.
             * 
             * @param x Row of array.
             * @param y Column of array.
             * @param value Value to place at [x][y] of array.
            **/
            void itemset(const int& x, const int& y, const T& value) { this->arr[x][y] = value; }

            /**
             * Returns a flattened representation of the array.
             * Default seperator is a space.
            **/
            std::string tostring(void) const { return this->tostring(' '); }

            /**
             * Returns a flattened representation of the array, seperated by the passed character.
             * 
             * @param seperator Character to seperate values.
            **/
            std::string tostring(const char& seperator) const {
                std::string str;
                for (int i = 0; i < this->size(); i++) {
                    str += std::to_string(this->item(i)) + seperator;
                }
                str.pop_back(); // Removes trailing seperator.
                return str;
            }

            /**
             * Writes the array to the given filepath. Each value will
             * be on its own line.
             * 
             * @param filepath Path to file.
            **/
            int tofile(const std::string& filepath) const { return this->tofile(filepath, '\n'); }

            /**
             * Writes the array to the given filepath with values being seperated.
             * 
             * @param filepath Path to file.
             * @param seperator Character to seperate values.
            **/
            int tofile(const std::string& filepath, const char& seperator) const {
                std::ofstream file(filepath);
                file << this->tostring(seperator);
                file.close();
                return !file.is_open();
            }

            /**
             * Fills the entire array with the passed value.
             * 
             * @param value Value to fill array.
            **/
            void fill(const T& value) {
                for (int i = 0; i < this->size(); i++) {
                    this->itemset(i, value);
                }
            }

            // Shape Manipulation //

            /**
             * Returns an array containing the same data but with a new shape.
             * The x, y of the new array need to be convertable from the previous
             * array.
             * 
             * @param x Width of new array.
             * @param y Height of new array.
            **/
            array* reshape(const int& x, const int& y) {
                if (x * y != this->size()) throw "(numpp::reshape) : x * y does not equal numpp array size!";
                array* destination = new array(x, y);
                T* thisTemp = new T[this->size()];
                T* otherTemp = new T[destination->size()];
                this->tolist(&thisTemp);
                destination->tolist(&otherTemp);
                for (int i = 0; i < this->size(); i++) {
                    otherTemp[i] = thisTemp[i];
                }
                for (int i = 0; i < this->size(); i++) {
                    destination->itemset(i, otherTemp[i]);
                }

                delete[] thisTemp;
                delete[] otherTemp;

                return destination;
            }

            /**
             * Changes the shape and size of the array in-place.
             * 
             * @param x Width of new array.
             * @param y Height of new array.
            **/
            void resize(const int& x, const int& y) {
                if (x * y != this->size()) throw "(numpp::reshape) : x * y does not equal numpp array size!";
                array* destination = new array(x, y);
                T* thisTemp = new T[this->size()];
                T* otherTemp = new T[destination->size()];
                this->tolist(&thisTemp);
                destination->tolist(&otherTemp);
                for (int i = 0; i < this->size(); i++) {
                    otherTemp[i] = thisTemp[i];
                }
                for (int i = 0; i < this->size(); i++) {
                    destination->itemset(i, otherTemp[i]);
                }
                this->arr = destination->arr;
                this->rows = destination->rows;
                this->cols = destination->cols;
                this->type = destination->type;

                delete[] thisTemp;
                delete[] otherTemp;
            }

            // Item selection and manipulation //

            /**
             * Returns an array formed from the elements of the current array at the given indices.
             * 
             * @param indices Array of indices to extrapolate data from.
             * @param count Number of indices.
             * @param results Where the elements will be stored.
            **/
            void take(int* indices, const int& count, T* results[]) {
                *results = new T[count];
                for (int i = 0; i < count; i++) {
                    (*results)[i] = this->item(indices[i]);
                }
            }

            /**
             * Places the array of values at the array of indices in the current array.
             * 
             * @param indices Array of indices.
             * @param values Array of values.
             * @param count Number of indices/values.
            **/
            void put(int* indices, T* values, const int& count) {
                for (int i = 0; i < count; i++) {
                    this->itemset(i, values[i]);
                }
            }

            /**
             * Modifies each value of the array by the value and operator passed.
             * 
             * @param value Value to input.
             * @param op Addition, subtraction, multiplication, or divison
            **/
            void modify(const T& value, const numpp::op& op) {
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        if (op == numpp::op::add) this->arr[i][j] += value;
                        if (op == numpp::op::sub) this->arr[i][j] -= value;
                        if (op == numpp::op::mul) this->arr[i][j] *= value;
                        if (op == numpp::op::div) this->arr[i][j] /= value;
                    }
                }
            }

            // Sorting Algorithm // (Insertion)

            /**
             * Performs the Insertion Sort algorithm on the numpp array.
            **/
            void sort(void) {

                // Convert numpp array to one dimensional list
                T* temp = new T[this->size()];
                this->tolist(&temp);

                // Perform sorting algorithm
                int key, j;
                for (int i = 0; i < this->size(); i++) {
                    key = temp[i];
                    j = i - 1;
                    while (j >= 0 && temp[j] > key) {
                        temp[j + 1] = temp[j];
                        j--;
                    }
                    temp[j + 1] = key;
                }

                // Replace numpp array values with new values
                int pos = 0;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] = temp[pos];
                        pos++;
                    }
                }

                // Erase memory
                delete[] temp;
            }

            /**
             * Returns the indices where the elements are not zero.
            **/
            int nonzero(int* temp[]) {
                // Get the count of non zero elements
                int count = 0;
                for (int i = 0; i < this->size(); i++) {
                    if (this->item(i) != 0) count++;
                }

                // Create array for storing elements
                *temp = new int[count];

                // Loop again finding and adding these elements
                for (int i = 0; i < this->size(); i++) {
                    T val = this->item(i);
                    if (val != 0)
                        (*temp)[i] = val;
                }

                // Return number of non-zero elements
                return count;

            }

            // Calculation //

            /**
             * Returns the maximum value in the array.
            **/
            T max(void) const {
                int value = INT32_MIN;
                for (int i = 0; i < this->size(); i++) {
                    T temp = this->item(i);
                    if (temp > value) value = temp;
                }
                return value;
            }

            /**
             * Returns the maximum value on the axis.
             * 
             * @param axis Axis of array.
            **/
            T max(const int& axis) const {
                int value = INT32_MIN;
                for (int i = 0; i < this->cols; i++) {
                    T temp =this->arr[axis][i];
                    if (temp > value) value = temp;
                }
                return value;
            }

            /**
             * Returns the minimum value in the array.
            **/
            T min(void) const {
                int value = INT32_MAX;
                for (int i = 0; i < this->size(); i++) {
                    T temp = this->item(i);
                    if (temp < value) value = temp;
                }
                return value;
            }

            /**
             * Returns the minimum value on the axis.
             * 
             * @param axis Axis of array.
            **/
            T min(const int& axis) const {
                int value = INT32_MAX;
                for (int i = 0; i < this->cols; i++) {
                    T temp = this->arr[axis][i];
                    if (temp < value) value = temp;
                }
                return value;
            }

            /**
             * Returns the sum of all elements in the array.
            **/
            T sum(void) const {
                int value = 0;
                for (int i = 0; i < this->size(); i++) {
                    value += this->item(i);
                }
                return value;
            }

            /**
             * Returns the sum of all elements on the axis.
             * 
             * @param axis Axis of array.
            **/
            T sum(const int& axis) const {
                int value = 0;
                for (int i = 0; i < this->cols; i++) {
                    value += this->arr[axis][i];
                }
                return value;
            }

            /**
             * Returns the mean value of all elements in the array.
            **/
            T mean(void) const { return this->sum() / this->size(); }

            /**
             * Returns the mean value of all elements on the axis.
             * 
             * @param axis Axis of array.
            **/
            T mean(const int& axis) const { return this->sum(axis) / this->cols; }

            /**
             * Returns true if all elements int the array are true.
             * For integer numpp arrays, this will determine if all the
             * values are non-zero.
            **/
            bool all(void) const {
                for (int i = 0; i < this->size(); i++) {
                    if (!this->item(i)) return false;
                }
                return true;
            }

            /**
             * Returns true if all the elements on the axis are true.
             * For integer numpp arrays, this will determine if all the
             * values are non-zero.
             * 
             * @param axis Axis of array.
            **/
            bool all(const int& axis) const {
                for (int i = 0; i < this->cols; i++) {
                    if (!this->arr[axis][i]) return false;
                }
                return true;
            }

            /**
             * Returns true if any of the elements in the array are true.
             * For integer numpp arrays, this will determine if any of the
             * values are non-zero.
            **/
            bool any(void) const {
                for (int i = 0; i < this->size(); i++) {
                    if (this->item(i)) return true;
                }
                return false;
            }

            /**
             * Returns true if any of the elements on the axis are true.
             * For integer numpp arrays, this will determine if any of the
             * values are non-zero.
             * 
             * @param axis Axis of array.
            **/
            bool any(const int& axis) const {
                for (int i = 0; i < this->cols; i++) {
                    if (this->arr[axis][i]) return true;
                }
                return false;
            }
            
            // Arithmetic, matrix multiplication, and comparison operators //

            bool operator<(const array& other) const { return this->size() < other.size(); }
            bool operator<=(const array& other) const { return this->size() <= other.size(); }
            bool operator>(const array& other) const { return this->size() > other.size(); }
            bool operator>=(const array& other) const { return this->size() >= other.size(); }
            bool operator==(const array& other) const { return this->size() == other.size(); }
            bool operator!=(const array& other) const { return this->size() != other.size(); }

            array* operator=(const array* other) {
                this->arr = other->arr;
                this->rows = other->rows;
                this->cols = other->cols;
                this->type = other->type;
                return this;
            }
            
            array* operator+(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] += other->arr[i][j];
                    }
                }
                return this;
            }

            array* operator-(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] -= other->arr[i][j];
                    }
                }
                return this;
            }

            array* operator*(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] *= other->arr[i][j];
                    }
                }
                return this;
            }

            array* operator/(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] /= other->arr[i][j];
                    }
                }
                return this;
            }

            array* operator+=(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] += other->arr[i][j];
                    }
                }
                return this;
            }

            array* operator-=(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] -= other->arr[i][j];
                    }
                }
                return this;
            }

            array* operator*=(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] *= other->arr[i][j];
                    }
                }
                return this;
            }

            array* operator/=(const array* other) {
                if (this != other) return NULL;
                for (int i = 0; i < this->rows; i++) {
                    for (int j = 0; j < this->cols; j++) {
                        this->arr[i][j] /= other->arr[i][j];
                    }
                }
                return this;
            }

    };

}

And here's a test file, main.cpp

#include "numpp.hpp"

int main() {

    int x[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
    numpp::array<int> arr(x, numpp::int32);

    arr.print();

    const auto [width, height] = arr.shape();
    std::cout << "Width: " << width << " Height: " << height << std::endl;

    const auto [indexStep, rowStep] = arr.strides();
    std::cout << "Index Step: " << indexStep << " Row Step: " << rowStep << std::endl;
    std::cout << "Array Dimensions: " << arr.ndim() << std::endl;
    std::cout << "Array Size: " << arr.size() << std::endl;
    std::cout << "Item Size: " << arr.itemsize() << std::endl;
    std::cout << "Number of bytes: " << arr.nbytes() << std::endl;
    std::cout << "Item at index 5: " << arr.item(5) << std::endl;
    std::cout << "Item at index [2][3]: " << arr.item(2, 3) << std::endl;

    return 0;

}
\$\endgroup\$
2
  • 2
    \$\begingroup\$ From a quick look, you're badly mishandling memory and leaking a lot of it. You also have mismatched new[] vs delete usage (new[] needs to use delete[]). These should be addressed, and more extensive test code added. As it is, it will be unnaturally difficult to use your arithmetic operators (which should use references, not pointers). \$\endgroup\$ – 1201ProgramAlarm May 5 at 16:19
  • \$\begingroup\$ Here is something I cobbled with. Take a look if you're interested. github.com/frozenca/Ndim-Matrix \$\endgroup\$ – frozenca May 6 at 15:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.