Unsetting javascript array in a loop? - javascript

I have the following code:
$.each(current, function(index, value) {
thisArray = JSON.parse(value);
if (thisArray.ttl + thisArray.now > now()) {
banned.push(thisArray.foodID);
}
else{
current.splice(index,1);
}
});
There's a problem with the following line:
current.splice(index,1);
What happens is that it (probably) unsets the first case which fits the else condition and then when it has to happen again, the keys don't match anymore and it cannot unset anything else. It works once, but not in the following iterations.
Is there a fix for this?

You can use a regular for loop, and loop backwards:
for(var i=current.length-1; i>= 0; i--) {
var thisArray = JSON.parse(current[i]);
if (thisArray.ttl + thisArray.now > now()) {
banned.push(thisArray.foodID);
} else {
current.splice(i, 1);
}
});
Also it seems thisArray is actually an object, not an array...

you can use regular for loop, but after splice, decrease index by 1
for(var i=0; i<current.length; i++){
current.splice(i, 1); i--;
}

You should pay attention if you mutate an array (by moving objects) while it's being traversed because some element may get skipped like in your case.
When you call current.splice(index, 1) element index will be removed and element index+1 will take its place. But then index will be incremented, thus skipping one element.
A better solution is IMO the read-write approach. You keep one index as the "read pointer" and you always increment it, and another index (the "write pointer") that is incremented only when you decide an element should be kept in the array:
var wp = 0; // The "write pointer"
for (var rp=0; rp<a.length; rp++) {
if (... i want to keep element a[rp] ...) {
a[wp++] = a[rp];
}
}
a.splice(wp); // Remove all elements after wp
This is a o(N) operation and will move each element at most once. Other approaches like starting from the end and using the same splice(i, 1) approach instead will keep moving all elements after i each time an element needs to be removed.

Related

leetcode.com - Remove Element - why doesn't i++ work?

I am working on an algorithm from leetcode.
here is the description:
Given an array and a value, remove all instances of that value in
place and return the new length. Do not allocate extra space for
another array, you must do this in place with constant memory. The
order of elements can be changed. It doesn't matter what you leave
beyond the new length. Example: Given input array nums = [3,2,2,3],
val = 3 Your function should return length = 2, with the first two
elements of nums being 2.
My question is based on the following code:
var removeElement = function(nums, val) {
var count = 0;
for(i=0; i<nums.length; i++){
if(nums[i] == val) {
nums.splice(i,1);
i--;
}
}
};
My question is, why does decrementing with i-- work but incrementing with i++ does not work?
A situation in which my answer is not accepted is when the input array is [3, 3], and the value is 3.
Splice removes the element at i from the array, so you need to stay at the current position, if you want to go on iterating normally. As theres an i++ in the for loop, it would normally go one position forward. So you need to go one step backward before to stay at the same position.
nums=[3,2,2,3]
val=3;
//first iteration
i=0
current=3//===val
nums.splice(0,1);//nums=[2,2,3]
i--;//i=-1
//next iteration
i++;//i=0
current=2;
//next iteration
i++;//i=1
current=2;
//next iteration
i++;//i=2
current=3;
nums.splice(2,1);//nums=[2,2]
i--;//i=1
//next iteration
i++;//i=2 === nums.length => break
In the case [3,2,2,3] it does not matter if you use i++ instead of i-- as it will jump over indexes 1 and 2 wich are not relevant. So this buggy code works by accident...

Returning duplicates in multidimensional Javascript array

