Splice dosen't work in forEach loop [duplicate] - javascript

I have this code which is supposed to iterate over each item in an array, removing items based on some condition:
//iterate over all items in an array
//if the item is "b", remove it.
var array = ["a", "b", "c"];
array.forEach(function(item) {
if(item === "b") {
array.splice(array.indexOf(item), 1);
}
console.log(item);
});
Desired output:
a
b
c
Actual output:
a
b
Obviously the native forEach method doesn't check after each iteration whether the item has been deleted, so if it is then the next item is skipped. Is there a better way of doing this, aside from overriding the forEach method or implementing my own class to use instead of an array?
Edit - further to my comment, I suppose the solution is to just use a standard for loop. Feel free to answer if you have a better way.

Lets see why JavaScript behaves like this. According to the ECMAScript standard specification for Array.prototype.forEach,
when you delete an element at index 1, the element at index 2 becomes the element at index 1 and index 2 doesn't exist for that object.
Now, JavaScript looks for element 2 in the object, which is not found, so it skips the function call.
That is why you see only a and b.
The actual way to do this, is to use Array.prototype.filter
var array = ["a", "b", "c"];
array = array.filter(function(currentChar) {
console.log(currentChar); // a, b, c on separate lines
return currentChar !== "b";
});
console.log(array); // [ 'a', 'c' ]

One possibility would be to use the array.slice(0) function, which creates a copy (clone) of the array and thus the iteration is separated from the deletion.
Then the only change to the original approach using array.forEach would be to change it to array.slice(0).forEach and it will work:
array.slice(0).forEach(function(item) {
if(item === "b") {
array.splice(array.indexOf(item), 1);
}
alert(item)
});
After the forEach, the array will contain only a and c.
A jsFiddle demo can be found here.

Using Array.prototype.filter as in thefourtheye's answer is a good way to go, but this could also be done with a while loop. E.g.:
const array = ["a", "b", "c"];
let i = 0;
while (i < array.length) {
const item = array[i];
if (item === "b") {
array.splice(i, 1);
} else {
i += 1;
}
console.log(item);
}

Another possibility would be to use the array.reduceRight function to avoid the skip:
//iterate over all items in an array from right to left
//if the item is "b", remove it.
const array = ["a", "b", "c"];
array.reduceRight((_, item, i) => {
if(item === "b") {
array.splice(i, 1);
}
}, null);
console.log(array);
After the reduceRight, the array will contain only a and c.

Related

Remove a user id from array of ids [duplicate]

