javascript, forEach and removeChild unpredictable behavior - javascript

I wish to remove all items from a list and replace them with others
var list = document.querySelector("ul");
[].forEach.call(list.childNodes, list.removeChild.bind(list));
The code above does not work as expected, instead it removes only half the items (leaving every second item in the list).
If i change it to
var list = document.querySelector("ul");
[].slice.call(list.childNodes).forEach(list.removeChild.bind(list));
Then it works as expected,
can anyone explain ?

In the first one you are mutating an array you are iterating over.
In the second you are making a copy and then iterating over it.
The following is another option that doesn't require making a copy:
for(; list.firstChild; list.removeChild(list.firstChild));
This removes the firstChild while it is not null.

Concept
To explain the "unpredictable" behavior in the first scenario, consider this case:
var array = [0, 1, 2, 3, 4, 5, 6, 7];
This makes the behavior easier to explain without the distracting .call() and .bind() methods to wrap your head around.
array.forEach(function(num, index) {
console.log(num, index);
array.splice(index, 1);
});
You may be wondering why the output is:
0 0
2 1
4 2
6 3
But it's actually really simple. .forEach() iterates over the indices until i < array.length is no longer satisfied, while at the beginning of each iteration, your array looks like this:
[0, 1, 2, 3, 4, 5, 6, 7];
^
0
[1, 2, 3, 4, 5, 6, 7];
^
1
[1, 3, 4, 5, 6, 7];
^
2
[1, 3, 5, 6, 7];
^
3
[1, 3, 5, 7];
^
(4 < array.length) !== true
This is what happens when you manipulate an array being iterated over within a call to .forEach().
For the case where you execute [].slice.call(array), all you're doing is making a shallow copy of all the indices of the array. This allows you to iterate over the copy's indices while removing the nodes from the original.
Below is a comprehensive example, but make sure your browser supports ES6 template strings.
Demo
var array = [0, 1, 2, 3, 4, 5, 6, 7];
document.write(`<p>original.forEach()</p>`);
array.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(index, 1);
});
document.write(`<pre>result: [${array}]</pre>`);
array = [0, 1, 2, 3, 4, 5, 6, 7];
var copy = array.slice();
document.write(`<p>copy.forEach()</p>`);
copy.forEach(function(num, index) {
document.write(`<pre>num: ${num}, index: ${index}, array: [${array}]</pre>`);
array.splice(array.indexOf(num), 1); // removing by reference, not by index
});
document.write(`<pre>result: [${array}]</pre>`);
body > * {
padding: 0;
margin: 0;
}

Related

how to solve targetArrayInGivenOrder in JavaScript

Write a function that takes two arrays of integers (nums and index) and
returns a target array under the following rules:
Initially target array is empty.
From left to right read nums[i] and index[i], insert at index index[i] the
value nums[i] in target array.
Repeat the previous step until there are no elements to read in nums and index.
Example 1
Input: nums = [0, 1, 2, 3, 4], index = [0, 4, 1, 2, 3]
Output: [0, 4, 1, 2, 3]
Example 2
Input: nums = [1, 2, 3, 4, 0], index = [0, 1, 2, 3, 0]
Output: [1, 2, 3, 4, 1]
Your example does not match your description. According to your example, you want to insert the nums[index[i]] in your target array's i'th position.
You can do this using Javascript's array.map function like this -
const targetArray = index.map(i => nums[i]);
You may need to add necessary sanity checks as well depending on the contexts.
You can simply achieve this by using Array.forEach() along with Array.splice() method.
Live Demo :
const numsArr = [1, 2, 3, 4, 0];
const indexArr = [0, 1, 2, 3, 0];
let outputArr = [];
numsArr.forEach((elem, index) => {
outputArr.splice(index, 0, numsArr[indexArr[index]]);
});
console.log(outputArr);

Using splice to delete item in array

