Flattening array understanding code - javascript

In another question of mine someone posted a really cool solution on how to flatten an nth nested array into one array. Since I did not want to start a long chat, and I still don't really fully understand what this code does, I thought I'd ask.
So my impression is that first in this case our array has length 2, and then it becomes 1 in the while loop. We then check is array[1], is an array. It is so we proceed. Now here is where I'm a bit confused. I believe we call the flatten function again so we can get into the nested arrays, but I'm still kind of hazy on the reasoning. We then take array[1] and slice it, here doesn't slicing just mean getting the whole array[l] anyways? since we go from 0th position to the end since slice() has no parameters.
function flatten(array) {
var l = array.length, temp;
while (l--) {
if (Array.isArray(array[l])) {
flatten(array[l]);
temp = array[l].slice();
temp.unshift(1);
temp.unshift(l);
[].splice.apply(array, temp);
}
}
}
var array = [['1', '2', '3'], ['4', '5', ['6'], ['7', '8']]];
flatten(array);
console.log(array);
https://jsfiddle.net/curw7mdp/

So I'm going to assume you understand the basics of recursion. I'll walk you through line by line.
var l = array.length, temp;
Declares l equal to the length of the array, and declares temp.
while (l--)
This decrements l after the iteration of the loop (as opposed to --l doing it before);
if (Array.isArray(array[l]))
This is checking if the 'l'th element in the array is another array. This is important because it means this element isn't flat.
flatten(array[l]);
This is where it gets fun, the function recursively calls itself so that it can now traverse the sub-array. And if the sub-array contains another array, it can keep going down deeper. I believe this is head recursion.
temp = array[l].slice();
Looks a little weird, but this allows us to extract the array into a new variable called temp.
temp.unshift(1);
temp.unshift(l);
[].splice.apply(array, temp);
This is also a very janky way of writing things, but basically it puts 1 and l as the first to elements in the temp array, and then it calls splice on array, with temp as the parameters. Only the first two elements of temp are passed as parameters (the two we put in just a second ago), so it uses basically removes the sub array, and replaces it with the flattened version.

References: splice(), apply(), unshift()
See comments for detailed explanation.
function flatten(array) {
// put the length of the array in l
// and create a temp variable with nothing in it
var l = array.length, temp;
// while(l--) will iterate through
// each item in the array backwards
// this is just a little faster than doing
// for(var i=0; i<array.length; i++)
while (l--) {
// The current item in the while loop is array[l]
// (that's an L, by the way, not a one)
// so this is saying "if current item is an array...."
if (Array.isArray(array[l])) {
// we call the function again (recursion)
// if this is an array. eventually, one of
// the inner items will be something other
// than an array and the recursion will stop
flatten(array[l]);
// when you call slice() with no parameters
// all it does is create a copy of the entire array
// and then we store it in temp
temp = array[l].slice();
// adds the number 1 to the begining of the array
// after we unshift one more time below,
// this will become the second parameter for the
// splice() call below, which is the number of
// items to delete
temp.unshift(1);
// adds the current index to the begining of the array
// this will be the first argument passed to splice()
// below, the first argument for splice is the
// index to start splicing..
temp.unshift(l);
// apply()'s first argument is context, in this case
// passing "array" as the context means the action will
// be performed on the array variable,
// which is the original arrray..
// apply()'s second argumetn is an array, each item
// of the array is passed in order to the function
// as arguments
// So basically this is saying.. "remove the current
// index (which is an array) and replace it with each
// of the items that were in that array
[].splice.apply(array, temp);
}
}
}

Related

How can I insert a value on every index of array using a loop and slice and/or concat?

I'm currently doing an assignment for school and could really use your help.
I have to declare a function which takes two arguments, x and an array, arr. It has to return an array which contains multiple arrays with x inserted into respectively index 0 in the first array, index 1 in the second array and so on until there's no more numbers in the array. See picture of an example and for clarification of what the final result is expected to look like. It has to work on any given array and the assignment specifies that slice() and concat() would be good to use. example of assignment
function insert_all_positions (x, arr) {
var newArr = [];
for(var i = 0; i < arr.length; i++) {
return(arr.concat(x)); }
};
This just adds the x-value to the end of the array and I have to loop it so the value will be inserted at all indexes. I'm thinking the array.splice() method can be used, I'm just not sure how as I'm not particularly experienced with it. Thank you :)
As Nina already said: the idea of assignments is, that you try something yourself which we can then help you to improve.
Nonetheless, here is one simple way of doing what was required:
function iap(v,arr){
var l=arr.length, ret=[];
for (var i=0;i<=l;i++) ret.push(arr.slice(0,i).concat([v],arr.slice(i,l)));
return ret;
}
console.log(iap(8,[1,2,3]));
Try this
function myFunc(x,arr){
let result =[]
for(let i=0;i<arr.length;i++){
let arrToAdd = [...arr]
arrToAdd[i]=x
result.push(arrToAdd)
}
return result
}
You create a result array that you push your arrays into, then you run a loop that will run the exact number of times as the length of your argument arr. Each iteration of the loop creates a new array that is a copy of arr and then you just change one number in it each time and add the entire array to the result.

