filling a multidimensional array within "for" loop (javascript) - javascript

It seems like JavaScript somehow tries to optimize code, so if we want to fill a multidimensional array (largeArr) with changing values of one-dimensional array (smallArr) within a loop and use this code:
largeArr = []
smallArr = []
for (i=0; i<2; i++)
{
smallArr[0]=i
smallArr[1]=2*i
largeArr[i]=smallArr
}
we get an unexpected result: largeArr=[[1,2],[1,2]] (must be [[0,0],[1,2]]). So, Javascript calculates smallArr values in the first place, and only then fills largeArr.
To get the right result we must declare smallArr in the loop:
largeArr = []
for (i=0; i<2; i++)
{
smallArr = []
smallArr[0]=i
smallArr[1]=2*i
largeArr[i]=smallArr
}
and then it works as expected (largeArr=[[0,0],[1,2]]).
Why does it behave this way?

Because Pointers, that's why. Javascript takes after Java, and C, in this (and only this) way. When you do the assignment
largeArr[i] = smallArr
you're assigning a pointer. A breakdown of pointers:
In C, (and to a lesser extent, Java and Javascript) you don't have a basic array type - instead, an array points to a space in memory, and you can fill that space with whatever information you want (or rather, you've declared). The way a pointer exists in memory? A four (or eight, or two, depending on your system) byte memory address, which tells the compiler/parser where to get the appropriate in formation. So, when you do that assignment there, you're telling it: "Hey, set largeArr[i] equal to the memory address of smallArr." Thus, when you make changes to smallArr, it's reflected every time you dereference the array - because it's actually the same array. But when you do:
smallArr = []
inside the loop, you're saying, "make a new array, and set smallArr equal to the address of that array." That way, the arrays stay separate.

With the line largeArr[i]=smallArr, you set the i property to a reference to the smallArr. You do not copy it. In the end, all properties of the largeArr will point to the same one smallArr, where you have overwritten the values each time.
By initializing the smallArr each loop turn, you create new objects; so each property of largeArr will point to a different array. Btw, it is an assignment, not a declaration - you would (and should) declare the variables as local (to the function) with a var statement.

In the last for iteration
smallArr[0]=i
smallArr[1]=2*i
(where i=1) the above code is transformed into :
smallArr[0]=1
smallArr[1]=2
And your big array is nothing than this :
[smallArr, smallArr]
which leads to the unexpected result :
[[1, 2], [1, 2]]
In javascript objects are copyed by reference (a kind of c style pointer).
In order to have the desired result, you must copy the array by value, or assign a different array in each loop :
var largeArr = [];
for (i=0; i<2; i++)
largeArr[i] = [[i, 2*i]];

When you assign an array reference as you have above, you're not assigning the values of that array, but just a reference to the array.
Think of it as a pointer. largeArr[0] and largeArr[1] are pointing to smallArr, and the loop iterations are simply changing the contents of smallArr. The thing to which largeArr is being "pointed" is not changing.

Related

how is this function mutating non-returned array

