Refactor from Procedural Paradigm to Functional Paradigm - javascript

In the process of learning functional programming, I am trying to refactor the following code using map, filter, and/or reduce.
I see that I can deal with the conditional using the filter method, but don't know how to handle the repeated assignment so I can avoid use of a for loop.
I am thinking that I would use the map method to handle the diff assignments, and chain a filter method that would deal with the conditional. Am I on the right track?
Would someone be kind enough to refactor the following code in the functional paradigm and explain. Thanks.
This function finds the first non-consecutive number in an array.
function firstNonConsecutive (arr) {
var diff = 0;
for(var i = 0; i < arr.length; i++) {
diff = arr[i+1] - arr[i];
if(diff > 1) {
return arr[i+1];
}
}
return null;

Consider using Array.find https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find, like most functional array functions it takes a callback/predicate that takes 3 parameters, the item, the index and the whole array. With this you can look ahead/behind in the same way you currently do. Like this:
function firstNonConsecutive2(arr) {
return arr.find((item, index, array) => {
const diff = array[index - 1] - item; // Use index -1 to look behind instead of ahead since we want to return/find the item that is non-consecutive
return diff === 0; // I think this was a small bug in your version which worked if the numbers were incrementing only
});
}
In the first iteration of the find "loop" it'll try to diff undefined with for example 1, which is NaN, NaN is not equal to 0 so it keeps searching. Next it'll try maybe 1 and 2 so diff becomes -1 so it keeps searching. Until it reaches for example 5 and 5, which diffs to 0 so the find predicate is now true, so it will return the second 5 since that is the current item, we're looking behind us by using index - 1.
Let me know if you want further explanation of something!

If you are looking into fp, then also recursion would find a good application here:
const firstNonConsecutive = (list) => {
if (!list.length) {
// list is empty, not found
return -1;
}
const [head, ...tail] = list;
const [n] = tail;
if (n - 1 !== head) {
// found
return n;
}
// yet another round
return firstNonConsecutive(tail);
};
console.log(
firstNonConsecutive([1, 2, 3, 4, 5, 6, 7, 9, 10]),
);
https://en.wikipedia.org/wiki/Recursion_(computer_science)

You want to use Array.find, not .map, .filter, or .reduce. You could find a way to use those, but they waste time because they don't return as soon as the first match is found.
Here are some progressively more verbose solutions to help you understand how the first one works.
The second is the most functional, because it's declarative unlike the first.
array.find(nonConsecutive) reads close to plain English and declares what you want to do, leaving the imperative implementation details hidden away inside the nonConsecutive function.
const array = [1, 2, 3, 4, 5, 6, 7, 9, 10];
console.log(
array.find((n, i) => i && n != array[i - 1] + 1) // 9
);
const nonConsecutive = (n, i, arr) => i && n != arr[i - 1] + 1;
console.log(
array.find(nonConsecutive) // 9
);
console.log(
array.find(function (number, index) { // we don't need third "array" argument because the array is already in scope.
if (index == 0) return false; // if index is zero, return. Otherwise, next line would access index -1.
if (number != array[index - 1] + 1) return true; // if number is not equal to the the previous number, plus one, it's not consecutive. Return it.
return false; // if we reach this line than the number must be consecutive, so return false.
}) // 9
);

Recursion by mathematical induction -
If the first element, a, or the second element, b, are null, return undefined
(induction) Neither the first element, a, nor the second element, b are null. If b is consecutive to a, return the recursive result of the smaller problem
(induction) Neither the first element, a, nor the second element, b, are null and b is not consecutive to a. Return the answer, b.
const firstNonConsecutive = ([ a, b, ...more ]) =>
a == null || b == null
? undefined // 1
: b === a + 1
? firstNonConsecutive([ b, ...more ]) // 2
: b // 3
console.log(firstNonConsecutive([ 4, 5, 6, 8, 9, 10 ]))
// 8
console.log(firstNonConsecutive([ 7, 8, 9, 10, 13, 14 ]))
// 13
console.log(firstNonConsecutive([ 99 ]))
// undefined
console.log(firstNonConsecutive([]))
// undefined

Related

Javascript recursion related question about push() and unshift() methods working opposite

function countup(n) {
if (n < 1) {
return [];
} else {
const countArray = countup(n - 1);
countArray.push(n);
return countArray;
}
}
console.log(countup(5));
After running the above code it returns an array: [1, 2, 3, 4, 5], but push() adds new values at the end of an array so when value of n was 5 it should push 5 to the end of the array and when value of n got 4 it should push 4 at the end of the array like [5,4].
So why not it is returning [5,4,3,2,1] ? It is hard to understand what is happening in this code probably because of this recursion. Isn’t unshift() (which add new values to start of the array) should return [1,2,3,4,5] and push() [5,4,3,2,1] why opposite is happening?
As #Joseph stated in a comment the second to last function call would get pushed to the array first, then it would return that array, where it immediately adds the next number up the call stack.
Here are the steps being taken.
Initial call enters itself recursively n times, where the bottom call returns [] and then [1], [1, 2] ... [1, 2, ..., n] all the way up the call stack where upon the first function call ends and the program does something else.
To get [n, ..., 2, 1] you need to use the Array.prototype.unshift() method, which takes any javascript primitive type, ie String, Number, Boolean, and Symbols, in a comma separated format, like countArray.unshift(4, 5) or countArray.unshift(...anotherArray), and adds them to the beginning of the array.
ie
let someArr = [3, 2, 1];
someArr.unshift(5, 4);
console.log(JSON.stringify(someArr));
// outputs [5, 4, 3, 2, 1]
or
let someArr = [1, 2, 3];
let anotherArr = [5, 4]
someArr.unshift(...anotherArr);
console.log(someArr);
// outputs [5, 4, 1, 2, 3]
Where the output of
function countup(n) {
if (n < 1) {
return [];
} else {
const countArray = countup(n - 1);
countArray.unshift(n);
return countArray;
}
}
console.log(countup(5));
will be [5, 4, 3, 2, 1] tested with node in Vscode.
One useful way to think about this is to start by imagining you already had a function that does what you want for lower values, and then see how you would write one that works for higher values. That imaginary function should be a black box. All we should know is that it does what we want in the case of lower values. We don't care about its implementation details.
So lets say we had a function imaginaryBlackBox, and we knew that it returned the correct countUp values that we want. So, for instance, we know that imaginaryBlackBox (4) returns [1, 2, 3, 4].
Now, knowing that, how might we write a function that works for an input of 5 as well? How about something like this:
function countup(n) {
if (n < 1) {
return [];
} else {
const countArray = imaginaryBlackBox(n - 1);
countArray.push(n);
return countArray;
}
}
Again, we don't know how imaginaryBlackBox works. We just know that it returns the correct result for lower values of n. Our base case remains obvious. For another case, some n greater than 0, we call imaginaryBlackBox(n - 1), and by our basic assumption, we know that will return [1, 2, 3, ..., (n - 1)], which we store in countArray. Then we push n onto that array, to end up with [1, 2, 3, ..., (n - 1), n]. We return that value and we're done.
Now here's the trick. We know an implementation of imaginaryBlackBox -- it's the function we're writing! So we can simply replace it with countUp and know it will work.
function countup(n) {
if (n < 1) {
return [];
} else {
const countArray = countUp(n - 1);
countArray.push(n);
return countArray;
}
}
This required a few assumptions, and they are important to all recursive functions:
There is at least one base case whose value we can calculate without any recursive call. Here, when n is < 1, we simply return [].
For other cases, we can break down our problem into one or more recursive cases, where the input is in some clear and measurable way closer to a base case, so that subsequent steps will hit a base case in a finite number of calls. Here we reduce n by 1 on every step, so eventually it will be below 1.
Our function is effectively pure: its outputs depend only on its inputs. That means we can't count on changing a global variable or reading from one that might be changed elsewhere. Note that I use the qualifier "effectively" here; it doesn't matter if this function has observable side-effects such as logging to the console, so long as its output is dependent only on its input.
Anytime you have those conditions, you have the makings of a good recursive function.

I'm trying to find the difference between two arrays but my code isn't working for 0s any advice?

here is my code:
function arrayDiff(a, b) {
let newArr = a.concat(b);
let singleVals = newArr.filter(num =>{
if(!a.includes(num) || !b.includes(num))
return num;
})
return singleVals;
}
an example of a result I'm looking for would be
a = [-16,12,5,8]
b = [12,5,8]
result = [-16]
That test would work with my code at the moment, however with something like this
a = [-16,6,19,0,9]
b = [9,-16,6]
my result = [19] when it should be [19,0]
I assume it has something to do with 0 counted as false or something like that. But an explanation would help.
Thanks
You are returning a number in filter but filter likes to get a boolean.
So it evaluates all the values !== 0 to true and evaluate zero to false like you assumed.
When you return now for example 19 then it evaluates to true and it will be in your singleVals Array.
But if you return 0 it evaluates to false and it won't be in your singleVals Array
Here a list of the 6 falsey values if you convert any of those to a boolean it will return false
false
undefined
null
NaN
0
"" (empty string)
You have to return a boolean instead of a number in your filter function.
function arrayDiff(a, b) {
let newArr = a.concat(b);
let singleVals = newArr.filter( num =>{
// it is not including the number
if(!a.includes(num) || !b.includes(num)){
return true;
}else{
return false;
}
})
return singleVals
}
console.log(arrayDiff([3,2,1,0], [3,2,1,4,5])); // expected 0, 4, 5
console.log(arrayDiff([-16,6,19,0,9],[9,-16,6])); // expected 9, 19
More Informations
https://www.samanthaming.com/tidbits/19-2-ways-to-convert-to-boolean/
A more efficient alternative is to use Set in place of the newArr = a.concat(b), since Set never contains duplicates.
function arrayDiff(a, b) {
const uniques = new Set([...a, ...b]);
return Array.from(uniques.values())
.filter(
val => !a.includes(val) || !b.includes(val)
);
}
const a = [-16, 6, 19, 0, 9],
b = [9, -16, 6];
const singleVals = arrayDiff(a, b);
console.log('singleVals:', JSON.stringify(singleVals));
If running time matters...
You didn't ask about this directly, but a number of these solutions contain nested loops, which will blow up on you for larger input arrays. So, in case you want a suggestion that is more efficient in terms of time, I've added this one.
Nested loops are not desirable from an algorithmic efficiency perspective. If a has 10 items, and b has 8 items, and you're looping over every "b" for every "a", your running time will grow as the product of the size of those two arrays, quickly becoming very slow (O(n*m)). You're doing 8*10 = 80 steps to process 18 values. Imagine if it were 100 * 100, or 1000 * 1000...
Here's a solution that returns a result without nested loops, at the cost of creating two sets and an extra array for the results. From what I can see this is no more space than any of the other suggestions would take, and less time:
const a = [-16, 6, 19, 0, 9]
const b = [9, -16, 6]
function diff(a, b) {
// Set creation is O(n) time and space, where n is the length of array a
const aUniques = new Set(a)
// Set creation is O(m) time and space, where m is the length of array b
const bUniques = new Set(b)
// Final array is at worst space O(n + m), but usually much less
const allUniques = []
// A single loop, O(n) time
aUniques.forEach(value => {
// Set lookups are very fast, O(1).
if (!bUniques.has(value)){
allUniques.push(value)
}
})
// A single loop, O(m) time
bUniques.forEach(value => {
if (!aUniques.has(value)){
allUniques.push(value)
}
})
// Final runtime is O(n) + O(m) + O(n) + O(m), or O(2n +2m)
// This grows as O(n+m), which time-wise is much better than O(n*m).
return allUniques
}
console.log(diff(a, b))
Does this matter for small input arrays? No. But it's good to understand the implications of different approaches.
A nice, short article on "big O" notation, if it helps: https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/
You're right, it has to do with zeroes being falsy. Array.prototype.filter expects a function that returns a boolean, not a number.
function arrayDiff(a, b) {
let newArr = a.concat(b);
let singleVals = newArr.filter(num => {
let isMissing = !a.includes(num) || !b.includes(num)
return isMissing
})
return singleVals
}
console.log(arrayDiff([3,4,2,0],[1,2,3,4])); // expected 0, 1

map function to return 1 boolean value, instead of an array of boolean values

Say you have an array like this:
arrayExample = [1, 2, 3, 4, 5, 6, 7]
I want to use the map function to iterate through arrayExample and return true if all the numbers are less than 8, false if they are not. However, when I do this I get an array of trues (like: [true, true, true... etc])
Would I be able to return just 1 value?
Here is my code so far:
var testBoolean = true;
var array = [1,2,3,4,5,6,7];
testBoolean = array.map(m => {
//if a number in the array is >=8, changes the testBoolean to false and return false only
if(m >= 8)
{
return false;
}
//VS code said I had to have a return here, I believe its because I need to have in case m < 8 (edited after reading a comment)
return true;
})
//prints an array [true,true,true,true,true,true,true]
document.write(testBoolean);
I'm a bit new to "map" but I believe it does this since it returns a value for every element, just confused on how to make it so it returns 1 true or false.
The simplest solution is to use .some().
We don't need to check every value. We need to find the first value not less than 8.
const array = [1, 2, 3, 4, 5, 6, 7]
const testBoolean = !array.some(m => m >= 8)
console.log(testBoolean)
For something like this .map() isn't the right tool. .map() is for converting all the elements in your array to new elements (ie: mapping each element to a new transformed value). As a result, .map() will always return an array (unless this behaviour is intentionally modified). Instead, you can use .every() for this, which will tell you if all elements in your array match your condition (ie: if your function returns true for every element, then you'll get true as your result, otherwise you'll get false). The .every() method will terminate early as soon as it finds one element which your callback function returns false for, which can help with efficiency:
const array = [1, 2, 3, 4, 5, 6, 7];
const testBoolean = array.every(m => {
if (m >= 8) {
return false;
}
return true;
});
console.log(testBoolean);
This can be written more concisely, by just returning the result of m < 8 (this will either evaluate to true or false)
const array = [1,2,3,4,5,6,7];
const testBoolean = array.every(m => m < 8);
console.log(testBoolean);
From a more general perspective, if you want to return a single value from an Array, .reduce() is your friend.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
Specifically in your case, you could do
array.reduce(function(accumulator, currentVal, i, arr) {
return accumulator && currentVal < 8;
}, true));
So .reduce() iterates through your array with an initial value true and returns the "previous" value (accumulator) AND whether the current value is less than 8.
You could think of it as
(true && (a[0] < 8 && (a[1] < 8 ... )))
The returned value at each iteration becomes the accumulator of the next. In this way you could not only do math operations but also change an array of a certain shape (e.g. array of {w:0, h:0} objects) to an output of another (e.g. a single number being the sum of all the hypotenuses calculated from each w and h).