I want to remove an element in an array with multiple occurrences with a function.
var array=["hello","hello","world",1,"world"];
function removeItem(item){
for(i in array){
if(array[i]==item) array.splice(i,1);
}
}
removeItem("world");
//Return hello,hello,1
removeItem("hello");
//Return hello,world,1,world
This loop doesn't remove the element when it repeats twice in sequence, only removes one of them.
Why?
You have a built in function called filter that filters an array based on a predicate (a condition).
It doesn't alter the original array but returns a new filtered one.
var array=["hello","hello","world",1,"world"];
var filtered = array.filter(function(element) {
return element !== "hello";
}); // filtered contains no occurrences of hello
You can extract it to a function:
function without(array, what){
return array.filter(function(element){
return element !== what;
});
}
However, the original filter seems expressive enough.
Here is a link to its documentation
Your original function has a few issues:
It iterates the array using a for... in loop which has no guarantee on the iteration order. Also, don't use it to iterate through arrays - prefer a normal for... loop or a .forEach
You're iterating an array with an off-by-one error so you're skipping on the next item since you're both removing the element and progressing the array.
That is because the for-loop goes to the next item after the occurrence is deleted, thereby skipping the item directly after that one.
For example, lets assume item1 needs to be deleted in this array (note that <- is the index of the loop):
item1 (<-), item2, item3
after deleting:
item2 (<-), item3
and after index is updated (as the loop was finished)
item2, item3 (<-)
So you can see item2 is skipped and thus not checked!
Therefore you'd need to compensate for this by manually reducing the index by 1, as shown here:
function removeItem(item){
for(var i = 0; i < array.length; i++){
if(array[i]==item) {
array.splice(i,1);
i--; // Prevent skipping an item
}
}
}
Instead of using this for-loop, you can use more 'modern' methods to filter out unwanted items as shown in the other answer by Benjamin.
None of these answers are very optimal. The accepted answer with the filter will result in a new instance of an array. The answer with the second most votes, the for loop that takes a step back on every splice, is unnecessarily complex.
If you want to do the for loop loop approach, just count backward down to 0.
for (var i = array.length - 0; i >= 0; i--) {
if (array[i] === item) {
array.splice(i, 1);
}
}
However, I've used a surprisingly fast method with a while loop and indexOf:
var itemIndex = 0;
while ((itemIndex = valuesArray.indexOf(findItem, itemIndex)) > -1) {
valuesArray.splice(itemIndex, 1);
}
What makes this method not repetitive is that after the any removal, the next search will start at the index of the next element after the removed item. That's because you can pass a starting index into indexOf as the second parameter.
In a jsPerf test case comparing the two above methods and the accepted filter method, the indexOf routinely finished first on Firefox and Chrome, and was second on IE. The filter method was always slower by a wide margin.
Conclusion: Either reverse for loop are a while with indexOf are currently the best methods I can find to remove multiple instances of the same element from an array. Using filter creates a new array and is slower so I would avoid that.
You can use loadash or underscore js in this case
if arr is an array you can remove duplicates by:
var arr = [2,3,4,4,5,5];
arr = _.uniq(arr);
Try to run your code "manually" -
The "hello" are following each other. you remove the first, your array shrinks in one item, and now the index you have follow the next item.
removing "hello""
Start Loop. i=0, array=["hello","hello","world",1,"world"] i is pointing to "hello"
remove first item, i=0 array=["hello","world",1,"world"]
next loop, i=1, array=["hello","world",1,"world"]. second "hello" will not be removed.
Lets look at "world" =
i=2, is pointing to "world" (remove). on next loop the array is:
["hello","hello",1,"world"] and i=3. here went the second "world".
what do you wish to happen? do you want to remove all instances of the item? or only the first one? for first case, the remove should be in
while (array[i] == item) array.splice(i,1);
for second case - return as soon as you had removed item.
Create a set given an array, the original array is unmodified
Demo on Fiddle
var array=["hello","hello","world",1,"world"];
function removeDups(items) {
var i,
setObj = {},
setArray = [];
for (i = 0; i < items.length; i += 1) {
if (!setObj.hasOwnProperty(items[i])) {
setArray.push(items[i]);
setObj[items[i]] = true;
}
}
return setArray;
}
console.log(removeDups(array)); // ["hello", "world", 1]
I must say that my approach does not make use of splice feature and you need another array for this solution as well.
First of all, I guess your way of looping an array is not the right. You are using for in loops which are for objects, not arrays. You'd better use $.each in case you are using jQuery or Array.prototype.forEach if you are using vanila Javascript.
Second, why not creating a new empty array, looping through it and adding only the unique elements to the new array, like this:
FIRST APPROACH (jQuery):
var newArray = [];
$.each(array, function(i, element) {
if ($.inArray(element, newArray) === -1) {
newArray.push(region);
}
});
SECOND APPROACH (Vanila Javascript):
var newArray = [];
array.forEach(function(i, element) {
if (newArray.indexOf(element) === -1) {
newArray.push(region);
}
});
I needed a slight variation of this, the ability to remove 'n' occurrences of an item from an array, so I modified #Veger's answer as:
function removeArrayItemNTimes(arr,toRemove,times){
times = times || 10;
for(var i = 0; i < arr.length; i++){
if(arr[i]==toRemove) {
arr.splice(i,1);
i--; // Prevent skipping an item
times--;
if (times<=0) break;
}
}
return arr;
}
An alternate approach would be to sort the array and then playing around with the indexes of the values.
function(arr) {
var sortedArray = arr.sort();
//In case of numbers, you can use arr.sort(function(a,b) {return a - b;})
for (var i = 0; sortedArray.length; i++) {
if (sortedArray.indexOf(sortedArray[i]) === sortedArray.lastIndexOf(sortedArray[i]))
continue;
else
sortedArray.splice(sortedArray.indexOf(sortedArray[i]), (sortedArray.lastIndexOf(sortedArray[i]) - sortedArray.indexOf(sortedArray[i])));
}
}
You can use the following piece of code to remove multiple occurrences of value val in array arr.
while(arr.indexOf(val)!=-1){
arr.splice(arr.indexOf(val), 1);
}
I thinks this code much simpler to understand and no need to pass manually each element that what we want to remove
ES6 syntax makes our life so simpler, try it out
const removeOccurences = (array)=>{
const newArray= array.filter((e, i ,ar) => !(array.filter((e, i ,ar)=> i !== ar.indexOf(e)).includes(e)))
console.log(newArray) // output [1]
}
removeOccurences(["hello","hello","world",1,"world"])

