datastructures/grid.js

/**
 * A grid is an abstraction around a two-dimensional array.
 * @class
 */
class Grid {
    type = 'Grid';

    /**
     * Constructs a grid.
     * @constructor
     * @param {number} rows
     * @param {number} cols
     */
    constructor(rows, cols) {
        if (arguments.length !== 2) {
            throw new Error('You should pass exactly 2 arguments to `new Grid(rows, cols)`');
        }
        if (typeof rows !== 'number' || !isFinite(rows)) {
            throw new TypeError(
                'Invalid value for `rows`. Make sure you are passing finite numbers to `new Grid(rows, cols)`.'
            );
        }
        if (typeof cols !== 'number' || !isFinite(cols)) {
            throw new TypeError(
                'Invalid value for `cols`. Make sure you are passing finite numbers to `new Grid(rows, cols)`.'
            );
        }

        rows = Math.max(0, rows);
        cols = Math.max(0, cols);

        this.grid = new Array(rows);
        for (let i = 0; i < rows; i++) {
            this.grid[i] = new Array(cols);
        }
    }

    /**
     * Initializes a Grid from an array.
     * @param {array} arr - Array containing elements to be made into a Grid.
     */
    initFromArray(arr) {
        if (arguments.length !== 1) {
            throw new Error('You should pass exactly 1 argument to `initFromArray`');
        }
        if (!Array.isArray(arr)) {
            throw new Error(
                'Invalid value passed to `initFromArray`. Make sure you are passing an array.'
            );
        }
        for (let i = 0; i < arr.length; i++) {
            for (let j = 0; j < arr[i].length; j++) {
                if (this.inBounds(i, j)) {
                    this.set(i, j, arr[i][j]);
                }
            }
        }
        return this;
    }

    /**
     * Initializes the contents of the grid with `value`.
     * @param {any} value - The value to be inserted in all
     * positions of the grid.
     */
    init(value) {
        if (arguments.length !== 1) {
            throw new Error('You should pass exactly 1 argument to `init`.');
        }
        if (typeof value === 'number' && !isFinite(value)) {
            throw new TypeError(
                'Non finite number passed to `init`. If you are passing a number, make sure it is a finite number.'
            );
        }

        for (let i = 0; i < this.numRows(); i++) {
            for (let j = 0; j < this.numCols(); j++) {
                this.grid[i][j] = value;
            }
        }
        return this;
    }

    /**
     * Gets the object stored at the requested row and column.
     * @param {number} row - The row of the desired object.
     * @param {number} col - The col of the desired object.
     * @returns {any} The value stored in the grid
     * at that position.
     */
    get(row, col) {
        if (arguments.length !== 2) {
            throw new Error('You should pass exactly 2 arguments to `get(row, col)`.');
        }
        if (typeof row !== 'number' || !isFinite(row)) {
            throw new TypeError(
                'Invalid value for `row`. Make sure you are passing finite numbers to `get(row, col)`.'
            );
        }
        if (typeof col !== 'number' || !isFinite(col)) {
            throw new TypeError(
                'Invalid value for `col`. Make sure you are passing finite numbers to `get(row, col)`.'
            );
        }
        return this.grid[row][col];
    }

    /**
     * Sets an object at the requested row and column.
     * @param {number} row - The row of the destination of the object.
     * @param {number} col - The column of the destination of the object.
     * @param {any} value - The value to be stored at
     * the specified location in the grid
     */
    set(row, col, value) {
        if (arguments.length !== 3) {
            throw new Error('You should pass exactly 3 arguments to `set(row, col, value)`.');
        }
        if (typeof row !== 'number' || !isFinite(row)) {
            throw new TypeError(
                'Invalid value for `row`. You passed a value of type ' +
                    typeof row +
                    '. Make sure you are passing a number.'
            );
        }
        if (typeof col !== 'number' || !isFinite(col)) {
            throw new TypeError(
                'Invalid value for `col`. You passed a value of type ' +
                    typeof col +
                    '. Make sure you are passing a number.'
            );
        }
        if (typeof value === 'number' && !isFinite(value)) {
            throw new TypeError(
                'Non finite value passed to `set`. If you are passing a number, make sure it is a finite number.'
            );
        }
        this.grid[row][col] = value;
    }

    /**
     * Returns the number of rows in the grid.
     * @returns {number} The number of rows in the grid.
     */
    numRows() {
        return this.grid.length;
    }

    /**
     * Returns the number of cols in the grid.
     * @returns {number} The number of cols in the grid.
     */
    numCols() {
        return this.grid[0].length;
    }

    /**
     * Checks whether the given row and col exist in the grid.
     * @param {number} row - Row of the position being checked.
     * @param {number} col - Col of the position being checked.
     * @returns {boolean} Whether or not the given position is in bounds.
     */
    inBounds(row, col) {
        if (arguments.length !== 2) {
            throw new Error('You should pass exactly 2 arguments to `inBounds(row, col)`.');
        }
        if (typeof row !== 'number' || !isFinite(row)) {
            throw new TypeError(
                'Invalid value for `row`. Make sure you are passing finite numbers to `inBounds(row, col)`.'
            );
        }
        if (typeof col !== 'number' || !isFinite(col)) {
            throw new TypeError(
                'Invalid value for `col`. Make sure you are passing finite numbers to `inBounds(row, col)`.'
            );
        }

        if (row < 0 || col < 0) {
            return false;
        }
        if (row >= this.numRows() || col >= this.numCols()) {
            return false;
        }
        return true;
    }

    /**
     * Converts a grid to a list.
     * For example:
     * -------
     * A B C D
     * E F G H
     * I J K L
     * -------
     * would convert to:
     * -------
     * [[0, 0, 'A'], [0, 1, 'B'], [0, 2, 'C'], [0, 3, 'D'], [1, 0, 'E']...[2, 3, 'L']]
     * @returns {array} List representation of the Grid.
     */
    toList() {
        let list = [];
        for (let i = 0; i < this.grid.length; i++) {
            for (let j = 0; j < this.grid[0].length; j++) {
                list.push([i, j, this.grid[i][j]]);
            }
        }
        return list;
    }

    /**
     * Generates a string representation of the Grid.
     * @returns {string} A representation of a grid of the format
     * "A B C \nD E F \nG H I \n"
     */
    toString() {
        let result = '';
        for (let i = 0; i < this.numRows(); i++) {
            for (let j = 0; j < this.numCols(); j++) {
                result += this.get(i, j) + ' ';
            }
            result += '\n';
        }
        return result;
    }
}

export default Grid;