Force an array to recalcuate length after sort - javascript

If you take an array and do the following:
arr = [];
arr[100] = 1;
The length will be 101, which makes sense due to 0-99 being set as undefined
Now if we sort that array: arr.sort() it will look like this: [1, undefined x100] since keys are not preserved. However, the length is still 101, since the undefined have all been moved to the end, instead of removed.
Is this behavior intentional and if so: is there a built-in function that removes undefined and recalculates and why is it intentional?
I am not asking how to write my own function to recalculate length. A sorted array's length can easily be forced with for (x = 0; arr[x] != undefined; x++);arr.length = x;

> arr = [];
> arr[100] = 1;
> The length will be 101, which makes sense due to 0-99 being set as undefined
The array has only one member at 100, it is like:
var arr = { '100': 1, 'length': 101};
Now if we sort that array: arr.sort() it will look like this: [1, undefined x100]
No, it doesn't. It has one member at 0 and a length of 100. There aren't 100 members with a value of 'undefined'.
If you wish to reduce the array to only the defined members, there is no built–in function to do that (i.e. essentially to set the length to the highest index that has a value).
You can only do that by iterating over the array, e.g. by reducing the length from the right until an existing property is reached:
function trimArray(arr) {
var i = arr.length;
while ( !(--i in arr)) {
arr.length -= 1;
}
}
Note that for the above to work properly, the array must be sorted. Also, it modifies the array passed in.
Here's a way to extend Array.prototype with a compress method so an array exists of only the defined members and the length is reset appropriately:
if (!('compress' in Array.prototype)) {
Array.prototype.compress = function() {
var i = 0,
lastExisting = 0,
len = this.length;
do {
if (i in this) {
this[lastExisting++] = this[i];
}
} while (++i < len)
this.length = lastExisting;
}
}
Note that it will not remove members whose value is undefined, only those that don't exists at all. So:
var x = [,,,,1,,,2];
x[20] = void 0;
x.compress()
alert(x + '\n' + x.length); // [1,2,<undefined>], length = 3
Edit
As pointed out by zzzzBov, this can also be done using filter:
var x = x.filter(function(item){return true;});
which will replace x with a new array of only the defined members regardless of their value. It will be non-sparse (or contiguous) and have an appropriate length, e.g.
var x = [,,,,1,,,2];
x[20] = void 0; // set x[20] to undefined, length is 21
x = x.filter(function(item){return true;}); // [2, 3, <undefined>], length = 3
Note that this method will only update the array referenced by x, it will not update any other reference to the same array, e.g.
var x = [,,1];
var y = x; // x and y reference same array
x = x.filter(function(item){return true}); // x is now another array of members
// filtered from the original array
alert(x); // [1] x references a different array
alert(y); // [,,1] y still references the original array
this may be useful behaviour if there is a requirement to keep the original array. In contrast, the compress method modifies the original array:
var x = [,,1];
var y = x; // x and y reference same array
x.compress();
alert(x); // [1] x is a modified version of the original array
alert(y); // [1] y references the same (original) array
I have no idea if there is an intention to add such a method to Array.prototype in future versions of ECMAScript or what it might be called, so a different name might be advisable, say xcompress or similar, to avoid unintended conflict with future versions and to make it obvious that it's a native method, not built–in.
I'm also a bit stumped for a suitable name, since packed and compressed already have defined meanings that are different to non–contiguous, but that seems unwieldy so it's compress for now.

arr = [];
arr[100] = 1;
The code won't set 0-99 to undefined, which it does is only set the key 100 to 1, and length property to 101.
And Array.sort won't change the property length of an array.
After sort, there are two own properties of arr, 0 is 1, and length is still 101.

If you're looking to get around the issue, filter the array once you've sorted it (or filter it before sorting).
var arr;
arr = [];
arr[100] = 1;
arr.sort();
arr = arr.filter(function (item) {
return typeof item !== 'undefined';
});
What's actually happening is that you're overriding the length property, which is used by the Array object to determine where the end of the array is, whether or not the array has that many properties.

