Why does splice() behave weird? - javascript

I am trying to flatten multiple arrays into one and remove duplicate elements in the array for an exercise from FCC.
Specifically, the splice method shows one thing in the console but acts differently. Can anyone tell me why splice() is not deleting the the duplicates that I have identified with the nested loop?
function uniteUnique(arr) {
var arr1 = arguments;
newArr= [];
for(var i=0; i<arr1.length; i++){
for(var l=0; l<arr1[i].length; l++){
newArr.push(arr1[i][l]);
}
}
console.log(newArr);
for(var t=0; t<newArr.length; t++){
for(var p=0; p<newArr.length; p++){
if(newArr[t]===newArr[p+1]){
console.log("without splice ", newArr[p+1]);
console.log("with splice ", newArr.splice(newArr[p+1],1))
newArr.splice(newArr[p+1],1);
}
}
}
return newArr;
}
console.log(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]));
Will output [4,1].
But should output [1,3,2,5,4]
first Iteration Log:
without splice 1
with splice [3]
Second:
without splice 1
with splice [2]
Third:
without splice 2
with splice [4]
Fourth:
without splice 1
with splice [2]

You have three errors here:
There's no need to use p+1 here. Just use p.
splice expects an index as its first argument, but you are passing the value being considered for removal as an index to perform the removal. This is doesn't make sense. Instead of .splice(newArr[p], 1) you need .splice(p, 1).
You do not stop a value from being considered against itself as a potential duplicate. Your if condition must also include the condition ... && p!=t since newArr[t]===newArr[p] will always be (uselessly) true in case that t equals p

Modifying an array in its loop is something is not recommended. If you don't have to use splice, just create a brand new array and push values by searching and if not already exists in it.
function uniteUnique() {
var newArr = [];
for(var i=0; i<arguments.length; i++) {
var arr = arguments[i];
for(var j=0; j<arr.length; j++) {
if(newArr.indexOf(arr[j]) == -1) {
newArr.push(arr[j]);
}
}
}
return newArr;
}
console.log(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]));

