2

I have one core object to my project called SudokuBoard. The only field SudokuBoard has is a 2D array. All prototype functions of SudokuBoard involve the 2D array and need visibility of it at all times.

The problem:

In an algorithm coming up, I NEED to have some way to make copies of the SudokuBoard. It's mandatory functionality. Whenever I try to make a copy, it's 2D array is simply a reference to the old one. I'm not sure why.

I read about how clone is an absolute nightmare in Javascript so I'm a bit worried. I'm a beginner so the last thing I want to do is install Jquery or use some external library to solve this problem. I provided the files below; they should run with no errors.

SudokuBoard.js

/**
 * Constructs a SudokuBoard object.
 * Initializes the board with the numbers
 * provided to the constructor.
 * @param nums array, must be BOARD_SIZE^2 length.
 * @constructor
 */

function SudokuBoard(nums)
{
    // Private Fields:

    var BOARD_SIZE = 9;
    // The Sudoku board, represented as a 2D array.
    var gameboard = [];

    if (nums.length != BOARD_SIZE * BOARD_SIZE)
    {
        document.write("InvalidSizeError");
        throw "InvalidSizeError";
    }

    var counter = 0;
    for (var i = 0; i < BOARD_SIZE; i++)
    {
        var row = [];

        // Each row has a set amount of elements.
        while (row.length < BOARD_SIZE)
        {
            row.push(nums[counter]);
            counter++;
        }

        // Add the row to the board.
        gameboard.push(row);
    }

    SudokuBoard.prototype.getBoard = function()
    {
        return gameboard;
    }
}

/**
 * Gets all values within a row of the 2D array.
 * The Y coordinate works on the typical number
 * scale, meaning indexes start from 1, not 0.
 * Y corresponds to the vertical axis. The bottom
 * left of the board is at 1,1. The bottom right
 * of the board is at 9,1.
 * @param y coordinate of the row.
 * @returns {Array}
 */
SudokuBoard.prototype.getRow = function(y)
{
    return this.getBoard()[this.getBoard().length - y];
};

/**
 * Gets all values within a column of the 2D array.
 * The X coordinate works on the typical number
 * scale, meaning indexes start from 1, not 0.
 * X corresponds to the horizontal axis. The bottom
 * left of the board is at 1,1. The bottom right
 * of the board is at 9,1.
 * @param x coordinate of the column.
 * @returns {Array}
 */
SudokuBoard.prototype.getColumn = function(x)
{
    var column = [];

    for (var i = 1; i <= this.getBoard().length; i++)
    {
        column.push(this.getSlot(x, i));
    }

    return column;
};

/**
 * Algorithm which finds the correct quadrant of a given
 * coordinate and gets all the numbers which are contained
 * inside it. This operation relies on the fact that there
 * are three quadrants and once you make it so the first
 * index of quadrant one is considered as (3,3) you can
 * divide all X and Y values by 3 and yield their quadrant #.
 * @param x coordinate.
 * @param y coordinate.
 * @returns {Array}
 */
SudokuBoard.prototype.getQuadrant = function(x, y)
{
    // Determine what quadrant this coordinate is in.
    var horizQuad = Math.floor((x + 2) / 3); // 1 2 or 3
    var vertQuad = Math.floor((y + 2) / 3); // 1 2 or 3

    var quadrant = [];

    for (var i = 1; i <= 3; i++)
    {
        for (var h = 1; h <= 3; h++)
        {
            // Add the number to the array.
            quadrant.push(this.getSlot((horizQuad - 1) * 3 + i, (vertQuad - 1) * 3 + h));
        }
    }

    return quadrant;
};

/**
 * Gets a given slot on the board.
 * The X,Y coordinates work on the typical number
 * scale, meaning indexes start from 1, not 0.
 * X corresponds to the horizontal axis while Y
 * corresponds to the vertical axis. The bottom
 * left of the board is at 1,1. The bottom right
 * of the board is at 9,1.
 * @param x coordinate.
 * @param y coordinate.
 */
