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>
-
You can check this post: stackoverflow.com/questions/7486085/…Rajesh Dixit– Rajesh Dixit2016年02月15日 11:40:49 +00:00Commented Feb 15, 2016 at 11:40
-
your question is dupp - > stackoverflow.com/questions/122102/…Vitaliy Terziev– Vitaliy Terziev2016年02月15日 11:47:26 +00:00Commented Feb 15, 2016 at 11:47
-
@vtz I've already looked through that thread and did not find any useful answers.Hatefiend– Hatefiend2016年02月15日 11:47:57 +00:00Commented Feb 15, 2016 at 11:47
-
@Hatefiend well one answer from me below thenVitaliy Terziev– Vitaliy Terziev2016年02月15日 11:55:31 +00:00Commented Feb 15, 2016 at 11:55
3 Answers 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.
-
1That 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– Hatefiend2016年02月15日 12:22:31 +00:00Commented Feb 15, 2016 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 saymyObj.myMethod()
JS first looks formyMethod()
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– nnnnnn2016年02月15日 12:30:32 +00:00Commented Feb 15, 2016 at 12:30
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);
-
After cloning by calling
var clone = {};
andclone.extend(myBoard);
, it is now of typeObject
(or it seems to be?) because whenever I trydocument.write(clone);
, it does not call mytoString()
prototype
method.Hatefiend– Hatefiend2016年02月15日 12:02:00 +00:00Commented Feb 15, 2016 at 12:02
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);
-
Hmmm this method just yields
[object Object]
. It doesn't seem to recognize it as aSudokuBoard
. This is after using the lines:var clone = cloneObj(myBoard);
Hatefiend– Hatefiend2016年02月15日 11:55:13 +00:00Commented Feb 15, 2016 at 11:55 -
Tried it again but it's the same result as belowHatefiend– Hatefiend2016年02月15日 12:03:29 +00:00Commented Feb 15, 2016 at 12:03
-
Hmmm my JavaScript does not have
jQuery
in its scope. Does that involve downloading something or importing a library?Hatefiend– Hatefiend2016年02月15日 12:05:23 +00:00Commented Feb 15, 2016 at 12:05 -
@Hatefiend Have you imported the jQuery library before the javascript code?Haring10– Haring102016年02月15日 12:07:39 +00:00Commented Feb 15, 2016 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– Haring102016年02月15日 12:10:32 +00:00Commented Feb 15, 2016 at 12:10