array.splice removing values from remaining elements - javascript

I ran across this strange side effect of array.splice, and distilled the code down to the minimum necessary to recreate. Yes, much of this could be done on one line with array.filter, but I'm interested in whether I've made a mistake or if something else is going on.
var array = [];
for (var i = 0; i < 10; i++) {
array.push({
value: i
});
}
array.forEach(function(item, index, intArray) {
if (item.value % 2 == 1) {
item.odd = true;
} else {
item.odd = false;
}
if (item.odd) {
console.log("Removing " + item.value);
intArray.splice(index, 1);
}
});
console.log(array);
Running this javascript results in the odd elements being removed as expected, but it also removed the item.odd values for items 2, 4, 6, and 8. Removing the intArray.splice line brings back the odd array elements, but it also brings back the item.odd values for all elements.
I've tested this in FF and Chrome. The behavior persists even if only the item is passed into the callback, with the index calculated via array.indexOf, and referencing the array from outside the loop.

I think that when you splice the array at every odd number, the forEach ends up skipping over the next item, which is an even number. So those items don't get modified at all.
var array = [];
for (var i = 0; i < 10; i++) {
array.push({
value: i
});
}
array.forEach(function(item, index, intArray) {
console.log(item); // only prints out 0, 1, 3, 5, 7, 9
if (item.value % 2 == 1) {
item.odd = true;
} else {
item.odd = false;
}
if (item.odd) {
console.log("Removing " + item.value);
intArray.splice(index, 1);
}
});
console.log(array);
In other words, forEach only visits each index once. So say it gets to item 1, which is at index 1. It deletes item 1. Item 2 is now at index 1. But index 1 has already been visited, so it moves on to the item at index 2, which is now item 3.