Flatten nested arrays using recursion (and without using loops)

My logic for the problem, using the below as the input.
var input = [['A','B'],1,2,3,['C','D']]
Check first element to see if is an Array or not using Array.isArray(input)
If first element is array, call function, first element ['A,'B'] as argument.
The first element of the nested array is 'A' which is not an array, so push this element into a result array, and shift this element out. Repeat the function call.
When trying to flatten nested arrays using recursion, my input variable to the function keeps getting reassigned, preventing me from calling the function again using the original array. How do I prevent the original input variable from getting reassigned?
I understand this is not the complete solution, however I am stuck at when I shift the first element out of the nested array.
I've gone through step by step with this function, but there must be something I'm missing, another set of eyes would help greatly.
I've also been using my chrome developer tool, set breakpoints to monitor the function step by step.
//Defining original input variable
var input = [['A','B'],1,2,3,['C','D']]
function flat(array){
var result = []
var firstElement = array[0]
//CHECK IF FIRST ELEMENT IS ARRAY OR NOT
if(Array.isArray(firstElement)){
return flat(firstElement)
}
//IF ELEMENT NOT ARRAY, PUSH ELEMENT TO RESULT
else{result.push(firstElement)
array.shift() //removing child element
return (flat(array)) //call function on same array
}
if(array.length===0){return result}
}
First iteration:
firstElement = ['A','B'], Array.isArray(firstElement) would be true, hence call flat(firstElement)
Second Iteration:
firstElement = 'A', Array.isArray(firstElement) is false, so we
1. jump down to push this element into result
2. remove 'A' by using array.shift()
3. Call flat(array), where array is now ['B']
Third Iteration:
firstElement = 'B', Array.isArray(firstElement) is false
1. jump down to push this element into result, result is now only ['B'] since I've reset the result when I recalled the function.
2. remove 'B' by using array.shift(), array is now empty, ->[ ]
3. How can I step out, and use flat() on the original input array?
Your code doesn't consider the following elements if the first element is an array. The solution below uses array.concat(...) to combine both the result of the recursion (going down the tree), but also to combine the results of processing the rest of the list (in the same level). Visualizing the problem as a tree, often helps with recursions IMO:
[] 1 2 3 []
| |
A [] C D
|
B C
So perhaps it is more clear here, that we must both concat the result of the recursion and the result of taking a "step" to the right (recursion again) which would otherwise be a loop iterating the array.
var input = [['A',['B', 'C']],1,2,3,['C','D']]
function flat(array) {
var result = []
if (array.length == 0) return result;
if (Array.isArray(array[0])) {
result = result.concat(flat(array[0])); // Step down
} else {
result.push(array[0]);
}
result = result.concat(flat(array.slice(1))) // Step right
return result;
}
console.log(flat(input));
// ["A", "B", "C", 1, 2, 3, "C", "D"]
This is somewhat analogous to a version with loops:
function flat(array) {
var result = []
for (var i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
result = result.concat(flat(array[i]));
} else {
result.push(array[i]);
}
}
return result;
}
EDIT: For debugging purposes, you can track the depth to help get an overview of what happens where:
var input = [['A',['B', 'C']],1,2,3,['C','D']]
function flat(array, depth) {
var result = []
if (array.length == 0) return result;
if (Array.isArray(array[0])) {
result = result.concat(flat(array[0], depth + 1));
} else {
result.push(array[0]);
}
var res1 = flat(array.slice(1), depth);
console.log("Depth: " + depth + " | Concatenating: [" + result + "] with: [" + res1 + "]");
result = result.concat(res1)
return result;
}
console.log(flat(input, 0));
If you want to avoid loops, and I'm considering concating/spreading arrays as loops, you need to pass the result array to your function.
const input = [['A', 'B'], 1, 2, 3, ['C', 'D']]
// Start the function with an empty result array.
function flat(array, result = []) {
if (!array.length)
return result
// Extract first element.
const first = array.shift()
// Call the function with the array element and result array.
if (Array.isArray(first))
flat(first, result)
// Or add the non array element.
else
result.push(first)
// Call the function with the rest of the array and result array.
flat(array, result)
return result
}
console.log(flat(input))
Here is my answer if you are using JavaScript
You can use the below one line code to flatten n level nested Array
let flattendArray = input.flat(Infinity);
Or use this approach using reduce and concat
function flatDeep(arr, d = 1) {
return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
: arr.slice();
};
Refer this link

