Is there a way to reference Javascript multidimensional arrays safely? - javascript

Imagine the following situation:
var array = new Array ( [0,0,0,0], [0,0,1,0], [0,0,0,0] );
var x = 0; var y = 0;
if(array[y][x]) {
// x and y can be any integer
// code should execute only for array[1][2]
}
When x and y refer to an item in the array that exists, everything is fine. Otherwise, the script terminates. Obviously this is not the behaviour I want - is it possible to reference Javascript multidimensional arrays safely?

You need to check that the referenced property exists at each level of the array:
if(array[y] && array[y][x]) {
// x and y can be any integer
// code should execute only for array[2][1]
}

You can use the in keyword to check whether there is a y-th element of the array and whether that element has a x-th element as preliminary checks:
if (y in array && x in array[y] && array[y][x]) {...
Javascript arrays aren't so much multidimensional as they are compound/jagged. You can also use Array.length, but that relies on the object being an array, which is part of what we're checking, so it complicates the check.

A bit more verbose than the other answers:
var array = [ [0,0,0,0], [0,0,1,0], [0,0,0,0] ];
var x = 0; var y = 0;
if(array.hasOwnProperty(y) && array[y].hasOwnProperty(x) && array[y][x] !== 0) {
// x and y can be any integer
// code should execute only for array[2][1]
}
...but this one is impervious to additions to Array.prototype.
Also, explicitly testing for equality with zero makes it more readable, IMHO. (Compensating for the reduced readability of the preceding conditions... :-P)

Related

How to use an element of an array as an argument of indexOf method?

I have two arrays of values. I want to use the elements of one array to be the argument of an indexOf function. But I get a -1 (meaning value not found) even when I know the value exists in the array.
I have tested this by hard coding the value in the argument of indexOf so I know in this case that my problem is with cur_data variable. When I hard code the cur_data[x] with 'xyz' the indexOf returns correct index however when I use the array value [xyz] it returns -1.
What am I doing wrong?
function iterateSheets() {
var price_data = SpreadsheetApp.openById('1Nttb7XqUlZwGtmwbcRc3QkY3f2rxx7XdsdEU3cK4K4').getSheetByName('price').getRange("A2:A353").getValues()
var price_data2 = price_data.map(function(r) {
return r[0];
});
var test = new Array(30)
var ts = SpreadsheetApp.openById('18qFvVMVEE1k5DWUYaSezKeobcLr8I4oAmHLUpd_X99k');
var allShts = ts.getSheets();
for (var i = 0; i < 1; i++) //allShts.length //need to add in code to make sure tab is one of the fcst tabs
{
var cur_data = allShts[i].getRange("B8").getValues()
if (allShts[i].getName() == "July" || allShts[i].getName() ==
"Aug" || allShts[i].getName() == "Sept") {
for (var x = 0; x < 1; x++) {
Logger.log(cur_data[x])
Logger.log(price_data2.indexOf(cur_data[x]));
}
}
}
}
2D Array of values
getValues() method returns a two-dimensional Array of values from the Range that should be accessed via the values[row][column] schema. The for loop only increments the first dimension, that is, rows, and never accesses the value via column reference. Thus, you end up passing an Array instance to the indexOf() method.
Modification
You can add a second for loop to iterate over each of the elements of the Array of values, plus modify the first loop to make it more flexible just in case you ever need to loop over multiple rows:
for (var x = 0; x < cur_data.length; x++) {
for (var y = 0; y < cur_data[x].length; y++) {
Logger.log(cur_data[x][y])
Logger.log(price_data2.indexOf(cur_data[x][y]));
}
}
Comparison
indexOf() method performs search via the strict equality comparison and here is where the fun part starts. As Array instances are also Objects, meaning the same rules of comparison that apply to objects apply to them. This means that no two objects are equal (take a look at the comparison result table).
Useful links
getValues() reference;
indexOf() MDN reference;
Equality comparisons guide;

Why is comparing an array and a number so slow in JavaScript? What is it doing?

I accidentally compared a large array and a number with <, and JavaScript locked up for over 5 seconds. What is the expected behavior of this comparison? Is it iterating over the whole array? MDN didn't clarify the situation.
As a concrete example, this code snippet takes over 5 seconds to print done:
var m = [];
m[268435461] = -1;
console.log('start');
if (m < 0) { }
console.log('done');
Javascript "arrays" (those with Array prototype, not typed arrays), are just objects, therefore this
var m = [];
m[268435461] = -1;
is exactly the same as
var m = {
"268435461": -1
}
except that in the first case, m has the Array prototype and a special length property.
However, methods defined in Array.prototype (like forEach or join) are trying to hide that fact and "emulate" sequential arrays, as they exist in other languages. When iterating their "this" array, these methods take its length property, increase the loop counter from 0 upto length-1 and do something with the value under the key String(i) (or undefined if there's no such key)
// built-in js array iteration algorithm
for (let i = 0; i < this.length - 1; i++) {
if (this.hasOwnProperty(String(i))
do_something_with(this[String(i)])
else
do_something_with(undefined)
Now, length of an array is not a number of elements in it, as the name might suggest, but rather the max numeric value of its keys + 1, so in your case, length will be 268435462 (check it!)
When you do m < 0, that is, compare a non-number to a number, JS converts them both to strings, and Array.toString invokes Array.join, which, in turn, uses the above loop to convert elements to strings and insert a comma in between:
// built-in js Array.join algorithm
target = '';
for (let i = 0; i < this.length - 1; i++) {
let element = this[String(i)]
if(element !== undefined)
target += element.toString()
target += ','
}
Illustration:
m = [];
m[50] = 1;
console.log(m.join())
This involves lots of memory allocations, and that's what is causing the delay.
(After some more testing, the allocation are not the deciding factor here, "hollow" loops will cause the same slowdown:
console.time('small-init')
var m = [];
m[1] = -1;
console.timeEnd('small-init')
console.time('small-loop')
m.forEach(x => null)
console.timeEnd('small-loop')
console.time('big-init')
var m = [];
m[1e8] = -1;
console.timeEnd('big-init')
console.time('big-loop')
m.forEach(x => null);
console.timeEnd('big-loop')
That being said, I don't think modern JS engines are that silly, and implement iterations exactly as described above. They do have array-specific optimizations in place, but these optimizations are targeted at "good" sequential arrays, and not at bizarre edge cases like this. Bottom line: don't do that!

Why do changes made to a cell propagate to other cells in this 2 dimensional array created using fill?

I have a problem with this 2-dimensional array in JS. When I change a[1][0], a[0][0] changes with it. Is there something wrong in the way I am initializing it? If yes, how can I initialize it properly?
>var a = Array(100).fill(Array(100).fill(false));
>a[0][0]
>false
>a[1][0]
>false
>a[1][0] = true
>true
>a[1][0]
>true
>a[0][0]
>true
var a = Array(100).fill(Array(100).fill(false));
a contains an array, each element of which references to an array. you are filling the outer array with an array which contains all false values. The inner array is being made only once and reference to the same array is passed to each element of outer array that is why if you perform an operation on one element it reflects on other elements as well.
This is actually equivalent to
var a1 = Array(100).fill(false);
var a = Array(100).fill(a1);
here a gets 100 elements all having reference to same array a1. So if you change one element of a, all elements change since they are references to same array.
you will need to fill each element in outer array with a new array. you can do something like this:
var a = [];
for(var i=0; i<100; i++)
a.push(Array(100).fill(false));
Your entire array will be filled with references to the same (second dimension) array object.
To fill it with distinct objects, you'd have to do something like this:
const a = Array(100).fill(false).map(x => Array(100).fill(false));
a[0][0] = true;
console.log(a[0][0]);
console.log(a[0][1]);
console.log(a[1][1]);
Note that the values in the initial array need to be explicitly set to something (in my example false, but could also be undefined), because the array created by the constructor is sparse and the map() function will only operate on properties that actually exist.
To work around that, you could use Array.from():
const a = Array.from(Array(100), x => Array(100).fill(false));
a[0][0] = true;
console.log(a[0][0]);
console.log(a[0][1]);
console.log(a[1][1]);
Your problem is that you are using the second Array(100).fill(false) in every position of the first array. Thats why when you change one value in the "second" dimension it will change in every single position of the array. If you want to avoid this you hve to create a new Array for each position of the initial.
var x = Array(100).fill().map(x => Array(100).fill(false));
what you are doing here is adding the same array object at each index of bigger array.
So when you do
Array(x).fill(Array(y).fill(false));
What you are doing actually is :
Array(x).fill(Y); // i.e. X.fill(Y)
Now whenever you change Y, you will get the same value at each index of X.
You should use loop to fill in data when data itself is an object(remember Array is object).
These looks like a duplicate question but I've always just used something like this
var x = new Array(10);
for (var i = 0; i < 10; i++) {
x[i] = new Array(10).fill(false);
}
x[5][5] = true
Credit: How can I create a two dimensional array in JavaScript?
Since arrays are reference types, creating an N Dimensional array in JS is not so trivial. You need to create a clone tool first.
Array.prototype.clone = function(){
return this.map(e => Array.isArray(e) ? e.clone() : e);
};
function arrayND(...n){
return n.reduceRight((p,c) => c = (new Array(c)).fill().map(e => Array.isArray(p) ? p.clone() : p ));
}
var arr = arrayND(2,3,4,"x")
console.log(JSON.stringify(arr));
arrayND takes indefinite number of integer arguments each designating the size of a dimension and the last argument is the fill value.
The fill function as described in mdn, copies the same value to all array indices. You can use the below utility to create clones of the array. But since i am using the slice operator, this is restricted to cloning arrays.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
if (!Array.prototype.clonefill) {
Object.defineProperty(Array.prototype, 'clonefill', {
value: function(value) {
// Steps 1-2.
if (this == null) {
throw new TypeError('this is null or not defined');
}
var O = Object(this);
// Steps 3-5.
var len = O.length >>> 0;
// Steps 6-7.
var start = arguments[1];
var relativeStart = start >> 0;
// Step 8.
var k = relativeStart < 0 ?
Math.max(len + relativeStart, 0) :
Math.min(relativeStart, len);
// Steps 9-10.
var end = arguments[2];
var relativeEnd = end === undefined ?
len : end >> 0;
// Step 11.
var final = relativeEnd < 0 ?
Math.max(len + relativeEnd, 0) :
Math.min(relativeEnd, len);
// Step 12.
while (k < final) {
O[k] = value.slice(0);
k++;
}
// Step 13.
return O;
}
});
}
Array(100).clonefill(Array(100))
a[0][0] = true
a[1][0]
false

Fill Arrays with random values

I have two 1D arrays and I want to fill them with 10 unique random x,y values in Processing.
For example:
x y
- -
3, 9
2, 4
6, 2
7, 5
My arrays are:
Table1 for the X values and
Table2 for the Y values.
My issue is if the number 3,9 exists already I don't want 9,3 to be stored in the arrays.
I can identify when x,y value (or y,x) already exists but once I replace it I cannot check if the new generated random number exist in the previous indexes.
This is what I have tried so far. However if 3 values aldready exists, the arrays Table1 and Table2 will store only 7 values instead of 10.
for (int i=0; i<10; i++) {
x=(int)random(6);
y=(int)random(6);
if ((Table1[i] != x && Table2[i] != y) || (Table1[i] != y && Table2[i] != x))
{
Table1[i] = x;
Table2[i] = y;
}
Any ideas how to control that?
I can think about only two ways of achieving it, and none is ideal.
Check if the numbers you generated already exists, and if it's the case, generate anothers until you get a unique combination. It could be expensive with a small range of possibilities, because it's random, and if you're very unlucky you could even end in an infinite loop...
Create an array containing every possible combination. Then, instead of generating random numbers, you'll generate a random index into this array (an integer in [0;array.length[). After that, you'll have to remove the choosen combination from the array (that way it won't be available for the next loop), and the inverse of it (if you picked (9;3), you have to remove (9;3) AND (3;9)).
I have this code that might help you,
first declare your arrays :
var a = [];
var b = [];
then you can call a function that does everything for you
fill(a,b)
The definition of this function should be something like this :
function fill(a, b) {
var arr = [];
while(arr.length<10) {
var pair = randomPair();
if (arr.indexOf(pair.join(','))==-1 || arr.indexOf(pair.reverse().join(','))==-1) {
a.push(pair[0]);
b.push(pair[1]);
arr.push(pair.join(','));
}
}
}
then the defintion of other used function is :
function randomPair () {
return [ parseInt(Math.random()*7) , parseInt(Math.random()*7) ]
}
so, obviously, the randomPair function returns 2 values x and y. the fill function tests if the pair already exists or not in normal order or reversed order. if not it's added both a, and b which are references to your main arrays;
I see no other option as to walk to the whole arrays again to check if they contains your new generated value(s).

JavaScript three assignation

I'm porting some JavaScript to Java and I'm having a hard time understanding two lines of the following piece of code:
var blocks=[];
for (var z=0; z<size; z++) {
var slice=blocks[z]=[]; //Those are the lines I don't understand.
for (var x=0; x<size; x++) {
var row=slice[x]=[]; //Those are the lines I don't understand.
for (var y=0; y<size; y++) {
row[y]=isFull(x,y,z);
}
}
}
The first line is declaring "slice", then it assigns "blocks[z]", and then again it assigns an empty array.
As I'm writing this it came to my head that maybe is for clearing any previous info before assigning new data, but I'm not sure.
Actually an empty array is assigned to blocks[z], and the value of blocks[z] (the empty array) is then assigned to slice.
Basically it's just a short way of assigning a value to two (or more) variables
yes and no, it would clear previous data, but that's what var is doing anyways.
The important part is that it assigns an array so that the following lines don't crash
blocks =[]; // blocks is an array of zero length 'filled' with NULL values;
var slice = blocks[z]; // slize would be the same as blocks[z] so it'd be NULL
blocks[z] = []; // blocks[z] is now an zero length array filled with NULL values.
All assignment code is executed from right to left so it first assigns an array to blocks[z] and assigns that same array to var slice
and so forth
x = y = z;
is exactly equivalent to:
y = z;
x = y;
in that order.

Categories