I'd ask a better question, but I don't don't know how. Thanks for your help.
***ISSUE: I'm sending array vari to a function and it's coming back changed even though I didn't return it or even use same variable name. Desired function: variable vari does not change
I've logged the function and isolated the change to the [].forEach() statement below, noted with ***. I send vari but return varis and assign to new variable sum. How does this change the vari variable?
//this is what calls the sub function using vari, an array, 411.0, 13.0
var sum = doSum1(vari);
function doSum1(vari0) {
// called from doSum1
// grab Variance data // ALL COLUMNS FOR NOW // fix loc/stat columns below
var vstat = vari0[0].indexOf('Status');
vari1 = vari0.filter(r=>r[vstat]); // to ensure indexOf works, and speed processing
var vhdr = ['Campaign ID','Campaign','Site ID','Site','Placement','Placement ID','Date','DCM Imp','Upw Imp','Tag Location','Status','Site Count','Site Imp'];
// move loc and status over in place of variance and percent (loc/stat will be site ct/imp)
varis=[];
// *** THIS FOREACH CHANGES varis AND vari. Not sure how... see more specifically below
['Not Tracking','Undertracking','Overtracking','Absent in DCM'].forEach(rf=>{
varis.push(vhdr.map(r=>''));
varis[varis.length-1][0]=rf;
varis.push(vhdr);
if(vari1.filter(r=>r[vstat].indexOf(rf)>=0).length==0) {
varis.push(vhdr.map(r=>''));
varis[varis.length-1][0]='none found';
} else {
varis.push(vari1.filter(r=>r[vstat].toString().indexOf(rf)>=0)[0]); // break out of outer []
//fix loc/stat location
//*** MORE SPECIFICALLY, this line in particular changes in vari, not just varis as intended.
varis[varis.length-1].splice(9,4,varis[varis.length-1][11],varis[varis.length-1][12],'','')
}
varis.push(vhdr.map(r=>'')); // trailing blank line
});
return varis;
I tried this in place of the splice as well, but same result... just not sure how varis is changing vari...
varis[varis.length-1][9] = varis[varis.length-1][11];
varis[varis.length-1][10] = varis[varis.length-1][12];
varis[varis.length-1][11] = '';
varis[varis.length-1][12] = '';
vari is a 2D array. That means that every element in vari is an array as well, and as such passed by reference and subject to mutation.
The Array.splice() method mutates its argument array. In the code, each varis[varis.length-1].splice() call modifies an array object that is copied from vari1 by reference, and therefore also vari0 whose elements are array objects that are copied to vari1 by reference. This is what causes vari to mutate.
To avoid the issue, use one these patterns:
var vari1 = vari0.map(row => row.slice()).filter(r => r[vstat]);
or
var vari1 = vari0.map(row => row.map(value => value)).filter(r => r[vstat]);
The patterns use Array.map() and Array.slice()to get a shallow copy of the 2D array referenced by vari0 (i.e., vari).
The first map() creates a new array of that contains the rows of vari0. The rows are arrays and therefore mutable, so a slice() or another map() is required to copy the rows into new arrays as well.
Note that the copy is shallow, which means that only primitive values such as text strings and numbers are copied by value. Your comments indicate that the rows of vari only contain primitives, so the pattern will make a copy that is safe to modify and will not mutate vari. Were the rows of the vari 2D array contain yet more arrays or other objects, the would be copied by reference and therefore still be subject to mutation.
Note that Array.splice() and Array.slice() are very different from each other. The Array.splice() method mutates its argument array. The Array.slice() method creates a shallow copy of the array, and is in fact often used to safely copy 1D arrays that contain primitives. In your use case, the vari array does not contain primitives but arrays, so we need to call slice() within map() to copy the primitive values in the second level of the 2D array.
In the general case, deep cloning an array or another object is surprisingly complex. The patterns above are probably the simplest way to do it in your use case. See What is the most efficient way to deep clone an object in JavaScript?

Array length Vs Number of values in Array

Recently i had to make an Array with values at large indexes (due to plugin constraints).
eg:
var names[100000] = "a";
var names[150000] = "b" ... and so on till 5 large indexes.
and in between all values are undefined names[100001] //undefined.
Now my doubt is Since the array has only 5 elements but if i do
names.length //it is 300001
its a large Array. I am not iterating this array nor i am running any loop through it. I will get the values directly through their defined indexes from the array.
So will this structure make any significant performance differences on the Browser or is it alright to use this as long as the number of values in the array is less irrespective of its indexes and no iteration is involved.
Thanks
The only thing that differentiates an array from a plain object is its length property and how it behaves (and a few array specific methods of course). The length value simply increases with certain operations, like setting a numeric property or pushing a new element. That's it in a nutshell. The array doesn't actually contain 100000 elements when you set the property 100000 to a value, all that's happening is that you're setting one property and the value of length is adjusted accordingly.
So, no, it won't have a lot of impact on performance, unless somebody actually iterates through the array using for (let i = 0; i < arr.length; i++).
You can create an array with the length, given by your plugin and work locally with an object to limit the iterations. After all your processing has been applied, you copy the values to the array and send it to the plugin's function.
Keep an array with the desired length as a buffer
var buffer = new Array(20000);
Internally work with an object
var data = {};
Assign values to the object
data[10001] = "foo";
Once transformations or data processing has been applied to the object, copy data to the buffer
for (key in data){
buffer[key] = data[key];
}
Send buffer to the plugin. And clear data, if desired.
By doing so, you will not iterate more, than the actual data you processed.

Javascript: Passing Address of Array Element into Function

In FORTRAN and C++, the address of a specific array element can be passed into a function. For example, in the main routine, WA1 is a work array of size 25 and offset is an integer variable that indicates the offset from the 0-index. Say offset is presently 6.
The declaration of the sub-routine might look like the following:
void Array_Manip1(double* WorkArray1){
. . .
When the sub-routine is called in the main program, the call might look like this:
Array_Manip1(&WA1[offset]);
By doing this, I can index WorkArray1 within the sub-routine starting at the 0-index, but knowing it is actually WA1[6].
e.g. -
for (int i = 0; i < 19; ++i)
WorkArray1[i] = whatever computation is required.
To do this in Javascript, I suppose the full array could be passed in to the sub-routine, plus one more variable to hold the offset. And then within the sub-routine, the offset would have to be added to the array index value.
e. g. -
for (int i = 0; i < 19; ++i){
WorkArray1[offset + i] = whatever computation is required.
But now I am passing one more variable into the sub-routine, and have to add the offset to the array index each time through the loop.
Is there a better way to do this in Javascript?
Is there a way to imitate C++'s ability to pass the address of a specific array element into a function?
The cleanest way would be to splice the array and pass in a subarray from the current index on. That way you still have one reference, and everything stays clean.
But no, arrays in most higher level languages do not allow you to reference a single element and then get back to the array. It is dangerous for a number of reasons on those kinds of languages where the underlying data may not even be stored contiguously. JavaScript is no exception, and you can pass in an array and an index, or a subarray, but you can't pass in a reference to an element in the array and get back to the array after passing it in.
Tim's answer is correct. I just want to add something about the C-like typed arrays: they can be created as a view into an ArrayBuffer, in which case you could create a new view of the same buffer as the larger array but starting at an offset, and pass that, without duplicating the underlying data. Closest you can get to your pointers.
You can sort of do what you want. And in fact, sometimes javascript can only do what you want. It all depends on what data the array contains.
In Javascript, the content of a variable may either be a value or a reference (pointer but without pointer arithmetic). But you have no choice in the matter. Numbers and strings are always values (there are exceptions but none of them apply when passing as function arguments) and everything else are always references.
So to get the behavior you want, simply use an object or array as your value holder instead of a string or number:
var ref_array = [ {value:1}, {value:2}, {value:3} ];
function increment (v_obj) {
v_obj.value ++;
}
var ref = ref_array[1];
increment(ref);
// ref_array will now contain: [{value:1},{value:3},{value:3}]
It's not that simple though. While the object appears to be passed by reference, the reference is however copied when the function is called. What this means is that ref and ref_array[1] and v_obj are three separate variables that point to the same thing.
For example, this wouldn't work:
function replace (obj1, obj2) {
obj1 = obj2;
}
replace(ref_array[1], {value:9});
// ref_array is still: [{value:1},{value:3},{value:3}]
That's because, while obj1 in the function above points to the same object as ref_array[1], it is not really a pointer to ref_array[1] but a separate variable. In C, this would be something like obj1 = &ref_array[1]. So passing an argument passes a copy of the pointer but not the pointer itself.

What is a good way to create a JavaScript array with big indices?

I'm making a web app where a user gets data from PHP, and the data consists of MySQL rows, so I want to save the used ones in a global variable, something like a buffer, to prevent extra AJAX requests.
I'm doing this right now :
window.ray = []; // global variable
$(function(){
data = getDataWithAjax(idToSearch);
window.ray[data.id] = data.text;
});
but when the id is big, say 10 for now, window.ray becomes this :
,,,,,,,,42
so it contains 9 unnecessary spots. Or does it? Is it only visible when I'm doing console.log(window.ray);
If this is inefficient, I want to find a way like PHP, where I can assign only indices that I want, like :
$array['420'] = "abc";
$array['999'] = "xyz";
Is my current way as efficient as PHP, or does it actually contain unnecessary memory spots?
Thanks for any help !
Use an object instead of an array. The object will let you use the id as the key and be more efficient for non-sequential id values.
window.ray = {}; // global variable
$(function(){
data = getDataWithAjax(idToSearch);
window.ray[data.id] = data.text;
});
You can then access any element by the id:
var text = window.ray[myId];
If you are assigning values directly by property name, then it doesn't make any difference in terms of performance whether you use an Array or an Object. The property names of Arrays are strings, just like Objects.
In the following:
var a = [];
a[1000] = 'foo';
then a is (a reference to) an array with length 1,001 (always at least one greater than the highest index) but it only has one numeric member, the one called '1000', there aren't 1,000 other empty members, e.g.:
a.hasOwnProperty['999']; // false
Arrays are just Objects with a special, self–adjusting length property and some mostly generic methods that can be applied to any suitable object.
One feature of sparse arrays (i.e. where the numeric properties from 0 to length aren't contiguous) is that a for loop will loop over every value, including the missing ones. That can be avoided and significant performance gains realised by using a for..in loop and using a hasOwnProperty test, just like an Object.
But if you aren't going to use any of the special features of an Array, you might as well just use an Object as suggested by jfriend00.

how to save an array in an object at runtime in javascript

I want to save an array in an object in runtime through a loop.
For example, I take an input in an array inp=[2, 7, 20, 15, 19] and I want to save it in an obj={0:2, 1:7, 2:20, 3:15, 4:19}. But, at runtime such that I have a
for(i=0;i<inp.length;i++)
{ save each element of array into the respective object element }
The problem is that I have to save arrays of different lengths, these array come from taking an input from user.
I am also sorting the object afterwards and returning the indices in another array in my code. I am stuck only at how to save an array in an object during runtime. I searched a lot for a clue to get started but, I could not find anything.
Autoassign: (credit: am not i am)
var obj = inp.slice();
Manual Assignment:
var obj = {};
for(var i=0, n=inp.length; i<n; i++)
obj[i]=inp[i];
Though an Array is technically a subclass of an Object in JavaScript, the only thing that is really happening in going from an Array to an Object, is that you're losing the native methods (indexOf,concat,reverse,etc) that are created during the array's construction.
Array is already an object
If you do some experiments, you would find out:
typeof([]) //<--retruns "object"

Categories

Resources