Fundamentals 4 Odin removeFromArray syntax questions? - javascript

const removeFromArray = function(firstArray,...toRemove) {
let modifiedArray = [...firstArray];
for (i = 0; i < firstArray.length; i++) {
if (modifiedArray.includes(toRemove[i])) {
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1)
}
}
return modifiedArray;
}
This is a project from odin fundamentals 4 for reference. I accidentally found the solution in my impatience while trying to figure out how to do it. The goal of said function is to input an array of whatever you like and remove whatever you like from said array via a function with 2 parameters. Ie I input removeFromArray([3,4,5], 3) and will be returned [4,5]
Question 1) Why are the brackets necessary around [...firstArray]
Question 2) If [i] is being used to loop over how many iterations you decide to input for the first parameter (an array), why is it being attached to (toRemove[i])?
Maybe I have the wrong idea of how this is actually working as a whole? Help appreciated, thanks!
Tests:
const removeFromArray = require('./removeFromArray')
describe('removeFromArray', function() {
it('removes a single value', function() {
expect(removeFromArray([1, 2, 3, 4], 3)).toEqual([1, 2, 4]);
});
it('removes multiple values', function() {
expect(removeFromArray([1, 2, 3, 4], 3, 2)).toEqual([1, 4]);
});
it('ignores non present values', function() {
expect(removeFromArray([1, 2, 3, 4], 7, "tacos")).toEqual([1, 2, 3, 4]);
});
it('ignores non present values, but still works', function() {
expect(removeFromArray([1, 2, 3, 4], 7, 2)).toEqual([1, 3, 4]);
});
it('can remove all values', function() {
expect(removeFromArray([1, 2, 3, 4], 1, 2, 3, 4)).toEqual([]);
});
it('works with strings', function() {
expect(removeFromArray(["hey", 2, 3, "ho"], "hey", 3)).toEqual([2, "ho"]);
});
it('only removes same type', function() {
expect(removeFromArray([1, 2, 3], "1", 3)).toEqual([1, 2]);
});
});
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))

Question 1) Why are the brackets necessary around [...firstArray]
Because without them it's a syntax error. That form of spread notation is only valid within an array literal. (Objects have the same notation, but it means something different.) ... isn't an operator, it's syntax that's only defined in certain places (array literals, object literals, destructuring patterns, function argument lists, and function parameter lists).
Question 2) If [i] is being used to loop over how many iterations you decide to input for the first parameter (an array), why is it being attached to (toRemove[i])?
It shouldn't be, that's incorrect. It happens to work if the array is longer than the number of things you're trying to remove from it because you're going from 0 to < firstArray.length and so if that's as many or more than the number of entries in toRemove, you see all of toRemove's entries (and undefineds, if firstArray is longer). Here's an example of that code not working:
const removeFromArray = function(firstArray, ...toRemove) {
let modifiedArray = [...firstArray];
for (i = 0; i < firstArray.length; i++) {
if (modifiedArray.includes(toRemove[i])) {
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1)
}
}
return modifiedArray;
}
console.log(removeFromArray([1, 2], 3, 4, 1)); // Should be [2], is actually [1, 2]
Instead, the for loop should be through < toRemove.length.
Another issue with that code is that it never declares i, so it's falling prey to what I call The Horror of Implicit Globals. I recommend using strict mode to make that the error it always should have been:
"use strict";
const removeFromArray = function(firstArray, ...toRemove) {
let modifiedArray = [...firstArray];
for (i = 0; i < firstArray.length; i++) {
if (modifiedArray.includes(toRemove[i])) {
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1)
}
}
return modifiedArray;
}
console.log(removeFromArray([1, 2], 3, 4, 1)); // Should be [2], is actually [1, 2]

Related

how to remove following arguments after the array