Simplify code that "toggles" an array item

I use lodash to insert an item into an array if it's not there, and remove it if it exists, kind of "toggling".
My code looks like this:
var items = ['a', 'b', 'c'];
var itemToToggle = 'a';
if (_.includes(items, itemToToggle)) {
_.pull(items, itemToToggle)
}
else {
items.push(itemToToggle)
}
Which seems not perfect enough.
Can I simplify it to, ideally, have something like _.toggle(items, itemToToggle)?
Another way to do it would be to use lodash's xor
var items = ['a', 'b', 'c'];
var itemToToggle = 'a';
new_array = _.xor(items, [itemToToggle])
return new_array // ['b', 'c']
Which will add the item if it does not exist, and remove if it does.
It does this by comparing the two arrays (items and [itemToToggle]) and returning a new array that is a merge of the two arrays, minus duplicates.
Your code seems fine to me. The only thing, I can think of is using the length to see if an item was removed, and if not, add it:
function toggleValueInArr(arr, value) {
var originalLength = arr.length; // cache the original length
_.pull(arr, value).length === originalLength && arr.push(value); // check if the length is the same as the original - ie no item was not removed. If so, push it.
return arr;
}

Javascript - Do something when an element in two arrays are the same?

I found a solution to where I get returned an array of elements without duplicates:
Array1 = Array1.filter(function(val) {
return Array2.indexOf(val) == -1;
});
However, I want to modify this code just a little bit. Instead of being returned an array without duplicates, I want to do something when there is a duplicate. The problem is, I'm not sure how exactly this code works. The thing is I'm not sure how val gets set, or what it even is.
for (var i = 0; i < json.length; i++) {
var item = json[i];
// if json.indexOf(val?), do something
}
Read the docs for the Array filter method then. The val parameter of the callback will be passed the single array items, i.e. json[i] or item in your case:
for (var i = 0; i < json.length; i++) {
var item = json[i];
if (json.indexOf(item) >= 0) {
// do something
}
}
var newArray = array1.filter(function(v, i) {
return array1.indexOf(v) == i;
});
This will return only unique itesm from array1;
array1.filter(function(v, i) {
// write your code here ('v' is individual value and 'i' is its index)
// don't return any anything if you don't want unique array to be returned.
// 'array1.indexOf(v) == i' checks if current value is duplicate from previous any values.
// try putting console.log on values you don't understand like (console.log(v,i) for values of 'v' and 'i')
return array1.indexOf(v) == i;
});
and off-curse you can loop an array with for loop as
for(i in array1){
// where i is index of array1, to get current value use array1[i]
if(array2.indexOf(array1[i]) >= 0){
// do something
}
console.log(i);
}
val is set by Array.prototype.filter, which calls the callback function on each element in the array. Since you don't want to filter you can use Array.prototype.forEach instead, which also calls the callback function once for each element in the array:
Array1.forEach(
// This function is called once per element in Array1
function(val){
if(Array2.indexOf(val) != -1){ // Check if that element is also in Array2
// `val` is in both arrays,
// Do something with it
}
}
);
You can utilize some modern libraries... like underscorejs.
Intersection is what you're looking for i guess: http://underscorejs.org/#intersection
_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2]
So your code may be something like
if(_.insersection(arr1, arr2)){
//since [] array is Falsy in JS this will work as a charm
}
From MDN: indexOf
Returns the first index at which a given element can be found in the array, or -1 if it is not present.
From MDN: filter
Creates a new array with all elements that pass the test implemented by the provided function.
The first function works by returning true when an item from array1 isn't found in array2 (== -1). i.e.: Iterate through A and add anything not found in B.
So, to change to return only duplicates return true for anything that is found in both:
Array1 = Array1.filter(function(val) {
return Array2.indexOf(val) >= 0;
});
Array1 now contains only items with duplicates.