Related

Nodejs code is not executing as expected

I am trying the below line of codes. I want to save the numbers after each iteration of outer while loop in an array named sn. But, after each iteration sn contains only the numbers of last iteration. May be I am missing whole concept of sync and async.
function test() {
var numbers = [0, 2, 7, 0];
var check = true;
var sn = [];
var p = 0;
while (check) {
var index = numbers.indexOf(Math.max(...numbers));
var value = Math.max(...numbers);
numbers[index] = 0;
for (var i = value; i > 0; i--) {
var temp = ++index;
index = temp % (numbers.length);
numbers[index] += 1;
}
console.log("numbers", numbers);
if (sn.includes(numbers)) { check = false };
sn.push(numbers);
console.log("hey there=========");
}
}
test();
There is nothing to do with sync or async here.
Here what is happening is that, you are trying to push 'numbers' array to 'sn' array.
Statement is "sn.push(numbers);"
So here we are pushing the Object reference of numbers array to 'sn', means you are not making a copy of numbers array and pushing to 'sn'.
You are just pushing the Memory reference of 'numbers' array.
So during first iteration, 'sn' will have exact value as you calculates.
But during the second iteration 'sn' will have two arrays. But those two values are same and points to the same memory location of 'number'.
So here what you should do is create a clone of 'numbers' array during each iteration.
if (sn.includes(numbers)) { check = false };
var cloneArray = numbers.slice(0);
sn.push(cloneArray);
This if statement: if (sn.includes(numbers)) { check = false }; will never be true because the Array.prototype.includes() method does not accept an array as a parameter; only individual elements. numbers is an array and thus will never be truthy.
If you are trying to see if an array contains a sub-array. The answer that Mild Fuzz has in this stack overflow: Javascript array contains/includes sub array should work.

Get first element in array with index not starting from 0

I'm using a javascript library which returns arrays not starting from zero like starting from 26 or 1500, what i want to do is a method to get the first element in that array regardless of the index number starting with 0 or any other number.
Are they any method to do this in javascript ?
I suggest to use Array#some. You get the first nonsparse element and the index. The iteration stops immediately if you return true in the callback:
var a = [, , 22, 33],
value,
index;
a.some(function (v, i) {
value = v;
index = i;
return true;
});
console.log(index, value);
The information below is generally useful, but for the problem the OP listed, Nina's answer is by far a better solution.
Those are called sparse arrays and they're one of the few situations where you may want to use for-in on an array.
Remember that arrays are objects in JavaScript, and array entries are properties keyed by names (array indexes) that meet certain criteria. So we can use the features that let us discover the properties on an object to find the indexes on your sparse array.
for-in example:
for (var n in theArray) {
if (theArray.hasOwnProperty(n) && isArrayIndex(n)) {
// Use theArray[n]
}
}
This answer shows how you can determine that n is an array index as opposed to being some other property. A very technical definition would be
function isArrayIndex(n) {
return /^0$|^[1-9]\d*$/.test(n) &&
n <= 4294967294;
}
...but a definition that's good enough for most of us would be
function isArrayIndex(n) {
return !isNaN(parseInt(n, 10));
}
Similarly, you can use Object.keys; since it only looks at own enumerable properties, you don't need the hasOwnProperty check:
Object.keys(theArray).forEach(function(n) {
if (isArrayIndex(n)) {
// ...
}
});
Note that officially, neither of those is in any particular order, not even in ES2015 ("ES6"). So in theory, you could see the indexes out of numeric order. In the real world, I've never seen an even vaguely-modern JavaScript engine that returned array indexes out of order. They're not required to, but every one I've tried does.
So officially, you would need to get a full list and then find the minimum value in it:
var min = Object.keys(theArray).reduce(function(min, n) {
var i = parseInt(n, 10);
return isNaN(i) || (min !== undefined && min > i) ? min : i;
}, undefined);
That'll given you undefined if the array is empty, or the min index if it isn't. But if you want to make the assumption you'll get the keys in numeric order:
// Makes an assumption that may not be true
var min = +Object.keys(theArray).filter(isArrayIndex)[0];
If you're using a JavaScript engine that's entirely up-to-date, you can rely on the order returned by Object.getOwnPropertyNames, which is required to list the array indexes in order.
var min = +Object.getOwnPropertyNames(theArray).filter(isArrayIndex)[0];
It may be useful to use a filter function on the array to get back a normalised array.
var fullArray = array.filter(function(n){
return n != undefined;
});
fullArray[0]
The answers here may help you decide Remove empty elements from an array in Javascript
I guess one alternative to Array.prototype.some() is the Array.prototype.findIndex() method. These are much faster than filter alone and will keep your array and indices untouched.
var arr = new Array(1000),
fi = -1;
arr[777] = 1453; // now we have a nice sparse array
fi = arr.findIndex(f => f !== void 0); // void 0 is the perfect undefined
console.log(fi);
console.log(arr[fi]);
With this piece of code you can find first assigned value index and then get the value from your array:
var a = [, , 22, 33];
var value = a.find((v, i) => i in a);
console.log(value);
/* ---------------------------------------------- */
var i = 0
while (!(i in a) && i < a.length) i++; // If i === a.length then the array is emtpy
console.info(i, a[i]);
First implementation uses Array.prototype.find which makes less variable usage so this is cleaner but to find the index you should call indexOf over the array.
But the second one is a little bit old fashioned but give the chance of having index without extra efforts.
BTW Nina's seems better. (can make it shorter?)
const arr = [0,1,2]
// using destructuring to get the first element
let [first] = arr
// plus: using destructuring to get the last element
let [first] = [...arr].reverse()