JS: Finding unpaired elements in an array

I have the following question (this is not school -- just code site practice questions) and I can't see what my solution is missing.
A non-empty array A consisting of N integers is given. The array contains an odd number of elements, and each element of the array can be paired with another element that has the same value, except for one element that is left unpaired.
Assume that:
*N is an odd integer within the range [1..1,000,000];
*each element of array A is an integer within the range [1..1,000,000,000];
*all but one of the values in A occur an even number of times.
EX: A = [9,3,9,3,9,7,9]
Result: 7
The official solution is using the bitwise XOR operator :
function solution(A) {
var agg = 0;
for(var i=0; i<A.length; i++) {
agg ^= A[i];
}
return agg;
}
My first instinct was to keep track of the occurrences of each value in a Map lookup table and returning the key whose only value appeared once.
function solution(A) {
if (A.length < 1) {return 0}
let map = new Map();
let res = A[0]
for (var x = 0; x < A.length; x++) {
if (map.has(A[x])) {
map.set(A[x], map.get(A[x]) + 1)
} else {
map.set(A[x], 1)
}
}
for ([key,value] of map.entries()) {
if (value===1) {
res = key
}
}
return res;
}
I feel like I handled any edge cases but I'm still failing some tests and it's coming back with a 66% correct by the automated scorer.
You could use a Set and check if deletion deletes an item or not. If not, then add the value to the set.
function check(array) {
var s = new Set;
array.forEach(v => s.delete(v) || s.add(v));
return s.values().next().value;
}
console.log(check([9, 3, 9, 7, 3, 9, 9])); // 7
You're not accounting for cases like this:
[ 1, 1, 2, 2, 2 ] => the last 2 is left unpaired
So your condition should be if ( value % 2 ) instead of if ( value === 1 ).
I think also there is not much benefit to using a Map rather than just a plain object.
The official solution works due to the properties of the bitwise XOR (^), namely the fact that a ^ a == 0, a ^ 0 == a, and that the operation is commutative and associative. This means that any two equal elements in the array will cancel each other out to become zero, so all numbers appearing an even amount of times will be removed and only the number with an odd frequency will remain. The solution can be simplified using Array#reduce.
function findOdd(arr) {
return arr.reduce((a,c)=>a ^ c, 0);
}
You need not to make a count of each and traverse again, if you are sure that there will be exactly one number which will occur odd number of times. you can sum the array and do + when odd entry and - when even entry (to dismiss it back) and in the hash (map or object) you can just toggle for subsequent entry of each number.
Here is an example:
let inputArray1 = [10,20,30,10,50,20,20,70,20,70,50, 30,50], //50 -> 3 times
inputArray2 = [10,20,30,20,10], //30 -> 1 time
inputArray3 = [7,7,7,7,3,2,7,2,3,5,7]; //5 -> 1 time
let getOddOccuence = arr => {
let hash = {};
return arr.reduce((sum, n) => sum + ((hash[n] = !hash[n]) ? n : -n), 0);
}
console.log('Input Array 1: ', getOddOccuence(inputArray1));
console.log('Input Array 2: ', getOddOccuence(inputArray2));
console.log('Input Array 3: ', getOddOccuence(inputArray3));
In case the input contains multiple or no numbers having odd number of occurance (if you are not sure there) then you have already the hash (and you can ignore performing sum) and return the keys of hash (where value is true (and not checking with %2 and then consider as truthy or false in case of you have count))
function solution(A) {
let result = 0;
for (let element of A) {
// Apply Bitwise XOR to the current and next element
result ^= element;
}
return result;
}
const unpaired = solution([9, 3, 9, 3, 9, 7, 9]);
console.log(unpaired);
Source: https://gist.github.com/k0ff33/3eb60cfb976dee0a0a969fc9f84ae145