Why array.forEach(() => { array.pop() }) would not empty the array

While on the nodejs REPL I was trying to clean up an array defined as const array = [...] and just found out that using array.forEach(() => /pop|shift/()) would not work. After such expression the array will still hold values in it.
I'm well aware of better methods to clean the array, like array.splice(0), but I'm really curious about this behavior as seems counter-intuitive, at least for me.
Here's the test:
const a = [1, 2, 3]
a.forEach(() => {
a.shift()
})
console.log(a) // [ 3 ]
const b = [1, 2, 3]
b.forEach(() => {
b.pop()
})
console.log(b) // prints [ 1 ]
Notes
At first I was using arr.forEach(() => arr.pop()), so I though that one of the values was short-circuiting the forEach but wrapping the lambda in a body-block { .. } will also produce the same results.
The results are consistent across different node versions and browsers .. so it seems like it's well-defined behavior.
The quantity of leftover values, those still in the result array, change depending on the length of the input array, and seems to be Math.floor(array.length / 2)
The leftover values are always ordered accordingly to the /pop|shift/ method used, so some of the calls are actually changing the input array.
It also returns the same results by calling Array.prototype.forEach(array, fn)
Check out this quote from here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
If the values of existing elements of the array are changed, the
value passed to callback will be the value at the time forEach()
visits them; elements that are deleted before being visited are not
visited.
You're iterating from the beginning and removing the last element each iteration. This means you're moving forward 1, and reducing the length by 1, each iteration. Hence why you're ending up with floor(initialLength / 2) iterations. You're modifying the same array that you're forEaching, which as stated above means that you will not have the callback invoked for those pop'd elements.
Modifying an array while iterating over it is generally a bad idea. In fact, in Java, trying to do so would cause an exception to be thrown. But let's convert the forEach into an old-school for loop, and maybe you'll see the issue.
for (let i = 0; i < a.length; ++i) {
a.pop();
}
Is it clearer now what's going on? Each iteration you're shortening the length of the array by 1 when you pop the last element off. So the loop will end after iterating over half the elements -- because by then, it will have REMOVED half the elements, too, causing the value of i to be more than the current length of the array.
The same thing is happening when you use forEach: you're shortening the array with each iteration when you pop, causing the loop to terminate after only half the elements have been iterated. In other words, the iterator variable will move forward past the end of the array as the array shrinks.
.pop
Let's do this instead:
let arr = 'abcde'.split('');
arr.forEach((x,i,a) => {
console.log('before', x,i,a);
arr.pop();
console.log('after', x,i,a);
});
console.log(arr);
Your index is incrementing but your length is decrementing, so you're deleting the last elements when your index is in the first elements, thus the result where you delete the right half of the array.
.shift
Same: the iterating index goes one way, the length in another, so the whole things stop mid-work:
let arr = 'abcde'.split('');
arr.forEach((x,i,a) => {
console.log('before', x,i,a);
arr.shift();
console.log('after', x,i,a);
});
console.log(arr);

Can Array.splice() be used to create a sparse array by adding an element at an index beyond the last element of the array?

Can Array.splice() be used to create a sparse array by adding an element at an index beyond the last element of the array?" I need this because in some situations I just want to push onto the array, but in other situations I need to splice into the array. But trying to use splice to make the array sparse did not work, though in my particular situation I was able to implement some code to test whether to use splice, or just assign an array element at an index beyond the array's length.
No. The ECMAScript specification does not allow a relative start position greater than the array length. From ES2015 on Array.prototype.splice, step 7:
...let actualStart be min(relativeStart, len).
The variable actualStart is what's actually used for the splice algorithm. It's produced by the minimum of relativeStart (the first argument to the function) and len (the length of the array). If len is less than relativeStart, then the splice operation will use len instead the actual argument provided.
In practical terms, this means that you can only append values onto the end of arrays. You cannot use splice to position a new element past the length index.
It should be noted the length of the array is not necessarily the index of the last item in the array plus 1. It can be greater.
Then, you can't use splice to insert elements beyond the length of the array, but if you make sure the length is large enough, you can insert beyond the last index plus 1.
var arrSplice = ['what', 'ever'];
arrSplice.length = 10; // Increase the capacity
arrSplice.splice(10, 0, 'foobar'); // Now you can insert items sparsely
console.log(arrSplice.length); // 10
console.log(arrSplice[arrSplice.length - 1]); // foobar
Array.splice() cannot be used to create sparse arrays. Instead, if the index argument passed to Array.splice() is beyond the length of the array, it seems that the element just gets appended to the array as if Array.push() had been used.
/* How you might normally create a sparse array */
var arrNoSplice = ['foo', 'bar'];
arrNoSplice[10] = 'baz';
console.log(arrNoSplice.length); // 11
/* Demonstrates that you cannot use splice to create a sparse array */
var arrSplice = ['what', 'ever'];
arrSplice.splice(10, 0, 'foobar');
console.log(arrSplice.length); // 3
console.log(arrSplice[arrSplice.length - 1]); // foobar