Since splice is destructive and forEach is not 'live' [it is not automatically updated], the iteration after splice( index, 1 ) will skip over the "old index+1" (the next iteration-index) because it has 'dropped' down to the new index (where the old array element used to be). Using splice to delete array elements (no matter how many elements are deleted) will always create this unexpected 'slippage' from the old index to the new index, and will skip over one array element every time splice is used.
When using splice in a loop to delete array elements I use a for loop with negative iteration. When the array slips down, the for loop will not skip over it because it drops down to the next (negative) iteration:
for ( let i = array.length - 1; i >= 0; i-- ){
// do destructive stuff with splice
}
=> start at the end of the array (don't forget to -1, since arrays are zero-based); continue while i >= 0; and decrement i at each iteration.

Related

Performing data manipulations on large records causes undefined behavior [duplicate]

I ran across this strange side effect of array.splice, and distilled the code down to the minimum necessary to recreate. Yes, much of this could be done on one line with array.filter, but I'm interested in whether I've made a mistake or if something else is going on.
var array = [];
for (var i = 0; i < 10; i++) {
array.push({
value: i
});
}
array.forEach(function(item, index, intArray) {
if (item.value % 2 == 1) {
item.odd = true;
} else {
item.odd = false;
}
if (item.odd) {
console.log("Removing " + item.value);
intArray.splice(index, 1);
}
});
console.log(array);
Running this javascript results in the odd elements being removed as expected, but it also removed the item.odd values for items 2, 4, 6, and 8. Removing the intArray.splice line brings back the odd array elements, but it also brings back the item.odd values for all elements.
I've tested this in FF and Chrome. The behavior persists even if only the item is passed into the callback, with the index calculated via array.indexOf, and referencing the array from outside the loop.
I think that when you splice the array at every odd number, the forEach ends up skipping over the next item, which is an even number. So those items don't get modified at all.
var array = [];
for (var i = 0; i < 10; i++) {
array.push({
value: i
});
}
array.forEach(function(item, index, intArray) {
console.log(item); // only prints out 0, 1, 3, 5, 7, 9
if (item.value % 2 == 1) {
item.odd = true;
} else {
item.odd = false;
}
if (item.odd) {
console.log("Removing " + item.value);
intArray.splice(index, 1);
}
});
console.log(array);
In other words, forEach only visits each index once. So say it gets to item 1, which is at index 1. It deletes item 1. Item 2 is now at index 1. But index 1 has already been visited, so it moves on to the item at index 2, which is now item 3.
Since splice is destructive and forEach is not 'live' [it is not automatically updated], the iteration after splice( index, 1 ) will skip over the "old index+1" (the next iteration-index) because it has 'dropped' down to the new index (where the old array element used to be). Using splice to delete array elements (no matter how many elements are deleted) will always create this unexpected 'slippage' from the old index to the new index, and will skip over one array element every time splice is used.
When using splice in a loop to delete array elements I use a for loop with negative iteration. When the array slips down, the for loop will not skip over it because it drops down to the next (negative) iteration:
for ( let i = array.length - 1; i >= 0; i-- ){
// do destructive stuff with splice
}
=> start at the end of the array (don't forget to -1, since arrays are zero-based); continue while i >= 0; and decrement i at each iteration.

Have integer added to end of array if larger than rest of integers in array

My function goes through an array to find the lowest index it should be inserted into. I am assuming the array is always sorted. It works unless the integer is larger than the rest of the array integers. At this point, it needs to be added to the end of the array.
I tried to use if else statements and have the number appended with a push but it just goes into a never-ending loop since I will always stay less than arr.length. I tried adding a break after the push inside the else but then, it always appends without inserting into the correct position if there is a place inside the array for it already.
function lowestIndexInsert(num,arr){
for (i = 0; i<arr.length; i++){
if (arr[i]>num){
arr[i]=num;
}
else {
arr.push(num);
break;
}
}
return arr.indexOf(num);
}
lowestIndexInsert(15,[8,25,33,52,70]);// should return 1
lowestIndexInsert(80,[8,25,33,52,70]);// should return 5
You can use splice to insert an element to the array and then instantly break. Once this is done you can catch the final case where i = length and hasn't been inserted yet. If you use 3 arguments such as: .splice(start, deleteCount, insertMe) the function will insert the item at the specific index and delete none. With this you can do:
function lowestIndexInsert(num,arr){
for (i = 0; i<arr.length; i++){
if (arr[i]>num){
arr.splice(i, 0, num);
// Break here to stop the loop after inserting
break;
}
// Perform a final check to see if the item was inserted.
if(i == (arr.length - 1)){
arr.push(num);
}
}
return arr.indexOf(num);
}
lowestIndexInsert(15,[8,25,33,52,70]);// should return 2
lowestIndexInsert(80,[8,25,33,52,70]);// should return 5
If you want to iterate as many times as the original array was long, you have to remember what that length was. That's what variable are for.
Also, you don't know if you need to add to the array until you have finished searching it.
You're pushing inside the loop, so you'll get your new value pushed once for every single number in the array that's greater than the value. That's not what you want.
You can greatly simplify this code, and avoid manual loops altogether:
function lowestIndexInsert(num,arr) {
let index = arr.length;
arr.some((value, i) => {
if (value > num) {
index = i;
return true;
}
return false;
});
arr[index] = num;
}

removing a duplicate number in a numbers array

I need a function to trim a specific number to only 1 repetition in a numbers array.
i can only use .pop, .push .length commands.
If I have array i.e [ 5,4,6,6,8,4,6,6,3,3,6,5,4,8,6,6] I need to trim the duplicates of the the digit 6 to show only one time, so the result would be -
[5,4,6,8,4,6,3,3,6,5,4,8,6].
using only one array.
I have tried to go thru the array with a for loop, and if i find a 6 that comes after another 6 i tried to move all the other elements one step back each time i find a duplicated 6 , array[i] = array [i+1]
I tried looping in a for inside a loop, no luck.
You can loop the array and re-assign values in place, by taking care about whether the current value and the next value are effectively the same and the next one is the targeted one.
Other than that, you need to also handle whether you're at the end of array.
Further explanations can directly be found in the snippet below.
As a final side note, the expected output should rather be:
[5,4,6,8,4,6,3,3,6,5,4,8,6]
instead of
[5,4,6,8,4,6,3,3,6,5,3,8,6]
since slightly before the last 8, in your input, you have a 4, not a 3.
function trimSpecificNumber(arr, target) {
// Define a tracker that will update the array in-place.
var _track = 0;
// Loop over all the elements in the array.
for (var i = 0; i < arr.length; i++) {
// acquire the current and the next value.
var _curr = arr[i], _next = arr[i+1];
// If there is no next value, assign the last element of the array.
if (!_next) arr[_track++] = _curr;
else {
// Otherwise, if the current element is not the same of the next one AND the next one actually isn't the searched needle, assign the current index to the current value (in place).
if (_next === _curr && _next === target) {
// Silence is golden, skip this one.
}
else {
arr[_track++] = _curr;
}
}
}
// Finally, assign the new length of the array, so that next elements will be truncated.
arr.length = _track;
}
var needle = [5,4,6,6,8,4,6,6,3,3,6,5,4,8,6,6];
trimSpecificNumber(needle, 6);
console.log(needle);
In your case iterate the array with a for loop, and if the current item is not
the item to dedupe, or is not identical to the previous, add the item to the current counter, and increment the counter. When the loop ends, change the length of the array to the length of the counter.
function removeSpecificDuplicate(arr, item) {
let counter = 0;
for(let i = 0; i < arr.length; i++) {
if(arr[i] !== item || arr[i] !== arr[i-1]) arr[counter++] = arr[i];
}
arr.length = counter;
}
const arr = [5,4,6,6,8,4,6,6,3,3,6,5,4,8,6,6]
removeSpecificDuplicate(arr, 6);
console.log(arr)

Javascript: Unshift() is causing an infinite loop but can't see why

I am trying to make a function that takes an array and returns the array with the even numbers before the odd numbers. I figured I'd iterate through the array and check the divisibility of each number with modulo. If it's an even number, I unshift that number to the front of the array and keep going.
I've removed unshift to log that it iterates through so I know the issue isn't with my loop.
/// A = [3,5,6,3,4,2,1]
var sortArrayByParity = function(A) {
for (var x =0; x< A.length;x++) {
if (A[x] % 2 == 0) {
A.unshift(A[x])
}
}
return A;
};
Maximum stack error is occuring.
Because unshift adds an element to the array, thereby increasing its length. Furthermore, once the unshift adds an element to the beginning of the array, the next A[x] is the same as the previous one, as all elements get moved to the right, including x. So the loop never finishes, because the index x never reaches the ever increasing length.
If you need to add elements to an array through which you are iterating with a loop, it's better to do it with push in a loop that counts downwards from length to 0. That way, the loop will never iterate through added elements, and the elements that it will iterate through will not move.
When you unshift an array an element is added to the start of that array and increase the length.
Lets try
let arr = [1];
console.log('arr = ',arr, 'length =', arr.length);
arr.unshift(0);
console.log('arr = ',arr, 'length =', arr.length);
As a result when you find a even number you unshift it to the front but it will also be available in x + 1 position. and as you iterate you will find the same even number in next iteration.
correct implementation would be.
/// A = [3,5,6,3,4,2,1]
var sortArrayByParity = function(A) {
for (var x =0; x< A.length;x++) {
if (A[x] % 2 == 0) {
A.unshift(A[x]);
A.splice(x+1, 1);
}
}
return A;
};
console.log(sortArrayByParity([3,5,6,3,4,2,1]));
You should use both unshift and splice.

Delete from array in javascript

3 hours ago, I asked a question in SO , about deleting a part of an object, so I linked this question to it:
delete a part of object in javascript
but now another problem occurred when I deleted from that array.
I use that object to populate a FlexiGrid. but when I delete an item from that object by following code, instead of delete that item , it sets to undefined :( and flexigrid did not accept it for input data.
for (var i = 0; i < Roomdata.length; i++) {
if(Roomdata[i].id = X) {
delete Roomdata[i];
break;
}
}
For example, imagine I have 3 items in Roomdata like this :
{item1, item2, item3}
When I call this code to delete item2 , Roomdata object looks like this :
{item1, undefined, item3}
and this is a bad format to be accepted by flexigrid as input data
Is there any solution ?
Thanks every body and sorry about my bad syntax (I am new in English)
regards , Foroughi
Walk through the array in reverse order, and use .splice to remove the element.
You have to walk in the reverse order, because otherwise you end up skipping elements See below.
for (var i = Roomdata.length-1; i >= 0; i--) {
if (Roomdata[i].id == X) {
Roomdata.splice(i, 1);
break;
}
}
What happens if you don't walk in the reverse order:
// This happens in a for(;;) loop:
// Variable init:
var array = [1, 2, 3];
var i = 0;
array.splice(i, 1); // array = [2, 3] array.length = 2
// i < 2, so continue
i++; // i = 1
array.splice(i, 1); // i=1, so removes item at place 1: array = [2]
// i < 1 is false, so stop.
// array = [2]. You have skipped one element.
What you have is an Array. You should use the splice() method to remove an element from an array, not by deleteing the element.
for (var i = 0; i < Roomdata.length; i++) {
if(Roomdata[i].id = X) {
Roomdata.splice(i, 1);
break;
}
}
Using splice in spite of delete.
Roomdata.splice(i, 0);
splice attribute removes blank string elements, undefined references, NULLs and FALSEs.
it will solve your problem
To remove all of the elements of an array matching a particular value, you can do this:
// Remove all elements in arr[] matching val
for (let i = 0; i < arr.length; i++) {
if (arr[i] === val) {
arr.splice(i--, 1); // Remove arr[i] and adjust the loop index
}
}
Note that this is a forward scan of the array. The decrement of the loop index is necessary so that the loop does not skip the next element after an element is removed.

Categories

Resources