destroyer(array1, some arguments) function should return the array1 excluding the arguments. I found some working ways like return arr = arr.filter(val => !rem.includes(val)); but I need to fix this code and find out why this code giving an incorrect result. It supposed to be [1]
function destroyer(arr, ...rem) {
for(let i = 0; i < arr.length; i++) {
if (rem.includes(arr[i])) {
arr.splice(i, 1);
};
};
return arr;
}
console.log(destroyer([3, 5, 1, 2, 2], 2, 3, 5));
function destroyer(arr, ...rem) {
const itemsToRemove = new Set(rem);
return arr.reduce((acc, curr) => itemsToRemove.has(curr) ? acc : [...acc, curr] ,[])
}
console.log(destroyer([3, 5, 1, 2, 2], 2, 3, 5));
The problem is the call of the function Array.prototype.splice within the for loop, this function is mutating the array and basically affects the indexes of the array, therefore the current index i is no longer valid.
To work around this, you can loop in a backward direction, this way we can mutate the array, and the current index is not affected.
One more thing, your approach is mutating the array, a better approach would be by using the function Array.prototype.filter along with the function Array.prototype.includes, see other answers.
function destroyer(arr, ...rem) {
for(let i = arr.length; i >= 0; i--) {
if (rem.includes(arr[i])) {
arr.splice(i, 1);
};
};
return arr;
}
console.log(destroyer([3, 5, 1, 2, 2], 2, 3, 5));
Or you can do it like this:
const destroyer=(arr, ...rem)=>arr.filter(v=>!rem.includes(v));
console.log(destroyer([3, 5, 1, 2, 2], 2, 3, 5));

Why this rest parameter is usefull here?

I have two solutions for the same problem (both work), one is mine and the other one is from the internet.
In the internet's solution they add the variable modifiedArray. What is the point of doing that?
In the internet's solution, is the [...arr] in the modifiedArray variable not the same that the argument arr on the function removeFromArray()?
My solution:
const removeFromArray = function(arr, ...Args) {
for (i = 0; i <= Args.length; ++i) {
if (arr.includes(Args[i])) {
arr.splice(arr.indexOf(Args[i]), 1)
}
}
return arr;
}
module.exports = removeFromArray
Internet's solution:
const removeFromArray = function(arr, ...Args) {
let modifiedArray = [...arr]
for (i = 0; i < arr.length; ++i) {
if (modifiedArray.includes(Args[i])) {
modifiedArray.splice(modifiedArray.indexOf(Args[i]), 1)
}
}
return modifiedArray;
}
module.exports = removeFromArray
Test for both answers:
const removeFromArray = require('./removeFromArray')
describe('removeFromArray', function() {
it('removes a single value', function() {
expect(removeFromArray([1, 2, 3, 4], 3)).toEqual([1, 2, 4]);
});
it('removes multiple values', function() {
expect(removeFromArray([1, 2, 3, 4], 3, 2)).toEqual([1, 4]);
});
it('ignores non present values', function() {
expect(removeFromArray([1, 2, 3, 4], 7, "tacos")).toEqual([1, 2, 3, 4]);
});
it('ignores non present values, but still works', function() {
expect(removeFromArray([1, 2, 3, 4], 7, 2)).toEqual([1, 3, 4]);
});
it('can remove all values', function() {
expect(removeFromArray([1, 2, 3, 4], 1, 2, 3, 4, 5)).toEqual([]);
});
it('works with strings', function() {
expect(removeFromArray(["hey", 2, 3, "ho"], "hey", 3)).toEqual([2, "ho"]);
});
it('only removes same type', function() {
expect(removeFromArray([1, 2, 3], "1", 3)).toEqual([1, 2]);
});
});
The difference between your code and the other one, is that yours changes the arr parameter directly whereas the other first makes a copy of the array, and then modifies that copy.
When you pass an array to a function, you are actually passing a reference to that array, not a copy of it. This means that when you modify arr directly, you are also modifying the source array.
Here is a nice example:
const removeFromArray1 = function(arr, ...Args) {
for (i = 0; i <= Args.length; ++i) {
if (arr.includes(Args[i])) {
arr.splice(arr.indexOf(Args[i]), 1)
}
}
return arr;
}
const removeFromArray2 = function(arr, ...Args) {
let modifiedArray = [...arr]
for (i = 0; i < arr.length; ++i) {
if (modifiedArray.includes(Args[i])) {
modifiedArray.splice(modifiedArray.indexOf(Args[i]), 1)
}
}
return modifiedArray;
}
const arr1 = [1, 2, 3, 4];
console.log('Returned array 1', removeFromArray1(arr1, 3));
console.log('Source array 1', arr1);
const arr2 = [1, 2, 3, 4];
console.log('Returned array 2', removeFromArray2(arr2, 3));
console.log('Source array 2', arr2);
Here you can see that arr1 is modified after calling removeFromArray1, but arr2 is not modified after calling removeFromArray2. Changing the source array might have odd side-effects if you try to use the initial array expecting it not to be changed.
let modifiedArray = [...arr] is a simple way to make a shallow copy of the array.
The second solution is nearer to functional programming pattern (pure function).
In functional programming you dont push or delete items into existing arrays or objects.
You would rather create a new array with all the same items as the original array, Then you modify the duplicate and return it. The duplication is done by
let modifiedArray = [...arr]
The concept is described as pure Function. A function should not change anything outside the function. No side effects.

