Related
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.
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)
I have an array of objects. I am trying to find duplicated objects and then remove both instances of that object.
Right now I am using this method:
function checkForDups(data){
for(var i = 0; i < data.length; i++){
for(var j = i+1; j < data.length; j++){
if(data[j].number === data[i].number){
data.splice(j,1);
data.splice(i,1);
}
}
}
return data;
}
I believe problem is that it only checks for duplicates that have an index that greater than the current position it is checking. This means objects that are "behind" it in the array are not checked for duplication. Currently I run the array through this function a few times to get the desired results. However, this is obviously extremely inefficient. How could I achieve my desired results more efficiently?
I believe problem is that it only checks for duplicates that have an index that greater than the current position it is checking. This means objects that are "behind" it in the array are not checked for duplication.
No, that's a simply optimisation, made possible by the symmetry of the equality relation. By searching ahead and removing all duplicates in front of you, any current item can't be a duplicate of a previous one or it would have been already eliminated.
However, there are some things you did not take care of:
When splicing (removing) an item from the array, all subsequent ones are moved, and the array changes its length. To really examine all items in the array, you need decrease (or: not increase) the counter variable when removing, so that the new item which is now in the same spot as the removed one gets visited (the spot from which you just removed needs to get revisited).
You probably want to break the inner loop after having found a duplicate, otherwise you compare and remove totally different items.
You have not made clear what the algorithm should do when there are more than 2 duplicate items of the same sort in the array. Leave one when their number is odd? Thanks for your comment.
To remove all existing duplicates, you will need to continue the search, but must not remove the ith element immediately or you won't have anything to compare to furtheron - or you might even remove the ith item multiple times (see #2).
So this modification should fit:
function removeAllDups(data) {
// leaves only items in the array that appeared a single time
// removes everything whose .number can be found multiple times
for (var i = 0; i < data.length; i++) {
var found = false,
num = data[i].number;
for (var j = i+1; j < data.length; j++) {
if (data[j].number === num) {
found = true;
data.splice(j--, 1);
}
}
if (found) {
data.splice(i--, 1);
}
}
return data;
}
Here's an alternate implementation. This uses only two passes through the array, and removes all dupes in cases > 2: http://jsfiddle.net/nrabinowitz/1pdr780j/
function removeDupes(arr, test) {
test = test || function(a, b) { return a === b; };
function find(cache, element) {
return cache.some(test.bind(null, element));
}
var seen = [];
var dupes = [];
var len = arr.length;
var x;
var current;
// First pass - find dupes
for (x = 0; x < len; x++) {
current = arr[x];
if (find(seen, current)) {
dupes.push(current);
} else {
seen.push(current);
}
}
// Second pass: remove dupes. Reverse iteration saves headaches here
for (x = len - 1; x >= 0; x--) {
current = arr[x];
if (find(dupes, current)) {
arr.splice(x, 1);
}
}
}
This is an updated version that takes an optional test function to determine equality for dupe purposes; for the OP's case, the call would be
removeDupes(arr, function(a, b) {
return a.number == b.number;
});
Note that this assumes support for ES5 methods - Array#some, Function#bind. If you need to support older browsers, an ES5 shim or the Underscore library would fit the bill.
You could use underscore.js:
function checkForDups(data) {
return (
_.map(
_.filter(
_.pairs(
_.countBy(data)
), function(v) {return v[1] == 1}
), function(v) {return ~~v[0]}
)
)
}
Example
>> console.log(checkForDups([0,1,2,3,0,1,0,4]))
[2, 3, 4]
You can try it here.
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"])
I'm trying to loop through an array and delete and skip the elements until only one is existing. i've tried splicing but it messes up my loop because the element from arr[1] then becomes arr[0] etc.
Let's say there are 10 people. I'd like to remove person 1 then keep person 2 then remove person 3 and keep person 4. This pattern will go on until only one is left.
any kind of help will do.
Filter the falsy items:
var a=[1,2,"b",0,{},"",NaN,3,undefined,null,5];
var b=a.filter(Boolean); // [1,2,"b",{},3,5]
you should not change the collection during the iterating, not just JavaScript but all language, define a new array and add those ones you want to delete in it, and iterate that one later to delete from first one.
When you splice, just decrement your loop index.
There were lots of good suggestions, I'll post the code for the different options and you can decide which to use
Decrement index when splicing
http://jsfiddle.net/mendesjuan/aFvVh/
var undef;
var arr = [1,2, undef, 3, 4, undef];
for (var i=0; i < arr.length; i++) {
if ( arr[i] === undef ) {
arr.splice(i,1);
i--;
}
}
Loop backwards http://jsfiddle.net/mendesjuan/aFvVh/1/
var undef;
var arr = [1,2, undef, 3, 4, undef];
for (var i=arr.length - 1; i >=0; i--) {
if ( arr[i] === undef ) {
arr.splice(i,1);
}
}
Copy to new array http://jsfiddle.net/mendesjuan/aFvVh/2/
var undef;
var arr = [1,2, undef, 3, 4, undef];
var temp = [];
for (var i=0; i < arr.length; i++) {
if ( arr[i] !== undef ) {
temp.push(arr[i])
}
}
arr = temp;
Use filter which is just a fancy way to create a new array
var undef;
var arr = [1,2, undef, 3, 4, undef];
arr = arr.filter(function(item){
return item !== undef;
});
At the end of all those examples, arr will be [1,2,3,4]
Performance
IE 11, FF and Chrome agree that Array.splice is the fastest. 10 times (Chrome), 20 times (IE 11) as fast as Array.filter. Putting items into a new array was also slow when compared to Array.slice. See
http://jsperf.com/clean-undefined-values-from-array2
I am really surprised to see IE lead the pack here, and to see Chrome behind FF and IE. I don't think I've ever run a test with that result.
Loop backwards. (Removing items will thus not affect the indexes of elements not yet processed.)
If by any chance you're using CoffeeScript then to remove undefined from Array do this
values = ['one', undefined]
values = (item for item in values when item != undefined)
values
/* => ['one'] */
Surprisingly, nobody have answered the best and correct way:
Create new array
Iterate on the old array and only push the elements you want to keep to the new array
some credit goes to #nnnnnn comment
Not gathering exactly what you are trying to achieve, but I feel you are relying on the position index of an item in the array to continue with your program. I would in this case suggest a hashed array, i.e., a Key<>Value pair array.
In which case, arr["2"] always points at the item you had placed in it originally. Thus you can logically/numerically loop through, while not worrying about changes in position.
Beware of the Type Conversion risk and pitfalls!
Your best bet is to create a duplicate of the array, then splice from the original.
Or just go using a collection (key->value) and just delete the key eg
People = {a: "Person A", b: "Person B", c:"Person C"};
delete People.a;
delete People.c; //now the People collection only has 1 entry.
You can replace a,b,c with numbers just using it as an example,
People = {0: "Person A", 1: "Person B", 2:"Person C"};
delete People[0];
delete People[1];
this is a sample for you
<script lanauge = "javascript">
var arr = ["1","2","3","4"];
delete arr[1];// arr[1] is undefined
delete arr[2];// arr[2] is undefined
// now arr.length is 4
var todelete = [];
for (i = 0 ; i < arr.length ;i++)
{
if (typeof arr[i] == 'undefined') todelete.push(i);
}
todelete.sort(function(a, b) { return b-a }); // make the indeies from big to small
for (i = 0;i < todelete.length; i ++)
{
arr.splice(todelete[i],1);
}
// now arr.length is 2
</script>
This may not be what you want, but you can easily calculate what the final element at the end of this procedure will be, then just grab it. Assuming that the elements of the array are contiguous and start at arr[0], you can find:
var logBase2OfLength = Math.floor(Math.log(arr.length) / Math.log(2));
var finalElement = arr[(1 << logBase2OfLength) - 1];
Basically, if you take the integer power of 2 that is less than or equal to the number of elements in your array, that is the position of the element that will remain after all of the looping and deleting.
function removeUndefined(array)
{
var i = 0;
while (i < array.length)
if (typeof array[i] === 'undefined')
array.splice(i, i);
else
i++;
return array;
}
EDIT: I wrote this based on the title. Looks like the question asks something completely different.
>If you are getting undefined during deletion of the key-pair, Then to prevent "undefined" you can try code given below to delete key-pair
1) test = ["1","2","3","4",""," "];
2) var delete = JSON.stringify(test);
case1) delete = delete.replace(/\,""/g,'');
or
case2) delete = delete.replace(/\," "/g,'');
or
case3) delete = delete.replace(/\,null/g,'');
3) var result = JSON.parse(delete);
simply
[NaN, undefined, null, 0, 1, 2, 2000, Infinity].filter(Boolean)
while(yourarray.length>1) //only one left
{
// your code
}