How to understand this recursion

Hello all I am trying to understand this solution to combination sum.
function combinationSum(candidates, target) {
var result = [];
if ((candidates == null) || (candidates.length == 0)) {
return result;
}
var cur = [];
candidates = candidates.sort((a, b) => a - b)
csh(candidates, target, cur, result, 0);
return result;
};
function csh(cand, target, cur, result, j) {
//console.log(cur);
if (target == 0) {
var temp = cur.slice();
result.push(temp);
return;
}
for (var i = j; i < cand.length; i++) {
if (target < cand[i]) {
return;
}
//console.log(cur);
cur.push(cand[i]);
console.log(cur);
csh(cand, target - cand[i], cur, result, i);
cur.pop();
}
}
https://leetcode.com/problems/combination-sum/description/
While I understand the basic principles of recursion this problem is a little bit lost on me. So for example for the input:
candidates = [2,3,6,7]
target = 7
When you first enter the function cur is empty so our first iteration is:
[],
[2],
[2,2]
And then we keep adding cand[i] which is currently 2
[2,2,2]
However at this point, target = 1 which is less than cand[i] which is 2 so we return. And since we're returning we pop off the stack which pops the last 2 off the stack. Since we've returned we increment i and then we add 3 to cur
[2,2,3]
Since our target array is equal to 0 we now return again and my question is, at this point do we keep returning until cur is empty and continue the function like the following?
[2,2]
[2]
[]
[6]
[]
[7]
I'm just trying to understand what is being done in this function.
target is local to each invocation of the function. target is 0 only for some invocations of the function. Notice that the recursive invocation is:
csh(cand, target - cand[i], cur, result, i);
The target in that scope has not change, but the invocation of csh currently being entered will have a lower value for its target. When that function returns and the program flow reenters that other level, we resume using the higher value of target, insted of the reduce value target - cand[i] that was supplied to the subcall.
The algorithm will try all other possibilities on the [2,2,...] path as well, before changing the second element to the next alternative. Then it will explore the [2,3,...] space, and the [2,6,...] space, and ultimately all the [3,...], [6,...] and [7,...] possibilities as well.
The algorithm always goes as deep as a possible (i.e., as long of an array as possible) when it can do so without going over the original limit.
Note that it does not produce [2,3,2], because an earlier candidate cannot come after a later one (so a 2 can never be subsequent to a 3 in a result). It enforces this by making the for look start at i = j, where j is the array-depth of the last candidate used, so when the result-in-progress ends with the nth candidate, it only considers nth and higher candidates. this is of practical value because the algorithm only returns one permutation of each value result set: [2,2,3] and [2,3,2] contain the same set of values.
I completely understand that recursion can be very difficult to understand and to explain as well, but here is my take on it.
When csh is called for the 1st time, this is what is being passed
csh(cand, 7, [], [], 0)
Now from the for loop, i = 0, function called is
csh(cand, 5, [2], [], 0)
from the loop, i = 0, function called is
csh(cand, 3, [2,2], [], 0)
from the loop, i = 0, function called is
csh(cand, 1, [2,2,2],[],0)
from the for loop, target(1) < cand[0](2), so return to step Step 4 and pop the last 2 from [2,2,2] resulting in [2,2]
from the loop i = 1, function called is
csh(cand, 0, [2,2,3], [], 1)
here, target == 0 condition is met. so, [2,2,3] is pushed in the result. and then return to step 4. again, 3 is popped from [2,2,3].
from the loop i = 2, target(3) < cand[2](6), so return to step 3. and pop 2 from [2,2] resulting in [2].
from the loop i = 1, function called is
csh(cand, 2, [2,3], [[2,2,3]], 1)
from the loop i = 1, target(2) < cand[1](1) so return to step 9.
and so no...
Basically, each and every combination will be checked.
[2,2,2]
[2,2,3] --> added to result
[2,3,3]
[2,6]
[2,7]
[3,3,3]
[3,6]
[3,7]
[6,6]
[6,7]
[7] --> added to res

Categories

Resources