SudokuBoard.prototype.getSlot = function(x, y)
{
    return this.getBoard()[this.getBoard().length - y][x - 1];
};

/**
 * Sets a given slot on the board to a value.
 * The X,Y coordinates work on the typical number
 * scale, meaning indexes start from 1, not 0.
 * X corresponds to the horizontal axis while Y
 * corresponds to the vertical axis. The bottom
 * left of the board is at 1,1. The bottom right
 * of the board is at 9,1.
 * @param x coordinate.
 * @param y coordinate.
 * @param value to be placed.
 */
SudokuBoard.prototype.setSlot = function(x, y, value)
{
    this.getBoard()[this.getBoard().length - y][x - 1] = value;
};

SudokuBoard.prototype.clone = function()
{
    var numbers = [];

    for (var i = 0; i < this.getBoard().length; i++)
    {
        for (var h = 0; h < this.getBoard()[i].length; h++)
        {
            numbers.push(this.getBoard()[i][h]);
        }
    }

    return new SudokuBoard(numbers);
};

/**
 * ToString() method for SudokuBoard.
 * @returns {string}
 */
SudokuBoard.prototype.toString = function()
{
    const border = "+-----+-----+-----+";
    const nextline = "<br>";

    var temp = border + nextline;

    for (var i = 0; i < this.getBoard().length; i++)
    {
        temp += "|";

        for (var h = 0; h < this.getBoard()[i].length; h++)
        {
            // Every third character is proceeded by a |
            //\u00A0 for empty space.
            temp += ((this.getBoard()[i][h] == "0") ? "-" : this.getBoard()[i][h]) + ((h % 3 == 2) ? "|" : " ");
        }

        // Add a new line.
        temp += nextline;
    }

    // Return and add the bottom border.
    return temp + border;
};

Tester.js

var nums = [0, 0, 0, 0, 0, 0, 1, 4, 6,
            4, 0, 8, 7, 0, 0, 0, 0, 0,
            0, 6, 0, 0, 5, 0, 8, 9, 0,
            0, 0, 0, 1, 0, 0, 0, 0, 3,
            0, 8, 0, 0, 7, 4, 0, 0, 0,
            7, 0, 0, 0, 0, 0, 9, 0, 0,
            0, 0, 1, 8, 0, 9, 2, 0, 0,
            0, 0, 0, 5, 0, 0, 0, 0, 0,
            8, 0, 3, 0, 1, 7, 0, 0, 0];

var myBoard = new SudokuBoard(nums);
println("ORIGINAL:");
println(myBoard);

var clone = myBoard.clone();
println("CLONING:");
println(clone);

myBoard.setSlot(1, 1, 3);
println("CHANGED ORIGINAL:");
println(myBoard);

println("CLONE:");
println(clone);




/**
 * Used for debugging.
 * @param line
 */
function println(line)
{
    document.write(line + "<br>");
}

Runner.html

    <!DOCTYPE html>
<!--
  Project: SudokuSolver
  Name: Kevin
  Date: 2/12/2016
 -->

<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <font face="monospace"><font size="12">
    <!--Load from JavaScript file. -->
    <script type="text/javascript" src="SudokuBoard.js"></script>
    <script type="text/javascript" src="Tester.js"></script>
    </font></font>
</head>
<body>

</body>
</html>
Hatefiend
  • 3,416
  • 6
  • 33
  • 74

3 Answers3

3

I don't see anything wrong with your .clone() method.

The problem is in your constructor, in how it sets up the .getBoard() method. Each time your constructor is called you overwrite the prototype .getBoard() method, and that method refers to a local variable that is captured by the closure, not an instance variable. So call .getBoard() for any instance of SudokuBoard and you get the prototype method that will reference the local variable from the latest instance's closure and thus only return the most recently created board.

Change this line:

SudokuBoard.prototype.getBoard = function()