Why does the remaining in original array = [1, 3, 5, 7, 9]
Since arr.splice(i, 1) = i is the target index and 1 is the number of item to be removed, i is increases to 10 respectively from i++ which short for i = i + 1, So why does it remove 5 index and remain 5 in the array ? that's what i know so far and i have struggled to read the docs but still have no idea to understand, please explain it for me
let arr = [1,2,3,4,5,6,7,8,9,10];
for(let i = 1; i < arr.length; i++) {
arr.splice(i, 1);
}
It is because the length of arr decreases everytime splice function runs.
Here is how the array changes.
[2, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 6, 7, 8, 9, 10]
[2, 4, 6, 8, 9, 10]
[2, 4, 6, 8, 10]
So every loop, i increases and arr.length decreases by 1. so only 5 loops runs and the result is [2, 4, 6, 8, 10]
You're wondering why it's removing 1, 3, 5, 7, and 9, right?
Here's why. As the for loop iterates, i keeps increasing by one.
HOWEVER, by calling .splice, you are removing the first element of the array, so as a result, every other element moves down an index.
Let's play this out step by step for a few iterations of the for loop.
i = 0; arr.splice(0, 1) removes 1, so arr is [2, 3, 4, 5, 6, 7, 8, 9, 10]
i = 1; arr.splice(1, 1) removes 3, not 2, because now 3 is at index 1 of arr. Performing the splice leaves arr as [2, 4, 5, 6, 7, 8, 9, 10].
i = 2; arr.splice(2, 1) removes 5, because 5 is currently at index 2. As a result, arr becomes [2, 4, 6, 7, 8, 9, 10].
Is it clear now what's going on?
If your goal is to successively remove each element, one at a time, then instead of calling .splice(i, 1) in each iteration of the loop, you should call .splice(0, 1), since the value at index 0 changes each time you call .splice.
Remember, arrays are 0 based. Second, the length is changing each time it evaluates.
MDN links:
Splice
Map
So you may want to try
i =< length
Where length is determined and is set ahead of time and constant. You can try mapping the array to a new one, so the original array stays pure and unaffected by the splice.
You need to check the for loop end condition, i is not increasing to 10. Why? because i < arr.length.
So it will like this :
Iteration 1:
i=0; arr.length = 10; arr = [1,2,3,4,5,6,7,8,9,10]; ==> result [2,3,4,5,6,7,8,9,10];
Iteration 2:
i=1; arr.length = 9; arr = [2,3,4,5,6,7,8,9,10]; ==> result [2,4,5,6,7,8,9,10];
Iteration 3:
i=2; arr.length = 8; arr = [2,4,5,6,7,8,9,10]; ==> result [2,4,6,7,8,9,10];
.
.
.and so forth
i = 5 ==> arr.length: 5 ==> final result : [2, 4, 6, 8, 10]
So if you want to delete all items using splice:
let arr = [1,2,3,4,5,6,7,8,9,10];
while(arr.length > 0) {
arr.splice(0, 1);
}

Javascript: Insert item into an the exact center of an array

Apologies if this is an asked/answered thing, but I did a general search and couldn't find the result I was looking for.
Say I've got an array, but I don't know the length of it, for whatever reason. I want to insert items into the array at an exact position (in this case, the center)
For the purposes of this question, I'll provide the array and how I got the output to read properly..
function insertIntoMiddle(array, item) {
array.splice(4, 2, item);
return array.sort();
}
const items = insertIntoMiddle([1, 3], 2);
console.log(insertIntoMiddle([1, 3], 2), '<-- should be [1 , 2 , 3]');
console.log(insertIntoMiddle([1, 3, 7, 9], 5), '<-- should be [1, 3, 5, 7, 9]');
And we get an output of:
[1, 2, 3] <-- should be [1 , 2 , 3]
[1, 3, 5, 7, 9] <-- should be [1, 3, 5, 7, 9]
Which is as it should be.. But my question is, what if, for whatever reason, say it's a database that's being read into an array for manipulation, and over time the database has grown. We don't know how long the array is..But we still want to insert into the EXACT middle of the array.. How would one go about doing that?
You could splice it with the half of the length.
function insertIntoMiddle(array, item) {
array.splice(array.length >> 1, 0, item);
return array
}
console.log(...insertIntoMiddle([1, 3], 2));
console.log(...insertIntoMiddle([1, 3, 7, 9], 5));
You can use two pointers, move first with +2 and second with +1, untill the end.
when the first one is at the end second one will be at the middle.
Insert the item at the position of second pointer.
function insertIntoMiddle(array, item) {
let i = 0;
let j = 0;
while (array[i]) {
i += 2;
j++;
}
array.splice(j, 0, item);
return array;
}
console.log(insertIntoMiddle([1, 3, 7, 9], 5));
console.log(insertIntoMiddle([1, 3], 2));