In respect to the removing duplicates "in place" of your approach, splice is working fine and your algorithm is almost good, try to debug through each step of the iterations after the flattening, to remove the duplicates.
You are looping n squared times (because p = t and you iterate p * t times).
But, something is not doing what you want to, have a look at these:
if(newArr[t]===newArr[p+1]){ // HERE IT WON'T DO WHAT YOU WANT
I'm not going to give away the answer unless you mention it, I'm just trying to demonstrate that the problem does not rely with splice itself, rather than your algorithm.
The other line that you would need to modify would be this one
newArr.splice(newArr[p+1],1);
Basically with a few changes in these two lines, you would reach the goal to remove the duplicates.

Related

Trying to understand why my function won't remove more than one instance of the value arguments[i] from newArr [duplicate]

This question already has answers here:
How to filter an array from all elements of another array
(24 answers)
Closed 8 months ago.
function destroyer(arr) {
const newArr = [...arguments[0]]
for(let i = 0; i < newArr.length; i++){
for(let j = 1; j < arguments.length; j++){
if(arguments[j] == newArr[i]){
newArr.splice(i,1)
console.log(newArr)
}
}
}
}
destroyer([3, 5, 1, 2, 2], 3, 5, 2);
New JS learner here.
Working on a problem that is supposed to look through the first arg in destroyer which will be an array and remove the elements that match the arguments following the array.
Results are [1,2] in the console output. Intended results are [1] with the given parameters Upon further testing it seems like the destroyer function is only removing the first instance of any value that it matches in newArr. If I take the second instance of '2' out of the test set it behaves as intended. I'm trying to understand what in my logic here is wrong. I've tried several different iteration patterns and can't seem to see what the problem is.
Thanks for any help!
I am going to link a few other answers here is this seems to be a fairly common question. The gist is that you are iterating over the live array while removing items from that array resulting in potentially skipping items.
How to iterate over an array and remove elements in JavaScript
Remove multiple elements from array in Javascript/jQuery
Alternatively you could also the filter method to help remove multiple values:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Try this:
function destroyer(arr) {
const newArr = [...arguments[0]]
for(let i = newArr.length - 1; i >= 0; i--){
for(let j = 1; j < arguments.length; j++){
if(arguments[j] == newArr[i]){
newArr.splice(i,1)
console.log(newArr)
}
}
}
}
This is an array pointer issue.
when i = 3 and j = 3, the function will match arguments[3] == newArr[3].
At this moment, newArr will be removed 1 element which is the first 2, and then the newArr becomes an new array that is [3,5,1,2].
The next index i is 4 which doesn't exist in the new newArr. So, the function will return and finish. That's why you get [3,5,1,2].
function destroyer (arg) {
if(!Array.isArray(arg)) return arg;
return arg.filter(x => !Array.prototype.slice.call(arguments, 1).includes(x))
}
It is because newArr is getting shorter as you splice it through your loops and the loop itself is also shortened.
see this probably your solution

What is the [i] doing in this function?

const removeFromArray = function(firstArray,...toRemove) {
let modifiedArray = [...firstArray];
for (let i = 0; i < toRemove.length; i++) {
if (modifiedArray.includes(toRemove[i])) {
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1)
}
}
return modifiedArray;
};
console.log(removeFromArray([3,4,5], 3,5)) ; //result is [4]
Goal : to take any array as first argument and remove whatever you want from said array as next argument.
Sorry this is keeping me from sleeping but what is [i] doing attached toRemove?
Lets say I want to do removeFromArray(['a','b','c'], 'b');
This will mean toRemove is equal to 'b'. The length of toRemove will be 1. So the loop says run for one iteration because toRemove is only 1 length.
To this point I think I understand. I don't get why (modifiedArray.includes(toRemove[i])) because doesn't that just mean (modifiedArray.includes('b'[1])) ?
And if you do a like removeFromArray(['a','b','c'], 'a', 'c'); Would the [i] mean that toRemove will get iterated twice, once for each, so for the second iteration its value would be 2?
(modifiedArray.includes(['a', 'c'][1]))
(modifiedArray.includes(['a' ,'c'][2]))
or would it be
(modifiedArray.includes(['a' ,'c'][1]))
(modifiedArray.includes(['a' ,'c'][1]))
I hope I was able to kind of explain my confusion I'm a bit frustrated. Thanks!
const removeFromArray = function (firstArray, ...toRemove) {
Read about Rest parameters first.
firstArray will be first argument you are passing to this function.
...toRemove takes rest of the argument as an Array.
If you call the function this way removeFromArray([1,3,4], 4,5,6,7),
firstArray will be equal to [1, 3, 4]
toRemove will be equal to [4, 5, 6, 7]
let modifiedArray = [...firstArray]; // this will create a copy of the firstArray and store it in modifiedArray.
for (let i = 0; i < toRemove.length; i++) {
This will loop over the toRemove array whose value is [4, 5, 6, 7]
if (modifiedArray.includes(toRemove[i])) {
In the first iteration, it will check toRemove[0] which is 4 exists in modifiedArray, if it exists it will remove that element from the modifiedArray in the next line.
In the second iteration it will check toRemove[1] which is 5 and remove it from modifiedArray likewise it will go on removing all the elements present in toRemove from modifiedArray.
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1);
At the end it will remove all the elements which the array toRemove contains from the modifiedArray which is a copy of firstArray and return it.
That's simply because of the rest parameter
From MDN Documentation
The rest parameter syntax allows us to represent an indefinite number
of arguments as an array.
so in your example your arguments passed to your removeFromArrayfunction (3,5) become an array named toRemove and in order to access the values you would have to do remove[i].
here is an example to help you clarify
const removeFromArray = function(a, b) {
console.log(a)
console.log(b)
}
removeFromArray(1, 3)
adding the spread operator and passing the values as a single argument
const removeFromArray = function(...toRemove){
console.log(toRemove)
}
removeFromArray(1,3)
toRemove.lengthdetermines the number of cycles,Each value in toremove is executed once.
There's another important thing,indexOf only shows index of the first lookup object. So, if there are more than one same number in the array, it will not be deleted.
['a','b','b'].indexOf('b')
I'm not good at English. I can only talk to you so much.

Side effects when async operating on arrays

I'm learning node.js atm, now I'm asking myself:
How "threadsafe" are normal Arrays?
Example:
var myArr = ["Alpha", "Beta", "Gamma", "Delta"];
ee.on('event', function(itemString) {
//Loop over an Array that could change its length while looping through
for(var i=0; i<myArr.length; i++) {
// delete the item out of the array
if(myArr[i] == itemString)
myArr.splice(i,1);
}
});
If multiple of the Events are fired on the ee-Object, is there a chance, that the for Loop will fail because the indexes are already spliceed away?
Or said different: Is a way to ensure that the loop won't skip or fail because any elements that may be deleted by another callback call of the same event?
THX :)
node.js is single threaded and it does not interrupts sync execution.
Still, you're modifying the array while iterating it by its length which may lead to skipping elements.
Also, your event is not prepared to be fired twice for the same array element.
I think we've covered the threading issue well, but you really should still address the loop. For an example of the "skipping" problem I'm talking about, try this:
var a = [1, 2, 3, 4, 5];
for (var i = 0; i < a.length; i++) {
console.log(a[i]);
if (a[i] === 2) {
a.splice(i, 1);
}
}
Output:
1
2
4
5
Notice how the number 3 is never even seen by this loop.
One common way to fix this kind of loop so you can safely delete elements of the array while iterating over it is to go backwards:
var a = [1, 2, 3, 4, 5];
for (var i = a.length - 1; i >= 0; i--) {
console.log(a[i]);
if (a[i] === 2) {
a.splice(i, 1);
}
}
Output:
5
4
3
2
1
Notice that we see all the elements of the array this way.

When looping through values of a JS array, and I remove value, do I need to use while instead of for?

var myArray = [1,2,3,4,5,6,7,8,9];
function isOdd(value){
return value % 2;
}
for(var i = 0; i < myArray.length; i++){
if(isOdd(myArray[i])){
myArray.splice(i,1);
i--;
}
}
The code above takes an array of arbitrary length and checks each value. If the value of the bit of the array meets an arbitrary condition (in this case if it is odd), then it is removed from the array.
Array.prototype.splice() is used to remove the value from the array, and then i is decremented to account for the rest of the values in the array "moving down" to fill in the gap that the removed value left (so the loop doesn't skip over a value).
However, the for loop ends when i equals the length of the array, which gets shorter as values are removed.
Does the value of myArray.length decrease dynamically as the loop proceeds, or does it save the value at the start of the loop and not update as values are removed? If the latter, what can I do to fix my loop?
Thank you!
myArray.length is changing with the operation on the array. But looping and splicing leads to unwanted results, if not proper padded.
To prevent unnecessary corrections, use a while loop from the end, to keep the rest of the array for processing.
function isOdd(value) {
return value % 2;
}
var myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9],
i = myArray.length;
while (i--) {
if (isOdd(myArray[i])) {
myArray.splice(i, 1);
}
}
console.log(myArray);
The length property is read in every iteration, and the splice method does update its value, so it works as you would expect. However, I would say that this is not a good coding practice, a while loop is much more readable, so it should be the obvious choice.
To answer the question directly: you don't have to use while instead of for, but you definitely should.
Use Array.filter instead
var myArray = [1,2,3,4,5,6,7,8,9];
myArray=myArray.filter(function(item,index) {
return !(item % 2);
})
console.log(myArray)
This is where you'd want to use Array.prototype.filter() if you don't absolutely HAVE to modify the original array, in-place.
As you suspect, the .length property of the array is being updated every time you splice(). The filter() method was built for exactly this sort of operation.
var myArray = [1,2,3,4,5,6,7,8,9];
function isOdd(value){
return value % 2;
}
var filteredArray = myArray.filter(function(item){
return !isOdd(item);
});
console.log(filteredArray);
A more concise version of the above code:
var myArray = [1,2,3,4,5,6,7,8,9];
function isEven(value){
return value % 2 === 0;
}
var filteredArray = myArray.filter(isEven);
console.log(filteredArray);
An even more concise version relying on ES6 arrow syntax:
var myArray = [1,2,3,4,5,6,7,8,9];
var isEven = value => value % 2 === 0;
var filteredArray = myArray.filter(isEven);
console.log(filteredArray);
And, in the event that you absolutely MUST edit the array in-place / use splice() here, I would recommend using Array.prototype.forEach() over a for or while loop. forEach() is another higher order method that allows you to realize the same functionality with less boilerplate. As with most higher order methods/functions, it allows you to focus on defining what you need to do rather than exactly how it needs to be done.
var myArray = [1,2,3,4,5,6,7,8,9];
function isOdd(value){
return value % 2;
}
myArray.forEach(function(c, i, a){
if(isOdd(c)){
a.splice(i,1);
}
})
console.log(myArray);
You can use both of them and it's depends on which one you like. if you prefer to use while loop then Nina's answer looks good and if you want to use for loop then consider to manage counter changes by yourself completely or when the length changes:
function isOdd(value) {
return value % 2;
}
var arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
for (var i = 0; i < arr1.length;)
isOdd(arr1[i]) ? arr1.splice(i, 1) : i++;
console.log(arr1);
var arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
for (var i = 0; i < arr2.length; i++)
if (isOdd(arr2[i])) {
arr2.splice(i, 1);
i--;
}
console.log(arr2);

Constructive bubble sort not working as expected

I'm trying to create a simple bubble sort in JavaScript and cannot understand why my code is not working, the problem seems to be coming from the second if statement, I do not know the exact problem as the browser I'm testing it in refuses to load the page when using this code.
var arr = [4, 6, 0, 3, -2, 1];
var arr2 = [arr[0]];
arr.forEach(function(elem){
for(var j=0; j<arr2.length; j++){
if(elem < arr2[j]){
arr2.splice(j, 0, elem);
break;
}
//if number is largest on last iteration add it to the end of the array
if(j == arr2.length-1){
console.log(elem);
//problem seems to be here
arr2[arr2.length] = elem;
}
}
});
console.log(arr);
console.log(arr2);
You're adding elem to the array with .splice if it is lower than arr[j] but you never removing the old instance of elem. Conesquently, you're always adding items array and it is always getting larger. You need to take out the old instance of elem from the array so the array length remains constant from iteration to iteration.
So, somewhere, you should be passing 1 to the second argument of splice to take something out of the array.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice

Categories

Resources