to create an instance method instead of a prototype method:

this.getBoard = function()

If every instance has its own .getBoard() they'll all reference their own closure.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • 1
    That did it! I had no idea you could even declare functions like that. I thought you needed to add `.prototype` if you wanted your function to be able to be called on an object, like so: `myObject.myFunction()`. Interesting. I'll have to be more careful next time. Thank you! – Hatefiend Feb 15 '16 at 12:22
  • Adding methods to the prototype is *typically* the correct way to go. It saves resources, because then all instances share the one copy of the function. But there is nothing wrong with assigning instance methods via `this` when needed. Note that regardless of whether it is directly on the instance or on the prototype, a "method" is just a property that happens to reference a function. When you say `myObj.myMethod()` JS first looks for `myMethod()`directly on the object, and then if not found it looks for it on the prototype, and if still not found it keeps looking up the prototype chain. – nnnnnn Feb 15 '16 at 12:30
0

Primitives are passed by value, Objects are passed by "copy of a reference".

This is a snippet I have that my mentor gave me to clone JavaScript objects:

function cloneObj(obj) {
    var result = {};
    for(var key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] == 'object') {
                result[key] = cloneObj(obj[key]);
            } else {
                result[key] = obj[key];
            }
        }
    }
    return result;
};

--

Or maybe try this (from this post)?

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
Community
  • 1
  • 1
Haring10
  • 1,517
  • 1
  • 19
  • 37
  • Hmmm this method just yields `[object Object]`. It doesn't seem to recognize it as a `SudokuBoard`. This is after using the lines: `var clone = cloneObj(myBoard);` – Hatefiend Feb 15 '16 at 11:55
  • Tried it again but it's the [same result as below](http://stackoverflow.com/questions/35408337/shallow-clone-of-an-object-javascript/35408424?noredirect=1#comment58518634_35408734) – Hatefiend Feb 15 '16 at 12:03
  • Hmmm my JavaScript does not have `jQuery` in its scope. Does that involve downloading something or importing a library? – Hatefiend Feb 15 '16 at 12:05
  • @Hatefiend Have you imported the jQuery library before the javascript code? – Haring10 Feb 15 '16 at 12:07
  • @Hatefiend, you cannot print an object to the screen like that. Console.log it out and you will se the object is duplicated. – Haring10 Feb 15 '16 at 12:10
  • @Hatefiend use `println(JSON.stringify(clone));` to see it on the screen – Haring10 Feb 15 '16 at 12:11
0

Try with David Flanagan's extend.

/*
 * Add a nonenumerable extend() method to Object.prototype.
 * This method extends the object on which it is called by copying properties
 * from the object passed as its argument.  All property attributes are
 * copied, not just the property value.  All own properties (even non-
 * enumerable ones) of the argument object are copied unless a property
 * with the same name already exists in the target object.
 */
Object.defineProperty(Object.prototype,
    "extend",                  // Define Object.prototype.extend
    {
        writable: true,
        enumerable: false,     // Make it nonenumerable
        configurable: true,
        value: function(o) {   // Its value is this function
            // Get all own props, even nonenumerable ones
            var names = Object.getOwnPropertyNames(o);
            // Loop through them
            for(var i = 0; i < names.length; i++) {
                // Skip props already in this object
                if (names[i] in this) continue;
                // Get property description from o
                var desc = Object.getOwnPropertyDescriptor(o,names[i]);
                // Use it to create property on this
                Object.defineProperty(this, names[i], desc);
            }
        }
    });


var mainObject = {name:'test'};
var clone = {};
clone.extend(mainObject);
Vitaliy Terziev
  • 6,429
  • 3
  • 17
  • 25
  • After cloning by calling `var clone = {};` and `clone.extend(myBoard);`, it is now of type `Object` (or it seems to be?) because whenever I try `document.write(clone);`, it does not call my `toString()` `prototype` method. – Hatefiend Feb 15 '16 at 12:02