Lodash difference between _.remove() and _.pullAt()

What is the difference between lodash _.remove() and _.pullAt() functions?
var arr1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
_.remove(arr1, function (item) {
return item == 1
});
var arr2 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
_.pullAt(arr2, 1);
console.log(arr1.toString() + '\n' + arr2.toString());
// both result to [0,2,3,4,5,6,7,8,9,]
I've crated fiddle and read the description on the lodash site that states that _.remove()
Removes all elements from array that predicate returns truthy for and returns an array of the removed elements
and _.pullAt()
Removes elements from array corresponding to the given indexes and returns an array of the removed elements
Is there any difference at all? Or am I missing something?
Even your example made different things:
remove splices element by value, while pullAt by index.
Let's check it with different array [0, 3, 1, 1, 5]:
remove: [0, 3, 5] - all 1 items removed
pullAt: [0, 1, 1, 5] - arr[1] was spliced
You also can write other filters than compare by value with remove:
_.remove(arr, item => item % 2); // removes all odd numbers
_.remove(arr, user => user.deleted); // splice deleted users
_.remove(arr, item => item < 5); // and etc.

Explaination needed: Different outputs when used for...in and for(;;) in JavaScript

In NodeJS, I created the following two scripts, both of them was intended to remove even numbers from an array.
This is my 1st script:
#!/usr/bin/nodejs
var myarr = [2,3,5,1,6,2,28,5,7,90,3];
console.log(myarr);
for(var i in myarr){
if(myarr[i] % 2 == 0){
myarr.splice(i,1);
--i;
}
}
console.log(myarr);
Output for the first script was following:
[ 2, 3, 5, 1, 6, 2, 28, 5, 7, 90, 3 ]
[ 3, 5, 1, 2, 5, 7, 3 ]
In 2nd script, I changed for..in loop to for(;;) loop as follows:
#!/usr/bin/nodejs
var myarr = [2,3,5,1,6,2,28,5,7,90,3];
console.log(myarr);
for(var i=0;i<myarr.length;i++){
if(myarr[i] % 2 == 0){
myarr.splice(i,1);
--i;
}
}
console.log(myarr);
I got following output for the 2nd script:
[ 2, 3, 5, 1, 6, 2, 28, 5, 7, 90, 3 ]
[ 3, 5, 1, 5, 7, 3 ]
Although my intention was the same, two for loops gave me different outputs. I figured out that, in my first script, if there are two adjacent even numbers exist in the original array, if condition seems to be applied for the first even number only where the second even number is skipped. I would really appreciate if anybody can explain this difference clearly.
What you're doing is wrong. You're removing keys from the array whilst looping through the same array. Your for...in loop will only ever perform 7 iterations, as 4 of your keys are spliced from the array whilst the array is still being iterated through, whereas your for(;;) loop will always perform all 11 iterations as this is defined at the beginning (myarr.length).
You should define a second array to use for your results instead:
for...in
var myarr = [2,3,5,1,6,2,28,5,7,90,3],
resultarr = [];
console.log(myarr);
for(var i in myarr){
if(myarr[i] % 2 != 0){
resultarr.push(myarr[i])
}
}
console.log(resultarr);
-> [3, 5, 1, 5, 7, 3]
for(;;)
var myarr = [2,3,5,1,6,2,28,5,7,90,3],
resultarr = [];
console.log(myarr);
for(var i=0;i<myarr.length;i++){
if(myarr[i] % 2 != 0){
resultarr.push(myarr[i]);
}
}
console.log(resultarr);
-> [3, 5, 1, 5, 7, 3]
As an ending note, you shouldn't use the for...in loop for iterating through arrays anyway. This answer details why this is a bad idea.

Categories

Resources