Why isn't my javascript iterating through all of the arguments?

I'm working on The Odin Project and am on the Fundamentals Part 4 "removeFromArray" assignment. I have to return an array having removed all of the elements that are in the list of arguments. It works with two arguments, but I can't get it to work with four. My code is below:
const removeFromArray = function(firstArray, ...toRemove) {
let modifiedArray = firstArray;
for (i = 0; i < firstArray.length; i++) {
if (modifiedArray.includes(toRemove[i])) {
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1)
}
}
return modifiedArray;
}
removeFromArray([1, 2, 3, 4], 7, 2) // works, returns [1, 3, 4]
removeFromArray([1, 2, 3, 4], 3, 2) // also works, returns [1, 4]
removeFromArray([1, 2, 3, 4], 1, 2, 3, 4) //does not work, returns [3, 4]
Any help is much appreciated.
Splicing from the array shifts all the remaining elements down by one, so you end up skipping over the next element. I'd recommend using Array.prototype.filter instead.
I think the real issue here, is that you may not be aware that arrays, objects, and functions in JavaScript are passed by reference.
let modifiedArray = firstArray;
Meaning your firstArray and modifiedArray are both pointing to the same array in memory. The code above assigns the EXACT same array, technically the address in memory of the firstArray to the modifiedArray. Therefore, as you remove items from the modifiedArray, you are also removing them from the firstArray, and therefore changing the length of the firstArray.
You need to copy the array by value, not by reference.
Solution:
Therefore changing
let modifiedArray = firstArray;
to
let modifiedArray = [...firstArray];
or
let modifiedArray = firstArray.slice();
The first solution leverages destructuring of the first array, to create a copy of the array by value, not pointing to the same array in memory.
The second may be more familiar to you as a beginner, since this simply returns a copy of the array, without removing any elements.
See this thread if you have more questions about copying arrays by value: Copy array by value
const removeFromArray = function(firstArray, ...toRemove) {
let modifiedArray = [...firstArray];
for (i = 0; i < firstArray.length; i++) {
if (modifiedArray.includes(toRemove[i])) {
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1)
}
}
return modifiedArray;
}
console.log(removeFromArray([1, 2, 3, 4], 7, 2)); // works, returns [1, 3, 4]
console.log(removeFromArray([1, 2, 3, 4], 3, 2)); // also works, returns [1, 4]
console.log(removeFromArray([1, 2, 3, 4], 1, 2, 3, 4)); //does not work, returns [3, 4]
1, 2, 3, 4
After you remove the first item, everything suffles down, so what was the second item becomes the first item.
You don't have that problem if you start at the end and work in reverse (sort the list of items to remove).
I've changed the following line:
let modifiedArray = firstArray;
to
let modifiedArray = [...firstArray];
The problem you were facing is you were iterating over an array assuming it is a copy of the firstArray. But it was just pointing to firstArray. Now in the loop you startet removing items from the array and after you removed 2 in the iteration you were already on 2 so there was nothing left to iterate over...
const removeFromArray = function(firstArray, ...toRemove) {
let modifiedArray = [...firstArray];
for (var i = 0; i < firstArray.length; i++) {
if (modifiedArray.includes(toRemove[i])) {
modifiedArray.splice(modifiedArray.indexOf(toRemove[i]), 1)
}
}
return modifiedArray;
}
var x = removeFromArray([1, 2, 3, 4], 7, 2);
var y = removeFromArray([1, 2, 3, 4], 3, 2);
var z = removeFromArray([1, 2, 3, 4], 1, 2, 3, 4);
console.log('x', x);
console.log('y', y);
console.log('z', z);
You take the same index for the array and for the items to remove, which may not be the same.
You could iterate the item to remove and get the index and splice this item from firstArray.
This approach muates the given array and keeps the same object reference.
const removeFromArray = function(firstArray, ...toRemove) {
for (let i = 0; i < toRemove.length; i++) {
let index = firstArray.indexOf(toRemove[i]);
if (index === -1) continue;
firstArray.splice(index, 1);
}
return firstArray;
}
console.log(...removeFromArray([1, 2, 3, 4], 7, 2)); // [1, 3, 4]
console.log(...removeFromArray([1, 2, 3, 4], 3, 2)); // [1, 4]
console.log(...removeFromArray([1, 2, 3, 4], 1, 2, 3, 4)); // []
const removeFromArray = (firstArray, ...toRemove) =>
firstArray.filter(e => !toRemove.includes(e));
console.log(removeFromArray([1, 2, 3, 4], 7, 2));