Fast find for indices by ID

I have million of objects each have an unique ID - number.
Each object for making it simple contains name
They objects are being added into array.
Into this array i'm adding and removing objects.
In order to remove object I have the id, and then need to find the index in the array and splice it out.
In my case i have allot of objects and can have allot of removes operations. so in case i have 1000 removes. and all of this objects ids are stored in the end of the array, than i will run over the all 1 million objects till i find them.
Storing the index in the object after adding is not good, because every each remove i need to update the indices of all objects stored after the removed one.
For example removing the first 1000 will cause updating the rest of the 1M-1000 items indices.
My question is, what is the best solution for my problem?
-- UPDATE --
for example: My flat array look like this after adding 1M objects
[ obj1, obj2, obj3, .... obj1000000 ]
I want to remove now the object obj1000000. For finding which index this object
was inserted to i need to run over all the array (or till i found the item) and compare the current item id with my obj1000000 id, and break out from the loop when found. Then remove the item by it's index.
If i would store the index of each object in the object itself after it being added to the array, i would have to update the rest of the objects indices after removing one.
For example: obj1 will contains property index=0, obj2 will have index=1 and so on. To remove obj5 i just get its property index which is 4 and remove it. but now obj6 which has index=5 is incorrect. and should be 4. and obj7 should be 5 and so on. so update must be done.
My SmartArray holds an TypedArray created in some size. And i'm expending it if needed. When push is called. I'm simply set the value in the last item this._array[this.length++] = value; (Checking of course if to expand the array)
SmartArray.prototype.getArray = function () {
return this._array.subarray(0, this.length);
}
SmartArray.prototype.splice = function (index, removeCount) {
if (index + removeCount < this.length) {
var sub = this._array.subarray(index + removeCount, this.length);
this._array.set(sub, index);
} else {
removeCount = this.length - index;
}
this.length -= removeCount;
}
It is working very fast, subarray doesn't create a new array. And set is working very fast as well.
The standard solutions for this problem are
balanced (binary) trees,
hash tables.
They take respectively O(Log(N)) and O(1) operations per search/insertion/deletion on average.
Both can be implemented in an array. You will find numerous versions of them by web search.

using .slice method on an array

I'm practicing the array section of JavaScript Koan and I'm not fully understanding why these answers are correct. I added my assumptions below if someone could please clarify/let me know if I'm wrong :
it("should slice arrays", function () {
var array = ["peanut", "butter", "and", "jelly"];
expect(array.slice(3, 0)).toEqual([]);
Why wouldn't it at least slice "jelly" since the slice begins with
3? How does the cut off of 0 make it empty instead?
expect(array.slice(3, 100)).toEqual(["jelly"]);
If the cut off index goes beyond what currently exists in the array,
does this mean that a new array created from slice would contain all
indexes starting at 3 until the end of the array?
expect(array.slice(5, 1)).toEqual([undefined];
Will it always be undefined if the starting index doesn't exist in the
array?
});
The second argument to Array.slice() is the upper bound of the slice.
Think of it as array.slice(lowestIndex, highestIndex).
When you slice from index 3 to index 100, there is one item (in your case) that has index >= 3 and < 100, so you get an array with that one item. When you try to take a slice from index 3 to index 0, there can't be any items that meet the conditions index >= 3 and < 0, so you get an empty array.
--EDIT--
Also, array.slice() should never return undefined. That's one of the advantages of using it. If there are no matching values in the array, you just get back an empty array. Even if you say var a = new Array() and don't add any values to it, calling a.slice(0,1) will just give you an empty array back. Slicing from outside of the array bounds will just return an empty array also. a.slice(250) will return [] whereas a[250] will be undefined.

Categories

Resources