How to initialize 4d array in javascript?

Here is my code:
var arr = [[[[[]]]]];
var c = 20;
for (i=0;i<5;i++)
arr[i][0][0][0] = c;
alert(arr[2][0][0][0]);
It doesn't work, but how can I do this?
Most people here are using for loops, which I think are mostly obsolete in the age of anonymous functions in JavaScript. You people should know better :P
Anyway, you can solve this quite nicely in a one-liner. Here are a few scripts that can initialize your array...
If you already have a 4-dimensional array, you can initialize it elegantly like this:
arr.forEach(function(e) { e[0][0][0] = c })
Or, if you're more into map:
arr.map(function(e) { e[0][0][0] = c })
These are assuming you already have c defined, which you do in your code sample (20).
From now on, though, please Google your questions before asking them on stackoverflow. You will receive an answer that has already been accepted :)
It doesn't work because you haven't specified any elements beyond the first one, so the length of array is one and accessing further keys is incorrect.
I think, the most convenient way would be to push a new 3d array with c inside on every iteration (actually I have no idea what you're trying to achieve with this xD):
var arr = [];
var c = 20;
for (i=0;i<5;i++)
arr.push([[[c]]])
alert(arr[2][0][0][0]);
(in your example it's actually 5d, but as you've asked for 4d, writing 4d there)
It is unclear what you want, but I imagine a 4 dimension array is an array that has a set of arrays nested 3 deep, each of which has an array nested 2 deep, each of which has a single array that contains values.
In a one dimension array, you access the value at index 2 by:
arr[2];
In a two dimension array, you'd access the value at (2,3) by:
arr[2][3]
and so on until you get to the value at (2,3,1,2) in a four dimension array by:
arr[2][3][1][2]
and if that was the only value in the array, it would look like:
[,,[,,,[,[,,'value at 2312']]]];
If there was also a value at (1,1,0,2) the array would now look like:
[,[,[[,,'value at 1102']]],[,,,[,[,,'value at 2312']]]];
There can only be values in the last nested array, the value at indexes in every other array must be another array (for the lower dimensions), so to insert at value at, say (2,1,3,1) and assign it a value of 6, you need to loop over the array and inspect each index. If it's not already an array, insert an array and keep going, e.g.:
// Insert value in arrary at coord
// coord is a comma separated list of coordinates.
function insertValue( array, coord, value) {
var coords = coord.split(',');
var arr = array;
for (var c, i=0, iLen=coords.length-1; i < iLen; i++) {
c = coords[i];
if (!Array.isArray(arr[c])) arr[c] = [];
arr = arr[c];
}
arr[coords[i]] = value;
return array;
}
document.write('result: ' + JSON.stringify(insertValue([],'1,2,1,3','at 1213')));
I don't understand what you are trying to do in the OP: are you trying to create a value of 20 at coordinates (0,0,0,0), (1,0,0,0), (2,0,0,0), etc.? If that is the case, you also need a fill function that will iterate for the required number of times and pass suitable arguments to insertValue.
If that's what you want, then given the above you should be able to write such a function. On the first iteration it would pass:
insertValue(array, '0,0,0,0', 20)
and on the second:
insertValue(array, '1,0,0,0', 20)
and so on. You may wish to modify the function so that instead of the coords being a CSV string, you pass an array like [0,0,0,0] (which is what split turns the CSV string into), but that's up to you.
Note that you must pass all 4 dimensions, otherwise you will replace one of the dimension arrays with a value and effectively delete all other points in that dimension sector.
PS
ES5 introduced forEach, which helps encapsulate loops but doesn't necessarily mean less code, or faster execution, than an equivalent for loop:
// Insert value in arr at coord
// coord is a comma separated list of coordinates.
function insertValue( array, coord, value) {
var arr = array;
var coords = coord.split(',');
var last = coords.pop();
coords.forEach(function(c) {
if (!Array.isArray(arr[c])) arr[c] = [];
arr = arr[c];
})
arr[last] = value;
return array;
}
Create array with 5 nested arrays:
var arr = [[[[[]]]], [[[[]]]], [[[[]]]], [[[[]]]], [[[[]]]], [[[[]]]]];
var c = 20;
for (i=0;i<5;i++)
arr[i][0][0][0] = c;
alert(arr[2][0][0][0]);
EDIT: if you dig into functional programming and recursion, you can initialize your multidimensional array with just a few lines of code. Let's say you want 4-dimensional array with length 10 of each dimension:
function createNDimensionalArray(n, length) {
return n === 1
? new Array(length)
: Array.apply(null, Array(length)).map(createNDimensionalArray.bind(null, n - 1, length));
}
var arr = createNDimensionalArray(4, 10);
console.log(arr); // creates 4-dimensional array 10x10x10x10
Notice that initialization like this could be very slow if you create very big arrays (e.g. createNDimensionalArray(5, 10000).
If you prefer to set length of each dimension, you can modify previous the solution like this:
function createNDimensionalArray(dims) {
return dims.length === 1
? new Array(dims[0])
: Array.apply(null, Array(dims[0])).map(createNDimensionalArray.bind(null, dims.slice(1)));
}
var arr = createNDimensionalArray([2, 3, 4, 5]);
console.log(arr); // creates 4-dimensional array 2x3x4x5

Lodash forEach Associative Array

Is there a forEach loop in Lodash for associative arrays? The function called "forEach", I've found, only works for indexed arrays. For example, if I have an array myArray with values [1, 2, 3], and do
lodash.forEach(myArray, function(index) {
console.log(index);
});
and run the function (in Node), I get the expected result:
1
2
3
However, when I try this with an associative array, it doesn't work:
lodash = require('lodash');
myArray = [];
myArray['valOne'] = 1;
myArray['valTwo'] = 2;
myArray['valThree'] = 3;
lodash.forEach(myArray, function(index) {
console.log('7');
});
As you can see from running this in Node, the callback function doesn't fire even when it includes something other than the array elements. It just seems to skip the loop entirely.
First of all, why does this happen? Second of all, is there another function included in Lodash for this problem, or, if not, is there a way to use the forEach function to accomplish this, without changing the original array in the process?
Lodash has the function forOwn for this purpose. In the second array, if you do
_.forOwn(myArray, function(index) {
console.log(index);
});
you should get the intended result.
I'm still not sure why forEach seems to skip the first function, however, but I believe it may have to do with the array not having a "length". A JavaScript array's length is the highest numbered index it has. For example, an array myOtherArray defined as myOtherArray[999]="myValue" will have a length of 1,000 (because arrays are zero-indexed, meaning they start at 0, not 1), even if it has no other values. This means an array with no numbered indexes, or only negative indexes, will not have a length attribute. Lodash must be picking up on this and not giving the array a length attribute, likely to maintain consistency and performance, thus not rendering any output.
myArray = [];
myArray['valOne'] = 1;
myArray['valTwo'] = 2;
myArray['valThree'] = 3;
lodash.forEach(myArray, function(index) {
console.log('7');
});
An associative array is just a set of key value pairs, which is nothing but a Javascript object.
Above case - myArray.length === 0,
You are just addding properties to the array object, not adding any values to actual array.
Instead initialize your myArray like this and loop through using forIn
var myArray = {};
myArray['valOne'] = 1;
myArray['valTwo'] = 2;
myArray['valThree'] = 3;
lodash.forIn(myArray, function(value, key) {
console.log(key + " : " + value);
});
OR just
var myArray = {
valOne : 1,
valTwo : 2,
valThree : 3
};
lodash.forIn(myArray, function(value, key) {
console.log(key + " : " + value);
});
More about Object as Associative Array here

Can't reference array by index

I have an array defined as:
var subjectCache = [];
I then have some code to build it up, which is working ok.
However, if I try to reference the array by an index, e.g.:
var x = subjectCache[0];
or
var x = subjectCache[1];
I get undefined.
Also subjectCache.length is always 0 (zero).
if I try to reference it by its key, e.g.:
var x = subjectCache['12345'];
it works.
Is this normal? Shouldn't I be able to reference it by its index whatever?
I'm using Internet Explorer, if it makes a difference (and it probably does :( )
[Edit]
this is the code I'm using to build the array, although I really don't think it is to blame.
It's a callback from a webservice call. This is working fine and the array is being populated.
var subjectCache = [];
var subjectCacheCount = 0;
function refreshSubjectsCallback(data) {
// update subjects
// loop through retrieved subjects and add to cache
for( i=0; i < data.length; i++ )
{
var subject = data[i];
var subjectid = subject.SubjectId;
subjectCache[subjectid] = subject;
subjectCacheCount += 1;
}
}
[/Edit]
You're probably assigning keys manually instead of using subjectCache.push() to add new elements to the array:
var array = [];
array['foo'] = 'bar';
console.log(array.length); // 0
The length attribute isn't going to reflect those changes the way you'd expect:
> var a = [];
undefined
> a[100] = 2; // The previous `100` entries evaluate to `undefined`
2
> a.length;
101
Instead, use an object:
var object = {};
object['foo'] = 'bar';
for (var key in object) {
var value = object[key];
console.log(value);
}
From your symptoms, it sounds like you are trying to treat the array as an associative array.
In Javascript, arrays work like this:
var a = [];
a[1] = 10;
alert(a.length);
Objects work like this:
var o = {};
o.myProp = true;
o["myOtherProp"] = false;
Arrays only work with numeric keys not strings. Strings assign properties to the object, and aren't counted as part of length nor it's numeric indices.
When building the array, make sure you are assigning to a numeric position within the array.
No, it will not work, because you haven't created arrays but objects.
you will have to access it by its key.
var x = subjectCache['12345'];
If this works and subjectCache.length doesn't, I think you are making an object not an array. You are confused.
Somewhere along the road you lost the array, and the variable subjectCache points to a different kind of object.
If it was an array, it can't have the length zero and contain an item that is reachable using subjectCache['12345']. When you access an item in an array it doesn't make any difference if you use a numeric index or a string representing a number.

Categories

Resources