Pass an array and further arguments into a function. How?

I have a function which takes an array and further arguments like this:
function arrayandmore([1, 2, 3], 2, 3)
I need to return the array ([1, 2, 3]) without those elements which equals the arguments coming behind the array. So in this case, the returned array would be:
([1]).
One of my approaches is:
function destroyer(arr) {
var args = Array.from(arguments);
var i = 0;
while (i < args.length) {
var result = args[0].filter(word => word !== args[i]);
i++;
}
console.log(result);
}
destroyer([1, 1, 3, 4], 1, 3);
Console returns:
[ 1, 1, 4 ]
I don't understand, why it returns one too - I don't understand, why it does not work.
It is the same with using splice.
function destroyer(arr) {
var args = Array.from(arguments);
var quant = args.length - 1;
for (var i = 1; i <= quant; i++) {
if (arr.indexOf(args[i]) !== -1) {
arr = arr.splice(arr.indexOf(args[i]));
}
console.log(arr);
}
}
destroyer([1, 1, 3, 4], 1, 3);
I think, both ways should work. But I don't figure out why they don't.
Your while won't work because result is being overwritten in every loop. So, it only ever removes the last parameter to the destroyer function
You can use the rest parameter syntax to separate the array and the items to be removed.
Then use filter and includes like this:
function destroyer(arr, ...toRemove) {
return arr.filter(a => !toRemove.includes(a))
}
console.log(destroyer([1, 1, 3, 4, 5], 1, 3))

How to compare multiple arrays and return an array with unique values in javascript?

I would like to compare multiple array and finally have an array that contain all unique values from different array. I tried to:
1,Use the filter method to compare the difference between 2 arrays
2,Call a for loop to input the arrays into the filter method
and the code is as follows
function diffArray(arr1, arr2) {
function filterfunction (arr1, arr2) {
return arr1.filter(function(item) {
return arr2.indexOf(item) === -1;
});
}
return filterfunction (arr1,arr2).concat(filterfunction(arr2,arr1));
}
function extractArray() {
var args = Array.prototype.slice.call(arguments);
for (var i =0; i < args.length; i++) {
diffArray(args[i],args[i+1]);
}
}
extractArray([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3]);
However it does not work and return the error message "Cannot read property 'indexOf' of underfined" .... What's wrong with the logic and what should I change to make it works?
Many thanks for your help in advance!
Re: For all that mark this issue as duplicated ... what I am looking for, is a solution that can let me to put as many arrays as I want for input and reduce all the difference (e.g input 10000 arrays and return 1 array for unique value), but not only comparing 2 arrays .. The solutions that I have seen are always with 2 arrays only.
I don't use filters or anything of the sort but it will get the job done. I first create an empty array and concat the next array to it. Then I pass it to delete the duplicates and return the newly "filtered" array back for use.
function deleteDuplicates(a){
for(var i = a.length - 1; i >= 0; i--){
if(a.indexOf(a[i]) !== i){
a.splice(i, 1);
}
}
return a;
}
function extractArray() {
var args = Array.prototype.slice.call(arguments), arr = [];
for (var i = 0; i < args.length; i++) {
arr = deleteDuplicates(arr.concat(args[i]));
}
return arr;
}
var arr = extractArray([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3]);
console.log(arr) //[3, 2, 5, 1, 7, 4, 6]

Categories

Resources