Get first element of a sparse JavaScript array

I have an array of objects in javascript. I use jquery.
How do i get the first element in the array? I cant use the array index - as I assign each elements index when I am adding the objects to the array. So the indexes arent 0, 1, 2 etc.
Just need to get the first element of the array?
If you don't use sequentially numbered elements, you'll have to loop through until you hit the first one:
var firstIndex = 0;
while (firstIndex < myarray.length && myarray[firstIndex] === undefined) {
firstIndex++;
}
if (firstIndex < myarray.length) {
var firstElement = myarray[firstIndex];
} else {
// no elements.
}
or some equivalently silly construction. This gets you the first item's index, which you might or might not care about it.
If this is something you need to do often, you should keep a lookaside reference to the current first valid index, so this becomes an O(1) operation instead of O(n) every time. If you're frequently needing to iterate through a truly sparse array, consider another data structure, like keeping an object alongside it that back-maps ordinal results to indexes, or something that fits your data.
The filter method works with sparse arrays.
var first = array.filter(x => true)[0];
Have you considered:
function getFirstIndex(array){
var result;
if(array instanceof Array){
for(var i in array){
result = i;
break;
}
} else {
return null;
}
return result;
}
?
And as a way to get the last element in the array:
function getLastIndex(array){
var result;
if(array instanceof Array){
result = array.push("");
array.pop;
}
} else {
return null;
}
return result;
}
Neither of these uses jquery.
Object.keys(array)[0] returns the index (in String form) of the first element in the sparse array.
var array = [];
array[2] = true;
array[5] = undefined;
var keys = Object.keys(array); // => ["2", "5"]
var first = Number(keys[0]); // => 2
var last = Number(keys[keys.length - 1]); // => 5
I was also facing a similar problem and was surprised that no one has considered the following:
var testArray = [];
testArray [1245]= 31;
testArray[2045] = 45;
for(index in testArray){
console.log(index+','+testArray[index])
}
The above will produce
1245,31
2045,45
If needed you could exist after the first iteration if all that was required but generally we need to know where in the array to begin.
This is a proposal with ES5 method with Array#some.
The code gets the first nonsparse element and the index. The iteration stops immediately with returning 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);
If you find yourself needing to do manipulation of arrays a lot, you might be interested in the Underscore library. It provides utility methods for manipulating arrays, for example compact:
var yourArray = [];
yourArray[10] = "foo";
var firstValue = _.compact(yourArray)[0];
However, it does sound like you are doing something strange when you are constructing your array. Perhaps Array.push would help you out?

Categories