Related
I would like to find out if swapping elements can be done using only .reduce in JavaScript. If not, what else should be used from the functional programming land?
This is not for sorting an array. I wanted to find all the permutations of the array element using .reduce which required the swap step as per this method.
You could take a function which takes an array and two indices and uses a destructuring assignment.
const swap = (array, i, j) => [array[i], array[j]] = [array[j], array[i]];
var array = [1, 2, 3];
swap(array, 0, 1)
console.log(array);
A version with reduce by taking an array of indices and swap all pairs from start to end.
const
swap = (array, ...indices) =>
indices.reduce((a, b) => ([array[a], array[b]] = [array[b], array[a]], b));
var array = [1, 2, 3];
swap(array, 0, 1)
console.log(array);
In es6, the idiomatic way to swap array elements is:
;[a[i], a[j]] = [a[j], a[i]]
Using .reduce is not appropriate for this task. You could technically do something like this:
a = a.reduce((acc, element, idx) => {
acc.push(idx === i ? a[j] : idx === j ? a[i] : a[idx])
return acc
}, [])
but it would result in convoluted code.
If your goal is to avoid mutating the original array, you can use Object.assign:
b = Object.assign([], a, {[i]: a[j], [j]: a[i]})
The reduce function reduces the array to a value of an object, as defined by the accumulator.
let array1 = [2, 5, 8, 0, 10];
let array2 = [1, 4, 9, 7, 6];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
The sort() method sorts the elements of an array in place and returns the array. The default sort order is built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.
var months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months);
// expected output: Array ["Dec", "Feb", "Jan", "March"]
const sortingAccending = (a, b) => a - b
let numbers = [4, 2, 5, 1, 3];
numbers.sort(sortingAccending);
console.log(numbers);
// expected output: Array [1, 100000, 21, 30, 4]
And you answer your question, reduce can't be used for swapping elements.
You will have to either use sort for write your custom sort function
After reading the docs on map method,I still cannot get this to work.
I am trying to use map to get the average of every pair of numbers in an array.Please help me understand whats wrong.
function getAverage(num1,num2){return Math.ceil((num1+num2)/2)};
function a(input){ var b = input.map(getAverage(num1,num2)); return b; }
a([1,2,3,4]) //error num1 is not defined
//expected [2,4]
map projects a function to each element of a list/array, it simply "maps" a function over all the items.
[1, 2, 3].map(function (number) { return number + 1; });
// -> [2, 3, 4]
Therefor, first you need to have pairs of items in your "input" array, so it looks like this:
var numberPairs = [[1, 2], [3, 4]]
Until now, all you have are just single numbers but no pairs.
After conversion, you can use map like this:
numberPairs.map(function (pair) {
return Math.ceil((pair[0] + pair[1]) / 2);
});
This will give:
[2, 4]
as a result.
You can't calculate the average using a map. A mapping pass a function to each element and then returns an array with the same shape. That is not the case, you want to get a value from an array, and you can use the reduce method for that.
// adds two number
const adder = (a,b) => a + b;
// reduces the array adding all numbers and divides
// by the array length
const getAverage = (arr) => arr.reduce(adder)/arr.length;
// outputs 2.5
console.log(getAverage([1,2,3,4]))
You can use reduce() instead of map() to aggregate averages of every n values in the array:
const sum = array => array.reduce((a, b) => a + b, 0)
const getAverage = n => (averages, value, index, array) => index % n === 0
? [...averages, Math.ceil(sum(array.slice(index, index + n)) / n)]
: averages
const result = [1, 2, 3, 4].reduce(getAverage(2), [])
console.log(result)
Hi so I'm having trouble figuring out why my function will do the division but leave the multiplication as an array without completing the math. Here's the code:
const mathObj = {
"*": function(a , b) {return a * b},
"/": function(a , b) {return a / b},
"+": function(a , b) {return a + b},
"-": function(a , b) {return a - b}
}
const arr = [ 10, "/" , 2 , "*" , 10 , "/" , 2 ];
function solveTheMath(arr) {
const len = arr.length;
for(let i = 0 ; i < len ; i++){
if(arr[i] === "/" || arr[i] === "*"){
const sliced = arr.slice(i - 1 , i + 2);
var mathResult = mathObj[arr[i]](sliced[0], sliced[2]);
arr.splice(i - 1 , 3, mathResult);
console.log(arr);
//[5*5]
}
}
}
solveTheMath(arr);
Why doesn't the multiplication work but the division does?
My initial answer, while it did solve the issue, wasn't that correct. You wanted to use an iterative approach by the look of things (i.e. using a loop to navigate through the initial array and solve all operations before returning the result).
So I replied to you:
Both operations work, the problem is that you're only calling solveTheMath once.
You need to call your function again to solve the array you have constructed. If the constructed array is made of only one element that means the process has reached the end of the computing, you can, therefore, return the first (and only element) of the array.
You are solving the problem in a recursive manner:
const mathObj = {
"*": function(a , b) {return a * b},
"/": function(a , b) {return a / b},
"+": function(a , b) {return a + b},
"-": function(a , b) {return a - b}
}
const arr = [ 10, "/" , 2 , "*" , 10 , "/" , 2 ];
function solveTheMath(arr) {
const len = arr.length;
for(let i = 0 ; i < len ; i++){
if(arr[i] === "/" || arr[i] === "*"){
const sliced = arr.slice(i - 1 , i + 2);
var mathResult = mathObj[arr[i]](sliced[0], sliced[2]);
arr.splice(i - 1 , 3, mathResult);
if(arr.length == 1) {
return arr[0]; // <- no more calculations needed, return result
} else {
return solveTheMath(arr); // <- more calculations needed, call it again
};
}
}
}
console.log(solveTheMath(arr))
But actually that isn't correct, you can use both approaches: recursive and iterative to solve this problem. My initial answer provided a poor solution: I kept your for loop and called the function again to solve the remaining operations that were in the array. That wasn't necessary because the for loop only looped to find the second item and stopped. Anyway, here's a clearer answer, highlighting both approaches.
Note: I have renamed solveTheMath to calculate and mathObj to operations.
Iterative approach
That's the approach you went for with your question. Because you are using a for loop to calculate all operations on a single function call (so the function isn't calling itself over and over).
I recommend using a while loop for this because **you will have a hard time looping arr when it gets modified (you are replacing three elements with one on each loop).
I'll take the array [10, "/", 2, "*", 10, "/", 2] as the starting array to show the process step by step. You can solve the first operation of the provided array. For example, given: , calculate will calculate the first operation here: 10, "/", 2
While the array contains more than one element we will do the following:
the first three elements of the array contain: two factors and an operator sign. By slicing the array we can extract those values and save them. I'm using a destructuring assignment to make it more verbose:
const [a, operator, b] = arr.slice(0, 3);
here a = 10, operator = "/" and b = 2
we will calculate the result of this operation with this line:
const result = operations[operator](a, b);
result = 5 (cf: 10 / 2)
then replace the first three elements of the array with the integer result:
arr.splice(0, 3, result);
at this point, arr is equal to [5, "*", 10, "/", 2]
The block has been executed, the while condition is checked again. arr does contain more than one element so the block is executed again. Remember, at this point arr is equal to [5, "*", 10, "/", 2], not to [10, "/", 2, "*", 10, "/", 2] (we are making progress in the calculation).
At the end of the second loop we have arr that is equal to [50, "/", 2].
A loop after that it is equal to [25].
The while condition isn't met anymore because arr only contains one element, the while loop has stopped and the result can be returned.
const operations = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b
}
const calculate = arr => {
while(arr.length > 1) { // <- while there are calculations to be done, repeat this block
const [a, operator, b] = arr.slice(0, 3);
const result = operations[operator](a, b);
arr.splice(0, 3, result);
}
return arr[0]; // <- no more operations to be done, return result
}
console.log(calculate(
[10, "/", 2, "*", 10, "/", 2]
));
Recursive approach
We can use a recursive approach: the function will only calculate the first operation of the provided array and return a new array with the result of this first operation.
Here is an example:
Same as in the iterative array, given the input [10, "/", 2, "*", 10, "/", 2] we will first take the first two factors and operator sign by slicing the array. Then we will calculate the result of the operation. Finally, we'll replace the first three elements of the array with this result:
const [a, operator, b] = arr.slice(0, 3);
const result = operations[operator](a, b);
arr.splice(0, 3, result);
then we check the length of this array:
if it contains only one element, it can be returned
else if it doesn't (in our case) we call the function again (this time on [5, "*", 10, "/", 2]).
So the function runs again with a new input and arr becomes [50, "/", 2] which has more than one element so the function needs to be called again (with [50, "/", 2] as input)
Now, arr is [25] it only contains one element, the result can be returned (25).
const operations = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b
}
const calculate = arr => {
const [a, operator, b] = arr.slice(0, 3);
const result = operations[operator](a, b);
arr.splice(0, 3, result);
if (arr.length == 1) {
return arr[0]; // <- no more calculations needed, return result
} else {
return calculate(arr); // <- more calculations needed, call it again
}
}
console.log(calculate(
[10, "/", 2, "*", 10, "/", 2]
));
Going further...
You can see both methods are quite similar: the main process is the same but the way they handle the end of execution is different. In this case, both are reasonable to use. The iterative approach may seem more natural to you at first. However remember that recursion can allow you to solve more complicated problems. For example, if you would like to implement a kind parenthesis system in your function:
How would you go about calculating: 10*(2+2)/2? calculate([10, "*", 2, "+", 2, "/", 2]) would obviously return 11...
Take the input [[10, "+", 2], "/", 2] instead, that makes more sense! How can we calculate the correct result?
Well with our recursive approach this can be implemented quite easily: if a or/and b are arrays then we reassign them by calling calculate on them. That's all:
if(a.constructor == Array) {
a = calculate(a);
}
if(b.constructor == Array) {
b = calculate(b);
}
const operations = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b
}
const calculate = arr => {
let [a, operator, b] = arr.slice(0, 3);
if(a.constructor == Array) {
a = calculate(a);
}
if(b.constructor == Array) {
b = calculate(b);
}
const result = operations[operator](a, b);
arr.splice(0, 3, result);
if (arr.length == 1) {
return arr[0]; // <- no more calculations needed, return result
} else {
return calculate(arr); // <- more calculations needed, call it again
}
}
console.log(calculate(
[[10, "+", 2], "/", 2]
));
Adding those two if blocks in the while loop of the iterative approach would have worked. But then you would be left with a... recursive function. That's why you may want to go straight off with the recursive approach. That allows you to expand your code more easily.
More reference on recursion
Recursion in JavaScript, freecodecamp.org
How factorial's recursive implementation works, wikimedia
Understanding recursive functions in JavaScript, medium.com
Scope and the function stack: recursion, MDN web docs
Hello I have an array as follows:
[[1,1],[2,8],[3,12],[4,13],[5,23],[6,30],[7,41],[8,44],[9,50]]
So lets say it is the format of [[x,y]]
As you can see the above array, the first element inside inner arrays goes from 1,2...6 which is x, and the second element of inner arrays are 1,8,12,13,23 and 30 which is y.
This is a cumulative array of y and I want to convert it into non-cumulative but with a twist.
I want to convert y to non-cumulative array by getting the difference from the value of y at last 3rd value of x
Therefore I want the final result to be as follows:
[[1,1],[2,8],[3,0],[4,1],[5,11],[6,0],[7,11],[8,14],[9,0]]
I have tried a fiddle here:
https://jsfiddle.net/gxytx6ka/1/
So far I have been able to get the difference between two array elements by some help of Dekel (stackoverflow guy) as you can see in the above fiddle:
$(document).ready(function() {
var old_arr = [ [1, 1], [2, 8], [3, 12], [4, 13], [5, 23], [6, 30], [7, 41], [8, 44], [9, 50] ];
var new_arr = old_arr.reduce(function(a, b, c, d) {
if (a.length) {
a.push([d[c][0], d[c][1] - d[c - 1][1]])
} else {
a = [b]
}
return a;
}, []);
console.log(new_arr);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
which outputs:
[[1,1],[2,7],[3,4],[4,1],[5,10],[6,7],[7,11],[8,3],[9,6]]
but I want:
[[1,1],[2,8],[3,0],[4,1],[5,11],[6,0],[7,11],[8,14],[9,0]]
which I am not able to get.
I tried using a%3==0 in if condition, but not working...
Basically I want the final output in an efficient way.
Why reduce? A simple loop does the job perfectly fine:
let a = [[1,1],[2,8],[3,12],[4,13],[5,23],[6,30],[7,41],[8,44],[9,50]];
let out = [],
n = 0,
lastY = 0;
for (let [x, y] of a) {
if (++n % 3 == 0)
lastY = y;
out.push([x, y - lastY]);
}
console.log(JSON.stringify(out))
You could use Array#map, which returns simply an array with the result.
var array = [[1,1], [2,8], [3,12], [4,13], [5,23], [6,30], [7,41], [8,44], [9,50]],
result = array.map(function (a, i) {
(i + 1) % 3 || (this.last = a[1]);
return [a[0], a[1] - this.last];
}, { last: 0 });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6
var array = [[1,1], [2,8], [3,12], [4,13], [5,23], [6,30], [7,41], [8,44], [9,50]],
result = array.map((l => ([x, y], i) => (++i % 3 || (l = y), [x, y - l]))(0));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
It is possible to do this with reduce — You just need to figure out when to zero-out every third value and when to find the difference between the current value and it's previous (third) value.
Naming parameters with more intelligible names also helps out.
var old_arr = [ [1, 1], [2, 8], [3, 12], [4, 13], [5, 23], [6, 30], [7, 41], [8, 44], [9, 50] ];
var new_arr = old_arr.reduce(function(result, value, index, arr) {
if (index % 3 == 2) {
return result.concat([[value[0], 0]]); // Set every third value to 0.
} else {
var prevIndex = index - (index % 3) - 1;
var prevVal = prevIndex > -1 ? arr[prevIndex][1] : 0;
return result.concat([[value[0], Math.abs(prevVal - value[1])]]);
}
}, []);
console.log(JSON.stringify(new_arr)); // [[1,1],[2,8],[3,0],[4,1],[5,10],[6,0],[7,11],[8,3],[9,0]]
With reduce you can do it like this (also using ES6 arrow function and spread operator):
var arr = [[1,1], [2,8], [3,12], [4,13], [5,23], [6,30], [7,41], [8,44], [9,50]];
var result = arr.reduce( ([arr, v], [x, y], i) =>
[[...arr,[x, i%3==2 ? 0 : y-v]], i%3==2 ? y : v], [[],0])[0];
console.log(result);
Explanation
reduce is called with a double initial value (a pair):
[[],0]
The first element is the array that will accumulate into the final result, and the second value is the current value to which subsequent y values will be offset.
These two values are captured by the argument [arr, v] which is a destructuring assignment (ES6). The reduce callback will always return a new pair like that, with extended arr and (potentially) updated v.
The callback function also takes the current [x, y] pair from the original array. Again this is a destructuring assignment, so you have direct access to x and y variables. The third argument i is the current, zero-based index in the original array.
As said, the callback returns a pair. The first element in that pair is constructed as follows:
[...arr, [x, i%3==2 ? 0 : y-v]]
The ...arr notation spreads the previously collected result elements as a list, and one pair is added to that list: [x, i%3==2 ? 0 : y-v]. The x is just reproduced from the original [x, y], but the y follows the requested logic with the ternary operator: if we are at an index that has a remainder of 2 when divided by 3, then the y value should be 0. Otherwise, it should be offset against the previously set value v.
The second element in the pair, must be the new value for v:
i%3==2 ? y : v
Again, according to the requested logic, v remains unchanged when the remainder is not 2, but otherwise it is set to the current value of y.
So reduce will thus accumulate/update these two values in each iteration, and finally return a pair, of which only the first has our interest, which is why there is [0] at the end.
Notes
As you seemed to be looking for a reduce implementation, that is what I went with, but whenever your output array has just as many elements as your input array (like is the case here) you can also consider to use map as a good alternative (See answer that NinaScholz posted).
If you are open for a less functional programming way, you can also choose to use a for loop and maybe gain a bit of performance.
I'm using the following function to add specific numbers into an array that I later want to be assigned to a variable. For this I'm using two for loops, but I feel like there is a more succinct way to do it. I tried merging the two loops in one without getting an error, but the output is not the same.
Working Example:
function fill () {
var array = [];
for (var index = 0; index < arguments.length; index++) {
for (var number = arguments[index][0]; number <= arguments[index][1]; number++)
array.push(number);
}
return array;
};
/* Use */
var keys = fill([1, 10], [32, 34]);
/* Output */
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32, 33, 34]
Merged Example:
function fill () {
var array = [];
for (var index = 0, number = arguments[index][0];
index < arguments.length && number <= arguments[index][1];
index++ && number++) {
array.push(number);
}
return array;
};
/* Use */
var keys = fill([1, 10], [32, 34]);
/* Output */
[1, 1]
Is it possible to actually merge the two loops into one? If not, is there a way to write the foregoing function in less code?
Your code in the first example is fine. There is no real "clean" way to remove the nested loops.
You could iterate over them with forEach, but then you'd still have nested loops, even if one of them is disguised as a function call:
function fill () {
var array = [];
Array.prototype.slice.apply(arguments) // Make an array out of arguments.
.forEach(function(arg){
for (var number = arg[0]; number <= arg[1]; number++){
array.push(number);
}
});
return array;
};
console.log(fill([1, 10], [32, 34]));
And you'd have to use Array.prototype.slice.apply to convert arguments to an actual array. (which is ugly)
So, basically, nested loops aren't necessarily "evil". Your first example is as good as it gets.
JavaScript is a functional language. For the sake of modern coding purposes a functional approach is best for the coder's benefit.
var fillArray = (...args) => args.reduce((res,arg) => res.concat(Array(...Array(arg[1]-arg[0]+1)).map((e,i) => i + arg[0])),[]),
filled = fillArray([1, 10], [32, 34]);
console.log(filled);
OK what happens here.. It's very simple. We do the job by fillArray function. fillArray function takes indefinite number of arguments. So we collect them all in an array called args by utilizing the ES6 rest operator ....
var fillArray = (...args)
Now that we have our source arrays in the args array we will apply a reduce operation to this array with an initial value of an empty array (res). What we will do is.. as per each source (arg) array we will create a new array and then we will concatenate this to the res array. Ok we receive [1,10] as source which means we need an array of length arg[1]-arg[0]+1 right. So comes
Array(...Array(arg[1]-arg[0]+1))
we could also do like Array(arg[1]-arg[0]+1).fill() same thing. We now have an array filled with "undefinite" in the needed length. Then comes map. This is really very simple as we apply to this undefinites array like
.map((e,i) => i + arg[0]))
which means each item will be the current index + offset which is the arg[0]
Then we concatenate this array to our results array and pass to the next source array. So you see it is very straight forward and maintainable.
You might not be able to escape the two loops, but that shouldn't necessarily be a goal either. The loop themselves aren't really harmful – it's only if you're iterating over the same data multiple times that you might want to reconsider your code
Consider this entirely different approach
const range = (x , y) =>
x > y
? []
: [ x, ...range (x + 1, y) ]
const concat = (xs, ys) =>
xs .concat (ys);
const flatMap = (f, xs) =>
xs .reduce ((acc, x) => concat (acc, f (x)), [])
const apply = f => xs =>
f (...xs)
const fill = (...ranges) =>
flatMap (apply (range), ranges);
console.log
(fill ( [1,10]
, [32,34]
, [40,49]
, [100,100]
)
)
So yes, #Redu is on the right track with "JavaScript is a functional language", but I think his/her answer falls short of delivering a well-composed functional answer.
The answer above shows how functions with individualized concerns can be easy to read, easy to write, and easy to combine to achieve complex computations.
In ES6, you could use the rest operator and build a new array, based on the items.
function fill(...p) {
return p.reduce((r, a) => r.concat(Array.apply(null, { length: a[1] - a[0] + 1 }).map(() => a[0]++)), []);
};
var keys = fill([1, 10], [32, 34]);
console.log(keys);
Similar to another answer, but a little more complete:
const arrays = [[1,10],[32,34],[9,12]]
const range = (a,b) => a >= b ? [] :
[...Array(b-a).keys()].map(i => i+a)
const result = arrays.reduce( (a,c) =>
a.concat( range(c[0], c[1]+1) ), [] )
// => [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32, 33, 34, 9, 10, 11, 12 ]
If you prefer a more traditional range function, then:
const arrays = [[1,10],[32,34],[9,12]]
function range(a,b) {
var arr = []
for (let i = a; i < b; i++)
arr.push(i)
return arr
}
const result = arrays.reduce( function(a,c) {
return a.concat( range(c[0], c[1]+1) )
}, [] )
After almost 2 years and some great answers that were posted to this thread proposing interesting alternatives, I found a way to merge the two loops into one, but it ain't pretty!
Code:
function fill () {
var
ar = [],
imax = arguments.length,
/* The functions that calculate the bounds of j. */
jmin = i => arguments[i][0],
jmax = i => arguments[i][1] + 1;
for (
let i = 0, j = jmin(i);
i < imax && j < jmax(i);
/* If j reaches max increment i and if i is less than max set j to min. */
ar.push(j++), (j == jmax(i)) && (i++, (i < imax) && (j = jmin(i)))
);
return ar;
};
/* Use */
var keys = fill([1, 10], [32, 34], [76, 79]);
console.log.apply(console, keys);