I have searched high and low, not only on StackOverflow, but many other places elsewhere on the web. I've tried what seems like everything, but something is fundamentally flawed with my logic. I apologize for introducing another "Duplicates in Array" question, but I am stuck and nothing seems to be working as expected.
Anyway, I have a multi-dimensional JavaScript array, only 2 levels deep.
var array = [[Part #, Description, Qty:],
[Part #, Description, Qty:],
[Part #, Description, Qty:]]; //etc
What I need to do is create a function that searches array and returns any duplicate "Part #" lines. When they are returned, I would like to have the entire inner array returned, complete with description and qty.
The trick with this is that the Part #'s that would qualify as 'duplicate' would end differently (specifically the last 4 characters), so using String.prototype.substr makes sense (to me).
I know there are duplicates in the array in the way that I am looking for, so I know that if I had the solution, it would return those Part #'s.
Here is what I have tried so far that gets me the closest to a solution:
function findDuplicateResults(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i][0].substr(0,5) === arr[++i][0].substr(0,5)) {
result.push(arr[i]);
}
}
return console.log(result);
}
My thinking is that if the element in the array(with substr(0,5) is equal to the next one in line, push that to the result array. I would need the other duplicate in there too. The point of the code is to show only dupes with substr(0,5).
I have tried using Higher Order Functions such as map, forEach, reduce, and filter (filter being the one that boggles my mind as to why it doesn't do what I want), but I have only been able to return [] or the entire array that way. The logic that I use for said Higher Order Functions remains the same (which is probably the problem here).
I am expecting that my if condition is where the most of the problem is. Any pointers or solutions are greatly appreciated.
There is a mistake in your code. When you use ++i, you are changing the value of i, so it is going to skip one item in the next iteration.
Regarding the logic, you are only comparing one item to the next item, when you should really be comparing each item to all items:
function findDuplicateResults(arr) {
var result = [];
for (var i = 0; i <= arr.length - 1; i++) {
for (var k = 0; k <= arr.length - 1; k++) {
if (i !== k && arr[i][0].substr(0,5) === arr[k][0].substr(0,5)) {
result.push(arr[i]);
}
}
}
return result;
}
Although, the 'substr' could be dropped, and 'for' loop could be replaced by a higher order function:
function findDuplicateResults(arr) {
return arr.filter(function(item1){
return arr.filter(function(item2){
return item1[0] === item2[0];
}).length > 1;
});
}

Array.splice inside a for loop causing errors

im using Angular ng-repeat to display $scope.currentMessageList array
i also have a remove button bound via ng-click to the remove function, which looks like this:
remove: function () {
for (var i = 0; i < 25; i++) {
var index = i;
$scope.currentMessageList.splice(index, 1);
console.log($scope.currentMessageList.length + 'left');
}
}
There are 25 items in this collection, when I call the remove function,
I get this output:
24left
23left
22left
21left
20left
19left
18left
17left
16left
15left
14left
13left
13times X 12left
If I replace the for loop with angular.forEach
I get "12 left" only once, still it doesn`t remove more than 13 items
Ive also tried to use angular.apply, than I get digest already in progress error
Performing a splice while iterating through an array is a bad idea.
You should replace
for( var i = 0; i < 25; i++ ){
var index = i;
$scope.currentMessageList.splice( index, 1 );
console.log($scope.currentMessageList.length + 'left');
}
by a simple
$scope.currentMessageList.splice( 0, 25 );
You're removing items while walking the array.
When you reach half of the array you've already removed half the items, so you won't remove anything else.
You can fix this either by always removing the first item or by iterating backwards from 24 towards 0.
When you remove array items in loop, indexes get shifted too. As the result you can iterate over only the half of them. This is the issue here.
If you want to clear 25 first items you can remove them with Array.prototype.shift method instead. In this case it will remove the first element of the array 25 times, giving you expected result:
remove: function () {
for (var i = 0; i < 25; i++) {
currentMessageList.shift();
}
}
When you splice the array.. the length of the array changes.
When you are trying to remove the element at index 13, the length is 12 only.
Hence it is not removed.
Instead of splice, try shift();
You don't need to iterate over your array to remove all the items. Just do this:
remove : function(){
$scope.currentMessageList = [];
}
Check out this answer also. There are others way to achieve this that are also valid.

Using Jquery Splice

I am trying to remove an item from an Array using Splice method.
arrayFinalChartData =[{"id":"rootDiv","Project":"My Project","parentid":"origin"},{"1":"2","id":"e21c586d-654f-4308-8636-103e19c4d0bb","parentid":"rootDiv"},{"3":"4","id":"deca843f-9a72-46d8-aa85-f5c3c1a1cd02","parentid":"e21c586d-654f-4308-8636-103e19c4d0bb"},{"5":"6","id":"b8d2598a-2384-407a-e2c2-8ae56c3e47a2","parentid":"deca843f-9a72-46d8-aa85-f5c3c1a1cd02"}];
ajax_delete_id = "e21c586d-654f-4308-8636-103e19c4d0bb,deca843f-9a72-46d8-aa85-f5c3c1a1cd02,b8d2598a-2384-407a-e2c2-8ae56c3e47a2";
$.each(arrayFinalChartData, function (idx, obj) {
var myObj = obj.id;
if (ajax_delete_id.indexOf(myObj) >= 0) {
var vararrayFinalChartDataOne = arrayFinalChartData.splice(idx, 1);
}
});
console.log(arrayFinalChartData);
Please check at : http://jsbin.com/deqix/3/edit
Note : It does not complete the "last leg " of the loop. That means if I have 4 items, then it successfully executes 3 items. Same goes for 6,7...items.
I need to "REMOVE" few items and "PRESERVE THE BALANCE" in an array.
You can use for loop instead of $.each function:
alert('length before delete ' + arrayFinalChartData.length);
for (var i = arrayFinalChartData.length - 1; i >= 0; i--) {
id = arrayFinalChartData[i].id;
if(ajax_delete_id.indexOf(id) > -1){
arrayFinalChartData.splice(i, 1);
}
};
alert('length after delete ' + arrayFinalChartData.length);
Demo.
Complete edit :
After researching a bit, and console.logging a lot, I finally found where the issue is coming from ! It's actually quite simple, but very sneaky !
Theoretical explanation :
You are calling the splice function with the variable "idx", but remember that the splice function remaps / reindexes your array ! So, each time you splice the array, its size decreases by one while you're still inside the $.each function. The splice messes up jQuery indexation of your array, because jQuery doesn't know that you're removing elements from it !
Iterative explanation :
$.each function starts, thinking your array has 4 elements, which is true, but only for a while. First loop, idx = 0, no splice. Second loop, idx = 1, splice, which means that your array has now 3 elements left in it, reindexed from 0 to 2. Third loop, idx = 2, splice, which means your array has now two elements left in it, but $.each continues ! Fourth loop, idx = 3, js crashes, because "arrayFinalChartData[3]" is undefined, since it was moved back each time the array got spliced.
To solve your problem, you need to use a for loop and to start analyzing the array from the end, not from the beginning, hence each time you splice it, your index will decrease as well. And if you want to preserve balance, just push the removed items into an array. Remember that you are analyzing the array from the end, so items pushed into the "removedItems" array will be in reverse order. Just like this :
var removedItems = new Array();
for (var i = arrayFinalChartData.length - 1; i >= 0; i--) {
var myObj = arrayFinalChartData[i].id;
if (ajax_delete_id.indexOf(myObj) >= 0) {
removedItems.push(arrayFinalChartData.splice(i, 1)[0]);
}
}
console.log(arrayFinalChartData);
console.log(removedItems);
And a working demo (inspect the page, observe the console and click "Run") :
http://jsfiddle.net/3mL6C/3/
I will not give credit to myself for this answer, thanks to another similar thread for giving me a hint.
Your problem here is that when the $.each is set up, it's expecting a certain length of object, which you are then changing. You need to loop in a way that respects the dynamic length of the object.
var i = 0;
while (i < arrayFinalChartData.length) {
var myObj = arrayFinalChartData[i].id;
if (ajax_delete_id.indexOf(myObj) >= 0) {
// current item is in the list, so remove it but KEEP THE SAME INDEX
arrayFinalChartData.splice(i, 1);
} else {
// item NOT in list, so MOVE TO NEXT INDEX
i++
}
}
console.log(arrayFinalChartData);
Demo

Why is the JS Array skipping certain entries when running the splice method within a loop?

I have an array:
var productIds = new Array("1","6","7","Product-Total","ccFirst","ccLast","email","ccExpMonth","ccExpYear","billingAddress","billingCity","billingState","billingZip");
I want to delete a value if it is not a number:
for(var i=0; i<productIds.length; i++){
if(isNaN(Number(productIds[i]))) {
productIds.splice(i,1);
}
}
It seems the splice method is affecting the positions of the values.
I found this solution(Looping through array and removing items, without breaking for loop) which is what I think I need, but I can't figure out how to implement their answers for my code.
How can I fix my problem?
btw, I posted a more detailed jsFiddle: http://jsfiddle.net/fte3m/2/
When you delete the entry at index i, you need to subtract 1 from i so the entry that was moved down is not skipped.
for(var i=0; i<productIds.length; i++){
if(isNaN(productIds[i])) {
productIds.splice(i--,1); // <-- Decrement i
}
}
As RobG points out in his comment, it's easier to simply process the array in the other direction:
for(var i=productIds.length - 1; i>=0; i--){
if(isNaN(productIds[i])) {
productIds.splice(i,1);
}
}
Alternatively, if you don't mind reassigning productIds to be a new array object and you are running JS 1.6 or later:
productIds = productIds.filter(function (id) {
return !isNaN(id);
});
(Note that in the above, I just use isNaN(value) rather than isNaN(Number(value)). Whenever Number(value) would return NaN, isNaN(value) will return true, and vice versa. Note also that neither approach will filter out a null id, since Number(null)==0 and isNaN(null)==false. If you want to exclude null entries from the result, you will need to test for that separately.)

Categories