This function is written in JavaScript but I think that the concept can be implemented with some other programming languages.
function uniteUnique(arr) {
let seenBefore = []; //the accumulating array
for (let item of arguments) {
if (typeof (item) == "object") {
uniteUnique(...item);
}
else if (!seenBefore.includes(item)) {
seenBefore.push(item);
}
}
return seenBefore;
}
In short, the function iterates over arrays it receives as arguments, which may or may not contain other arrays themselves. the deepest level of any of those arrays contains int values. The function returns an array that contains all those ints (i.e those that appeared in nested arrays), but it returns each int only once, even if it appeared more than one time.
My problem lies in the fact that every time the recursion returns to a higher level, it initializes again the array that contains the saved ints, namely the array that the function needs to return (seenBefore), and therefore ruins the whole process. On one hand, I must initialize the array when the function starts, but on the other hand, it is initialized more than once and loses its previously stored values.
for example, if I would run the function
uniteUnique([1, 3, [6, 3], 2], [5, 2, 1, 4], [2, 1]);
the output should be
[1,3,6,2,5,4]
because the function has to return the numbers it stumble upon only once by the order of processing. The function actually returns an empty array because it is initialized again right before the function returns from the top-most level of the recursion.
How can I bypass this problem?
(P.S: I know this can be "solved" by pulling the accumulating array out of the function to a different scope, but that causes other problems, such as the need to reinitialize the accumulating array each time before I run the function if I run it more than once.)
You've misidentified your problem. Each call to uniteUnique() has a separate value for the local variable seenBefore -- nothing is being "initialized again" during recursive calls.
Your real problem is that the line:
uniteUnique(...item);
discards the result of that function call, so the contents of any nested arrays are ignored. You need to assign the return value of this function somewhere and use it.
You may also want to change the condition for this function call to:
if (Array.isArray(item)) {
as the current condition typeof item == "object" will include objects which cannot be iterated over.
Another possibility would be to define seenBefore as the empty array as a default second parameter, which is recursively passed to every call of the function:
function uniteUnique(arr, seenBefore = []) {
for (const item of arr) {
if (typeof (item) == "object") {
uniteUnique(item, seenBefore);
}
else if (!seenBefore.includes(item)) {
seenBefore.push(item);
}
}
return seenBefore;
}
uniteUnique(someArr);
Note that this accepts a single argument as an array, rather than multiple arguments.
You can utilize a nested function, so that you don't need to reinitialize the accumulating array each time:
function uniteUnique(arr) {
function iterate(seenBefore, arr)
{
for (let item of arr) {
if (Array.isArray(item)) {
iterate(seenBefore, item);
}
else if (!seenBefore.includes(item)) {
seenBefore.push(item);
}
}
}
let result = []; // the accumulating array
iterate(result, arr);
return result ;
}
You don't actually need to use arguments and spread operator here, because your function expects an array and you can simply pass an array.
You may also want to use Set for seenBefore, because Array.prototype.includes scans through an array, which is ineffective.
Just in case: if you can use the last ES2019 features and prefer simplicity, this can also be achieved with a one-liner:
const uniquesArray = [...new Set(nestedArray.flat(Infinity))];
Related
This question already has an answer here:
What are Closures and Callbacks?
(1 answer)
Closed 4 months ago.
Where do arguments like element, index and array come from, for e.g. the filter method like below:
const arr = [ "Laurence", "Mike", "Larry", "Kim", "Joanne", "Laurence", "Mike", "Laurence", "Mike", "Laurence", "Mike" ];
const arr2 = arr.filter((value, index, array) => {
console.log(value, index, array.indexOf(value));
return array.indexOf(value) === index;
});
console.log(arr2);
In some built-in methods we use them, I know what they do, but I don’t understand if they are like built-in parameters.
For example, when one defines a function one would add parameters like value, index and array.
But when one calls the function one doesn’t add any arguments.
Are these kind of parameters like built-in parameters or why we don’t have to specify what they are?
I wrote a custom filter function to show you that these parameters aren't built-in, they are just passed to your callback by the Array.prototype.filter function when the internal loop is executed.
const result = filter([1, 2, 3, 4, 5], item => item % 2 === 0)
console.log(result)
function filter(array, callback) {
const newArray = []
for (let i = 0; i < array.length; i += 1) {
// pass value index and array
if (callback(array[i], i, array)) {
newArray.push(array[i])
}
}
return newArray
}
forEach example:
forEach([1, 2, 3, 4, 5], (value, index) => console.log(value, index))
function forEach(array, callback) {
for (let i = 0; i < array.length; i += 1) {
callback(array[i], i, array)
}
}
The answer is very simple, No there aren't
In JavaScript, functions are objects like any other -- you can pass functions to functions. This is how the Array.prototype.filter function (the filter method you'd call on an array like arr.filter(...)) works -- it takes a function as a parameter. It filters by calling the function you pass to filter, for every element, with the element [of the array] as value, the index of the element [in the array] as index, and the array itself as array. If your function returns true, the element is contained in the array returned by filter, otherwise the element isn't contained in the returned array.
So yes, the filter function is what calls the function you pass to filter, binding parameters to values -- something that makes parameters arguments.
In JavaScript, if you do not pass an argument to a function that expects an argument, the argument is undefined -- you can say that the parameter isn't bound:
function f(a) {
return a;
}
console.assert(f() === undefined); /// Yields true
JavaScript will not warn you that an argument is missing.
That is all there is to it, really.
Answering this question largely involves the difference between the terms "parameter" and "argument", also known as "formal parameters" and "actual arguments".
This canonical question, What's the difference between an argument and a parameter? covers the subject with external links.
Summary
A (formal) parameter is the name used when coding or documenting a function to identify an argument that will be passed to it.
An (actual) argument is the value of a (formal) parameter passed to a function when called.
Code that calls a function can supply arguments using literal values, or values held in variables or object properties. Calling code may choose to name variables after documented parameter names of a called function - but is not required to do so.
Posted code
arr.filter((value, index, array) => {
console.log(value, index, array.indexOf(value));
return array.indexOf(value) === index;
});
Here the array method Array.prototype.filter is called with a callback function argument, being the in-lined anonymous function, (value, index, array)=>{ /* code */}.
value, index and array are the call back function's parameter names, chosen by the person who wrote the code to be readable in English. If they wanted the code to be readable in French, say, they might have used elementCourant instead of value.
"value", "index" and "array" are the terms used in (MDN) English documentation of arguments passed to the callback by the filter method.
TL:DR
In general look up the documentation for standard JavaScript methods to find out what arguments are passed to callbacks they make. You can use callback argument names from the documentation as formal parameter names in callback functions you write - if you want to and it suits your purpose.
I'm wanting to write a function to filter objects of an array on some condition. I know that when you pass an array to a function, the changes you make to the array inside the function also change the array outside the function (if you are able to make the distinction in the first place anyway). For example,
a=[1,2,3]
function addItem(list){
list.push(4)
}
addItem(a)
// now a = [1,2,3,4]
Also, even though the filter method only returns a new array and doesn't change the array it was called it, it can be used to change that array by writing a = a.filter(someFunction). I imagine there is some subtlety I'm missing here because when I try to combine these two facts to write a function filtering an array on some condition, the original array isn't changed at all. Here is an example:
function isEven(n){
return (n%2 == 0)
}
function filterOdds(list){
list = list.filter(isEven)
}
a=[1,2,3,4]
filterOdds(a)
// a is still [1,2,3,4]
I suppose I really have two questions:
Why doesn't the above code change a to [2,4]? and
How can I write a function to filter lists as I'm trying to do above? Of course, you could get the job done without the function, but what I really want is to filter multiple arrays in a single function. This just seemed like the relevant simplification.
In your second code, you are reassigning the argument only. Reassigning a variable will, in almost all cases, have no side-effects; all it'll do is change what further references to that variable in that scope will refer to. If there happens to be an outer variable which gets passed as an argument, that outer variable will not get reassigned regardless of what happens inside the function, unless the function deliberately reassigns the outer references.
You could implement this by not passing the list argument - have filterOdds reassign the outer variable:
function isEven(n) {
return (n % 2 == 0)
}
function filterOdds() {
list = list.filter(isEven)
}
list = [1, 2, 3, 4]
filterOdds()
console.log(list);
If you want a more generic solution that'll work for any array passed, since dynamically reassigning an outer variable inside a function isn't possible, your only decent remaining option would be to return the new array and, when calling the function, assign the result:
function isEven(n) {
return (n % 2 == 0)
}
function filterOdds(list) {
return list.filter(isEven)
}
a = [1, 2, 3, 4]
a = filterOdds(a)
console.log(a);
I'm writing a script for sheets using Google Apps script, and I'm passing an array as an argument into a function, and I'd like to sort the array inside, and return a new array that's also been manipulated a bit after it's been sorted. I took some code from another thread here
The code:
function removeDupes(array) {
var outArray = [];
array.sort(); //sort incoming array according to Unicode
outArray.push(array[0]); //first one auto goes into new array
for(var n in array){ //for each subsequent value:
if(outArray[outArray.length-1]!=array[n]){ //if the latest value in the new array does not equal this one we're considering, add this new one. Since the sort() method ensures all duplicates will be adjacent. V important! or else would only test if latestly added value equals it.
outArray.push(array[n]); //add this value to the array. else, continue.
}
}
return outArray;
}
array.sort(); returns the error
"TypeError: Cannot call method "sort" of undefined. (line 91, file
"Code")"
. How can I perform the sort() method on the array passed into this function as an argument?
The error is very clear, whatever code you are using to call removeDupes isn't passing an actual array otherwise the .sort() method would work on the incoming argument. [Side note: why do you need to sort the array if you are interested in removing duplicates anyway?]
Beyond that, the Array.filter() method can filter out duplicates without you having to do all the looping. And, even when you do loop an array, you should not use for/in loops, as they are for enumerating objects with string key names, not arrays. Array looping should be done with regular counting loops, .forEach() or one of the many customized Array.prototype looping methods.
var testArray = ["apple", "bannana", "orange", "apple", "orange"];
function removeDupes(arr){
// The .filter method iterates all of the array items and invokes a
// callback function on each iteration. The callback function itself
// is automatically passed a reference to the current array item being
// enumerated, the index of that item in the array and a reference to
// the array being iterated. A new array is returned from filter that
// contains whatever the callback function returns.
return arr.filter( function( item, index, inputArray ) {
// If the index of the item currently being enumerated is the same
// as the index of the first occurence of the item in the array, return
// the item. If not, don't.
return inputArray.indexOf(item) == index;
});
}
var filteredArray = removeDupes(testArray);
console.log(filteredArray);
This question already has answers here:
Understanding Javascript callback parameters
(3 answers)
Closed 5 years ago.
var animals = ["cat","dog","fish"];
var lengths = animals.map(function(c) {
return c.length;
});
console.log(lengths);//[3, 3, 4]
Here is the code. I don't understand where this 'c' argument comes from.
I tried to change this argument to another one (any word, actually, in both places), and the console.log result is always the same!
But this 'c' is not defined anywhere! Where does 'the engine' get the value of this 'c'?
You've asked two slightly different questions. First to the question body:
I don't understand where this 'c' argument comes from. I tried to change this argument to another (any word, actually, in both places), and the console.log result is always the same!
But this 'c' is not defined anywhere!
Where does 'the engine' gets the value of this 'c'?
You define the parameter name (as you've noticed, you can choose any name for it you like). The value comes from the array, because map calls your callback and determines what argument to pass for that parameter.
Here's a conceptual implementaton of Array.prototype.map, which make make this clearer:
// CONCEPTUAL ONLY, NOT AN ACTUAL VERSION OF IT
function maplike(array, callback) {
var result = [];
for (var i = 0; i < array.length; ++i) {
result[i] = callback(array[i]);
// ^^^^^^^^--- where 'c' comes from
}
return result;
}
var animals = ["cat","dog","fish"];
var lengths = maplike(animals, function(c) {
return c.length;
});
console.log(lengths);//[3, 3, 4]
Do array elements have names by default in JavaScript?
Sort of, but not in the way you're thinking. The name of the element is its index, 0, 1, etc. In fact, JavaScript arrays aren't really arrays at all* and those indexes are converted to string property names (in theory; in practice, JavaScript engines optimize it).
* (disclosure: that's a post on my anemic little blog)
You're telling the interpreter how the parameter is called, here:
function(c) {
^
Array.prototype.map() requires a callback that accepts up to 3 parameters. The first parameter is always the "current item", which you happen to have named c.
For a more in-depth explanation, have a look at T.J. Crowders answer, as well.
In javascript, functions are first class object, which means they can be assigned to variables, passed as function parameters and returned from values. The Array.prototype.map function takes a function with it's first parameter denoting an item of the array. When invoked, the map function executes the given function for each of the items and creates a new array from the outputs of the given function.
In your case, you are defining the input function on the fly, inside the map function.
You can actually define the function outside and pass the function by reference inside map like below.
function getLength(item) {
return item.length;
}
var animals = ["cat","dog","fish"];
var lengths = animals.map(getLength);
console.log(lengths);//[3, 3, 4]
Here, you can see it outputs the same result.
The code does not know what is the parameter named. You map an array. map function creates a new array in the lengths variable (variable being assigned to). How? It provides to the function parameter inside it, each element in the current array one-by-one by value.
Here the value is actual string name ("cat" or "dog" or "fish").
In javascript, parameters can be optional. This map function can take three parameters, currentValue, index, array. In your case, c provides currentvalue.
If you would add one more parameter c,idx. Map function will get currentvalue and index inside it.
var animals = ["cat","dog","fish"];
var lengths = animals.map(function(c, idx, arr, test) {
console.log(c); // currentvalue being processed in the array.
console.log(idx); // index of currentvalue in the array
console.log(arr); // original array being operated on.
console.log(test); // undefined always. not available in map.
return c.length;
});
console.log(lengths);//[3, 3, 4]
I would like to know the reason why this simple piece of code fails:
var arr = [1, 2, 3];
arr.push(arr[0]).shift();
console.log(arr);
it returns in firebug console "TypeError: arr.push(...).shift is not a function"
I think it happens because I invoke the shift() method not on an array but on the pushed element.
Is there a more elegant way to obtain the same result that,
var arr = [1, 2, 3];
arr.push(arr[0]);
arr.shift();
console.log(arr);
produce ?
Thanks in advance!
From the MDN:
The push() method adds one or more elements to the end of an array and
returns the new length of the array.
arr.push(arr[0]) doesn't return the array but a number which, obviously, has no shift function.
To my knowledge, there's no simple expression pushing an element to an array and returning that array. But in your case you may simply reverse the operations and do
arr.push(arr.shift());
I think it happens because I invoke the shift() method not on an array but on the pushed element.
Almost. push returns the new length of the array. A number obviously doesn't have a shift() method.
Your method of putting it on two lines is the simplest way.
Essentially this question is saying, can I somehow "elegantly" express the notion of moving the first item of an array to the end. Luckily, JS is a Turing-complete language, which allows us to define functions, so the "elegant" answer is just
rotate(arr)
Now it merely remains to define rotate. To rotate is to drop the first element in the result of adding the head element to the end:
function rotate(arr) { return drop(add(arr, head(arr))); }
Now drop is
function drop(arr) { return arr.shift(), arr; }
and head of course is
function head(arr) { return arr[0]; }
and add is
function add(arr, elt) { return arr.push(elt), arr; }
Another approach
I could also write a function to move n elements from position i to position j, using splice, as follows:
function move(arr, n, i, j) {
arr.splice.apply(arr, [j-n+1, 0].concat(arr.splice(i, n)));
return arr;
}
Then to rotate is to move one element at the beginning to the end:
function rotate(arr) { return move(arr, 1, 0, 999); }