I have been doing research on the benefits of using reduce over map. Mainly the benefit is that reduce has better performance time.
My question is how would i be able to use reduce to iterate over an array as if i would for the map function ?
An example
The following code combines the fruits to one string. How would i be able to iterate over this array using the reduce function ?
const fruits = ['pears', 'plums', 'grapes', 'apples']
console.log(fruits.reduce( (acc, fruit) => acc.concat(fruit)))
// "pearsplumsgrapesapples"
You'll sacrifice code legibility for a millisecond of difference but here it is:
['pears', 'plums', 'grapes', 'apples'].reduce(function (acc, currentFruit) {
// Your code here
acc.push(currentFruit);
return acc;
}, []);
You'll have to push your current value to a an array because the reduce method objective is to reduce the provided data to a single value.
At the end of the day, map is just better, simpler and you won't be able to notice the time difference.
The reduce function indeed iterates over the array, because it's meant to reduce all the array items to a single value by applying a function.
The map function iterates on the array too, in order to project the array elements to a value calculated by applying a provided function.
The difference between the two functions is that they are iterating over the array for a different purpose. Reduce is used to compute single value, map is used to project each array item to a new value.
In both cases you can't say anything about the computational complexity because you are passing a callback and you don't know what that callback is going to do. So the computational complexity depends on the callback function.
Take a look at this paper about reduce: https://hackernoon.com/javascript-array-reduce-50403c421968
You could pass an empty array as the reduce function accumulators initial value, then within the body of your reduce function you can append to the acc which will now be an array and execute conditional logic.
map is simple. It calls the function on each element of the input array, and returns a new array containing the corresponding function results.
result = array.map(f)
is equivalent to
result = [f(array[0], 0, array), f(array[1], 1, array), f(array[2], 2, array), ...]
You use reduce if you want to combine the results of each iteration in a custom way, to produce one result. The function takes two arguments -- the first is the return value of the previous iteration, and the second is the current element from the iteration.
result = array.reduce(f, init)
is equivalent to
temp1 = f(init, array[0], 0, array);
temp2 = f(temp1, array[1], 1, array);
temp3 = f(temp2, array[2], 2, array);
...
result = f(tempN, array[N], N, array);
If you don't provide an initial value argument, the first line becomes
temp1 = array[0];
and the rest is the same.
If you don't actually need a new result, you just want to execute some code on each array element, you use forEach(). It's like map, but it doesn't collect the results. If you want to log each array element on a new line, you would do:
array.forEach(el => console.log(el));
Related
There is a simple function, its essence is to count from a number (n) to 0.
But when using reduce, the function just doesn't work, and no matter how I rewrite it, it returns either an empty array, or undefined, or the number itself 2.
First, I created an array that will take n, then I created a reduce method in which currentValue will take n and subtract 1 from it, after accumulator it takes the resulting number and using the push method, add it to the list array, but I don’t understand how I should add a condition that if accumulator is equal to 0, then the function must be stopped.
const countBits = (n) => {
let list = [n];
let resultReduce = n.reduce((accumulator, currentValue) => {
accumulator = currentValue - 1;
list.push(accumulator);
});
return resultReduce;
};
console.log(countBits([2]));
Why isn't this working the way I intended it to?
reduce will run on each of the items in the array, with the accumulator (first argument to the callback function) being the value that is returned from the callback function's previous iteration. So if you don't return anything, accumulator will be undefined for the next iteration.
If you want to count from n to 0, reduce is not the way to go (as well as the fact that in your current implementation, you don't even use list which would contain all of your stored numbers from n to 0). I would advise that instead, you simply loop from n to 0 and push those values into an array like so:
const countBits = (n) => {
let list = [];
for (let i = n; i > -1; i--) {
list.push(i);
}
return list;
};
console.log(countBits(2));
Also note I've changed your syntax slightly in the function call - you were passing an array with a single element seemingly unnecessarily, so I just passed the element itself to simplify the code.
The answer by Jack Bashford is correct, but for completeness I would like to point out that generating a range of numbers is a common need. Libraries like Underscore, Lodash and Ramda provide a ready-to-use function for this purpose. You don’t have to write your own implementation every time you need something common and mundane like that; save the time and enjoy the fact that you can spend your time on something more groundbreaking instead.
console.log(_.range(2, -1, -1));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
Also for the sake of completeness, let’s consider how you might implement a downwards range function using reduce, anyway. reduce expects an input array, though it can also accept an object if using Underscore or Lodash. To make meaningful use of the input collection, we could generate a consecutive number for every element of the collection. For an array, we could of course just do _.range(collection.length - 1, -1, -1) instead, but for an object, or something that you don’t know the length of in advance, such as a generator, using reduce for this purpose might make sense. The mapDownwardsRange function below will do this:
function unshiftNext(array) {
const front = array.length ? array[0] : -1;
return [front + 1].concat(array);
}
function mapDownwardsRange(collection) {
return _.reduce(collection, unshiftNext, []);
}
console.log(mapDownwardsRange(['a', 'b', 'c']));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
I'd ask a better question, but I don't don't know how. Thanks for your help.
***ISSUE: I'm sending array vari to a function and it's coming back changed even though I didn't return it or even use same variable name. Desired function: variable vari does not change
I've logged the function and isolated the change to the [].forEach() statement below, noted with ***. I send vari but return varis and assign to new variable sum. How does this change the vari variable?
//this is what calls the sub function using vari, an array, 411.0, 13.0
var sum = doSum1(vari);
function doSum1(vari0) {
// called from doSum1
// grab Variance data // ALL COLUMNS FOR NOW // fix loc/stat columns below
var vstat = vari0[0].indexOf('Status');
vari1 = vari0.filter(r=>r[vstat]); // to ensure indexOf works, and speed processing
var vhdr = ['Campaign ID','Campaign','Site ID','Site','Placement','Placement ID','Date','DCM Imp','Upw Imp','Tag Location','Status','Site Count','Site Imp'];
// move loc and status over in place of variance and percent (loc/stat will be site ct/imp)
varis=[];
// *** THIS FOREACH CHANGES varis AND vari. Not sure how... see more specifically below
['Not Tracking','Undertracking','Overtracking','Absent in DCM'].forEach(rf=>{
varis.push(vhdr.map(r=>''));
varis[varis.length-1][0]=rf;
varis.push(vhdr);
if(vari1.filter(r=>r[vstat].indexOf(rf)>=0).length==0) {
varis.push(vhdr.map(r=>''));
varis[varis.length-1][0]='none found';
} else {
varis.push(vari1.filter(r=>r[vstat].toString().indexOf(rf)>=0)[0]); // break out of outer []
//fix loc/stat location
//*** MORE SPECIFICALLY, this line in particular changes in vari, not just varis as intended.
varis[varis.length-1].splice(9,4,varis[varis.length-1][11],varis[varis.length-1][12],'','')
}
varis.push(vhdr.map(r=>'')); // trailing blank line
});
return varis;
I tried this in place of the splice as well, but same result... just not sure how varis is changing vari...
varis[varis.length-1][9] = varis[varis.length-1][11];
varis[varis.length-1][10] = varis[varis.length-1][12];
varis[varis.length-1][11] = '';
varis[varis.length-1][12] = '';
vari is a 2D array. That means that every element in vari is an array as well, and as such passed by reference and subject to mutation.
The Array.splice() method mutates its argument array. In the code, each varis[varis.length-1].splice() call modifies an array object that is copied from vari1 by reference, and therefore also vari0 whose elements are array objects that are copied to vari1 by reference. This is what causes vari to mutate.
To avoid the issue, use one these patterns:
var vari1 = vari0.map(row => row.slice()).filter(r => r[vstat]);
or
var vari1 = vari0.map(row => row.map(value => value)).filter(r => r[vstat]);
The patterns use Array.map() and Array.slice()to get a shallow copy of the 2D array referenced by vari0 (i.e., vari).
The first map() creates a new array of that contains the rows of vari0. The rows are arrays and therefore mutable, so a slice() or another map() is required to copy the rows into new arrays as well.
Note that the copy is shallow, which means that only primitive values such as text strings and numbers are copied by value. Your comments indicate that the rows of vari only contain primitives, so the pattern will make a copy that is safe to modify and will not mutate vari. Were the rows of the vari 2D array contain yet more arrays or other objects, the would be copied by reference and therefore still be subject to mutation.
Note that Array.splice() and Array.slice() are very different from each other. The Array.splice() method mutates its argument array. The Array.slice() method creates a shallow copy of the array, and is in fact often used to safely copy 1D arrays that contain primitives. In your use case, the vari array does not contain primitives but arrays, so we need to call slice() within map() to copy the primitive values in the second level of the 2D array.
In the general case, deep cloning an array or another object is surprisingly complex. The patterns above are probably the simplest way to do it in your use case. See What is the most efficient way to deep clone an object in JavaScript?
So I have a 2-dimensional Array and want to use a "randomBool" function on each of the elements of the elements in the array.
The "randomBool" function just returns a random boolean:
const randomBool = () => Boolean(Math.round(Math.random()));
this would be the 2-dimensional Array, that I would input:
var test = [
["just","some","random","text"],
[1412,"test",1278391]
]
There is a working for-loop nested in a for-loop:
for (let el of test){
for(let i in el){
el[i] = randomBool();
}
}
I tried this:
test.forEach(el => el.map(el2 => randomBool()));
But it didn't work. Why?
You need to use two nested maps.
const randomBools = test.map(outer => outer.map(inner => randomBool()))
forEach is usually intended to iterate over each item in order to perform some kind of side effect without returning anything and without mutating the original array. For example, printing each item to the console.
map, on the other hand, is intended to take an array and return a new array of the same size, with the values transformed in some way, without mutating the original array. For example, uppercase all the words in a list.
Since you want to return a new 2 dimensional from your existing 2 dimension array with some data transformed, you need to nest your map functions. This will map first over the rows (outer), then the columns (inner). The results of the inner maps will be collected into the outer map and you'll be left with a 2 dimensional array with your new values, all without modifying the original array.
I am trying to reverse an array which is an element in an object.
colorKey = {
"2m":["#ffffff","#000000"]
}
colorKey["2mi"] = colorKey["2m"];
Array.reverse(colorKey["2mi"])
This is not working and returning colorKey["2mi"] the same as colorKey["2m"]. When I run the same command in developer console in browser, it reverses successfully. Where is the problem?
This is no static method off Array called reverse. reverse is an instance method (Array.prototype.reverse) off the Array object, so the instance of the Array must be the caller.
This solves your problem:
colorKey = {
"2m":["#ffffff","#000000"]
}
colorKey["2mi"] = colorKey["2m"];
colorKey["2mi"].reverse();
Output:
["#000000", "#ffffff"]
Calling reverse() for an array mutates it (reverse is in place - a new array is not created). What you want, apparently, is to have a reversed copy of the array. Basically, create a new array with the same values and then reverse it.
var a = [1, 2], b;
b = a.slice(0).reverse();
Slice creates a new array with the same elements (but remember that it is not cloning the elements).
#Rajat Aggarwal
What you are asking for, is to clone your previous array in reverse order.
The only trivial part of it would be reversing it. Because there is no way of cloning Objects and Arrays, nor a general method that you could write down as a function to be using it universally.
This specific array from the sample you provided can be cloned easily because it is flat and it only contains primitives. But the solution to it, will be exactly as specific as the sample array provided.
A specific solution to this task would be to use a plain coma-separated string of successive values and convert that to specific arrays of their corresponding primitive values.:
var colors = "#ffffff,#000000";
var colorKey = {
"2m":colors.split(","),
"2mi":colors.split(",").reverse()
}
which will yield you a:
>> colorKey
{
2m : #ffffff,#000000,
2mi : #000000,#ffffff
}
I make _.reduce on all elements of my array,unfortunately sometimes the array is just too big.
I want to make reduce on only constant number of elements from the array.
What do you suggest me to do?
You can use Array.prototype.slice to make a (temporary) copy of the first n elements of the array:
_.reduce(myArray.slice(0, n), ...);
If there are less than n elements in the array it'll just use all of them.
How about using _.first to get the the number of items you want to reduce:
// reduce the first 100 items in the array
var result = _.reduce( _.first(data, 100), fn, memo)