Array.reduce mutating state outside is not recommended? - javascript

Why is mutating variables outside .reduce() method considered bad practice? In the example below I am mutating a variable declared outside from inside the reduce method. Why is it not recommended to do so?
function balanceParens(string) {
let max = 0;
let res = string.split("").reduce((counter, char) => {
// Handle if parens open and close out of order
if (counter < 0) {
return counter;
}
// Add 1 for each open in order
if (char === "(") {
if(++counter > max) {
max = counter;
}
return counter;
}
// subtract 1 for each close in order
if (char === ")") {
return --counter;
}
// handle use case if char is not a paren
return counter;
}, 0);
console.log("Max depth was :", max);
return !res;
}
console.log(balanceParens("((()(((())))))((((()))))"));

Why is mutating variables outside .reduce() method considered bad practice?
Because you are mixing the functional with the imperative approach. Keep it to a single paradigm instead of confusing everyone.
You would go with
either reduce with a pure callback and no side effects
function step({max: oldMax, counter: oldCounter}, char) {
const counter = oldCounter + (char=="(") - (char==")");
const max = counter > oldMax ? counter : oldMax;
return {max, counter};
}
function maxParensLevel(string) {
assert(hasBalancedParens(string));
const {max, counter} = string.split("").reduce(step, {max:0, counter:0});
return max;
}
or a simple loop and two mutable variables
function maxParensLevel(string) {
assert(hasBalancedParens(string));
let max = 0;
let counter = 0;
for (const char of string.split("")) {
if (char == "(")
counter++;
else if (char == ")")
counter--;
if (counter > max)
max = counter;
}
return max;
}
but not both.

See #JordanRunning comment:
"If you do that, why bother using reduce at all? If your output is via side-effects you might as well be using forEach"
I was about to say the same. And here is a version of the function without an external dependency:
function balanceParens(string) {
let res = string.split("").reduce((state, char) => {
// Handle if parens open and close out of order
if (state.counter < 0) {
return state;
}
// Add 1 for each open in order
if (char === "(") {
if (++state.counter > state.max) {
state.max = state.counter;
}
return state;
}
// subtract 1 for each close in order
if (char === ")") {
state.counter = --state.counter;
return state;
}
// handle use case if char is not a paren
return state;
}, {max: 0, counter: 0});
console.log("Max depth was :", res.max);
return !res.counter;
}
console.log(balanceParens("((()(((())))))((((()))))"));

Related

Trying to solve sliding window median problem in leetcode

I am working on LeetCode code challenge 480. Sliding Window Median:
You are given an integer array nums and an integer k. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
Return the median array for each window in the original array. Answers within 10-5 of the actual value will be accepted.
I submitted my code, but it fails on test cases. I suspect that there is a problem in this part of my code:
const medianSlidingWindow = (array, window) => {
let start = 0;
let end = window - 1;
const min = new MinHeap(array);
const max = new MaxHeap(array);
const insert = (index) => {
if(max.size === 0){
max.push(index);
return;
}
(array[index] >= max.peak) ? min.push(index) : max.push(index);
balance();
}
const balance = () => {
if(Math.abs(max.size - min.size) >= 2){
const returned = (max.size > min.size) ? max.pop() : min.pop();
(max.size > min.size) ? min.push(returned) : max.push(returned);
}
}
const remove = (index) => {
(max.has(index)) ? max.pop(index, true) : min.pop(index, true);
balance();
}
const next = () => {
remove(start++);
insert(++end);
}
const getMedian = () => {
if(window % 2 === 0) return (max.peak + min.peak)/2;
return (max.size > min.size) ? max.peak : min.peak;
}
for(let i = 0; i <= end; i++){
insert(i);
}
const ret = [];
while(end < array.length){
ret.push(getMedian());
next();
}
return ret;
}
Here is the full code:
class MaxHeap{
#array = [];
#size = 0;
#reference = [];
#map = new Map();
constructor(reference = []){
this.#reference = reference;
}
get size(){
return this.#size;
}
/* Debug */
get array(){
return this.#array;
}
get peak(){
return this.get(0);
}
get(index){
if(index === null || index < 0 || index >= this.#array.length) return null;
return this.#reference[this.#array[index]];
}
has(indexReference){
return this.#map.has(indexReference);
}
swap(indexA, indexB){
let temp = this.#map.get(this.#array[indexA]);
this.#map.set(this.#array[indexA], indexB);
this.#map.set(this.#array[indexB], temp);
[this.#array[indexA], this.#array[indexB]] = [this.#array[indexB], this.#array[indexA]];
}
sink(index){
let currentIndex = index;
let greterChild;
while((this.get(greterChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 1 : 2*currentIndex + 2) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
this.swap(currentIndex, greterChild);
currentIndex = greterChild;
}
}
bubble(index){
let currentIndex = index;
let parent;
while((this.get(parent = Math.ceil((currentIndex - 2)/2)) ?? Number.MAX_SAFE_INTEGER) < this.get(currentIndex)){
this.swap(currentIndex, parent);
currentIndex = parent;
}
}
push(...char){
if(char[0].constructor === Array) char = char.flat();
for(let i = 0; i < char.length; i++){
this.#array.push(char[i]);
this.#map.set(char[i], this.#array.length - 1)
this.bubble(this.#array.length - 1);
this.#size++;
}
}
pop(index = 0, fromReference = false){
const ret = (fromReference) ? index :this.#array[index];
if(fromReference) index = this.#map.get(index);
this.swap(index, this.#array.length - 1);
this.#map.delete(ret);
this.#array.pop();
this.sink(index);
this.#size--;
return ret;
}
}
class MinHeap extends MaxHeap{
constructor(reference = []){
super(reference);
}
get size(){
return super.size;
}
get peak(){
return super.peak;
}
/* Debug */
get array(){
return super.array;
}
bubble(index){
let currentIndex = index;
let parent;
while((this.get(parent = Math.ceil((currentIndex - 2)/2)) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
this.swap(currentIndex, parent);
currentIndex = parent;
}
}
sink(index){
let currentIndex = index;
let lesserChild;
while((this.get(lesserChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 2 : 2*currentIndex + 1) ?? Number.MAX_SAFE_INTEGER) < this.get(currentIndex)){
this.swap(currentIndex, lesserChild);
currentIndex = lesserChild;
}
}
}
const medianSlidingWindow = (array, window) => {
let start = 0;
let end = window - 1;
const min = new MinHeap(array);
const max = new MaxHeap(array);
const insert = (index) => {
if(max.size === 0){
max.push(index);
return;
}
(array[index] >= max.peak) ? min.push(index) : max.push(index);
balance();
}
const balance = () => {
if(Math.abs(max.size - min.size) >= 2){
const returned = (max.size > min.size) ? max.pop() : min.pop();
(max.size > min.size) ? min.push(returned) : max.push(returned);
}
}
const remove = (index) => {
(max.has(index)) ? max.pop(index, true) : min.pop(index, true);
balance();
}
const next = () => {
remove(start++);
insert(++end);
}
const getMedian = () => {
if(window % 2 === 0) return (max.peak + min.peak)/2;
return (max.size > min.size) ? max.peak : min.peak;
}
for(let i = 0; i <= end; i++){
insert(i);
}
const ret = [];
while(end < array.length){
ret.push(getMedian());
next();
}
return ret;
}
What went wrong:
On the 30th testcase of the problem (link: https://leetcode.com/problems/sliding-window-median/submissions/859041571/), it resolves to a wrong answer but when I pick one of the windows that resolves to a wrong answer it gives me a correct answer. I'm currently confused because two of the heaps are fairly balanced (as one heap doesn't exceed above one element) and I've tested my heap that both seem to work perfectly. It will be very helpful if somebody helps me.
Link to SO questions I've followed:
How to implement a Median-heap
There are these problems in your heap implementation:
The get function will return null when an out of range index is given, which means the while condition in your sink method could sometimes choose an non-existing child (when there is only one child). Note that a numerical comparison with null will treat that null as 0, and depending of the sign of the value you compare it with can give false or true.
For example, your code fails this test case for that reason:
nums=[1,2,3,4]
k=4
You can fix this by returning undefined instead of null. Then also make sure that the false side of the comparison operator is the one with +1 (choosing the left child), while the true side takes the other child.
The pop method, when called with true for the second argument, does not guarantee to restore the heap property. It takes care of sinking the value at the given index, but does not consider the case where this value should actually bubble up!
For example, your code fails this test case for that reason:
nums=[10,6,5,2,3,0,8,1,4,12,7,13,11,9]
k=11
Here is a simplified example where I depict a min-heap with the referenced values:
5
/ \
8 6
/ \ /
10 12 7
If the node with value 10 is to be removed, the swap action will give this min-heap (which is correct):
5
/ \
8 6
/ \ /
7 12 10
And then your code calls sink on that node with value 7. It is clear that there is nothing to sink here, but instead that 7 should bubble up and swap with 8. Your code must foresee both scenarios: sift or bubble.
If you fix those two issues in your heap implementation, it will work.
I provide here the literal changes you have to make:
In the get method, replace return null with return undefined (or omit the explicit value)
In the MaxHeap sink method, swap the comparator expression, replacing:
while((this.get(greterChild = this.get(2*currentIndex+1) >= this.get(2*currentIndex + 2) ? 2*currentIndex + 1 : 2*currentIndex + 2) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
with:
while((this.get(greterChild = this.get(2*currentIndex+1) <= this.get(2*currentIndex + 2) ? 2*currentIndex + 2 : 2*currentIndex + 1) ?? Number.MIN_SAFE_INTEGER) > this.get(currentIndex)){
In the pop method, replace:
this.sink(index);
with:
this.sink(index);
this.bubble(index);
(You can also first check which of both is needed, but it doesn't hurt to just call both methods)

Finding if a given set of parentheses is valid or not

Below is my code, it works for some strings but not for all.
Ex: "()()()()()((" expected is false, my code returns true.
function validParentheses(parens){
var stack = [];
parens.split('').map((cur, index) =>{
if(stack.length === 0 || stack[index-1] === cur) stack.push(cur);
else stack.pop();
});
return stack.length > 0 ? false : true;
}
stack[index - 1] will be valid so long as you push every iteration. In the case that you pop an element, the incrementing index will always be out of bounds.
Change it to stack.length - 1 to always get the last element, regardless of what is pushed or popped.
For every '(' there must be a exactly one ')'. So you need a counter to see that there is an exact match
function validParentheses(parens){
const chars = parens.split('');
const numChars = chars.length;
let ii;
let numOpenParens = 0;
for (ii = 0; ii < numChars; ii += 1) {
curChar = chars[ii];
numOpenParens += curChar == '(' ? 1 : -1;
// return false if there is one too many closed parens
if (numOpenParens < 0) {
return false;
}
}
// return true only if all parens have been closed
return numOpenParens === 0;
}
For case when stack's length is greater than 0:
if top of the stack is equal to current iterated parenthesis, push that to stack
else pop the stack
function validParentheses(parens) {
var stack = []
parens.split("").forEach((cur) => {
if (stack.length > 0) {
if (stack[stack.length - 1] === cur) {
stack.push(cur)
} else {
stack.pop()
}
} else {
stack.push(cur)
}
})
return stack.length > 0 ? false : true
}
console.log(validParentheses("()()()()()(("))
console.log(validParentheses("()()()()()()"))
console.log(validParentheses("((()))"))
console.log(validParentheses("((())))"))
in stack[index-1] === cur
you are comparing if the char isn't the same like the one stored in the stack, so )( opposite parens will be valid
you can try do something like this
function validParentheses(parens) {
if (parens % 2 == 1) return false;
for (let i = 0; i < parens.length; i++) {
const char = parens[i];
if (char == "(") {
if (parens[i + 1] == ")") {
i++;
} else {
return false
}
} else {
return false
}
}
return true;
}
You need to check the last added value as well, because an unresolves closing bracket should remain in he stack.
BTW, Array#forEach is the method of choice, because Array#map returns a new array, which is not used here.
function validParentheses(parens) {
var stack = [];
parens.split('').forEach((cur, index) => {
if (cur === ')' && stack[stack.length - 1] === '(') stack.pop();
else stack.push(cur);
});
return !stack.length;
}
console.log(validParentheses("(())()"));
console.log(validParentheses("()()()()()(("));
console.log(validParentheses("))(())"));

Stuck with how to calculate a mode from an array in typescript

I am stuck with a problem where I am supposed to figure out a mode from an array in typescript.
I am using the program Visual Studio Code and I know that I need a for loop but I am unsure what I should loop through it. I also have to make sure that if the array is empty, the number that always shows up is 0 and if there is two integers that show up the same amount of times, that the smaller number (whether positive or negative) is the number that is put as the mode.
Currently I have this part of the code:
export let mode = (a: number[]): number => {
let mode: number;
mode = a[0];
if (a.length === 0) {
return 0;
}
for (let i = 1; i < a. length; i++) {
if ()
return mode;
};
I know that there needs to be an if statement after the for loop that changes the mode when necessary, but I am unsure beyond that.
As you indicated: your mode should be a function which:
accepts an array of numbers and should return the number with highest occurrence.
if there are two or more numbers that share the highest occurrence, then it should return the number with the least value.
if the given array is empty, it should return 0.
You could do this:
If given array is empty, return 0 right away. Else proceed with the rest of the steps.
Count the occurrences of each number in the array.
Sort the result of #2 by number of occurrence and value. Sort by highest occurrence, then lowest value.
Get the first of the items from #3.
Here's an implementation using reduce() to do step #2, and sort() to do step #3.
let mode = (numbers: number[]): number => {
if (numbers.length === 0) {
return 0;
}
const m = numbers.reduce((items, current) => {
const item = (items.length === 0) ? null : items.find((x) => x.value === current);
(item) ? item.occurrence++ : items.push({ value: current, occurrence: 1 });
return items;
}, [])
.sort((a, b) => {
if (a.occurrence < b.occurrence) {
return 1;
} else if (a.occurrence > b.occurrence || a.value < b.value) {
return -1;
} else {
return (a.value === b.value) ? 0 : 1;
}
});
return m[0].value;
}

How to count number of ways to parenthesize boolean expression string to evaluate to desired result

Straight out of CTCI, 8.14: Given a boolean expression consisting of the symbols 0 (false), 1 (true), & (AND), | (OR), and ^(XOR), and a desired boolean result value result, implement a function to count the number of ways of parenthesizing the expression such that it evaluates to result.
I'm attempting a brute force approach that calculates every single possible combo, if matches desired result, add it to an array(combos) and return that result length. It seems to work for most expressions, but not the 2nd example given. What do I seem to be missing?
function countEval(s, goalBool, combos = []) {
// on first call make s into array since theyre easier to work with
if (!(s instanceof Array)) {
// and turn 1s and 0s into their bool equivalent
s = s.split('').map((item) => {
if (item === '1') {
return true;
} else if (item === '0'){
return false;
} else {
return item;
}
});
}
if (s.length === 1 && s[0] === goalBool) {
combos.push(s[0]); // can be anything really
} else {
for (let i = 0; i < s.length - 2; i = i + 2) {
// splice out the next 3 items
const args = s.splice(i, 3);
// pass them to see what they evaluate too
const result = evalHelper(args[0], args[1], args[2]);
// splice that result back in s array
s.splice(i, 0, result);
// pass that array to recurse
countEval(s, goalBool, combos);
// remove said item that was just put in
s.splice(i, 1);
// and reset array for next iteration
s.splice(i, 0, ...args);
}
}
return combos.length;
}
function evalHelper(a, op, b) {
if (op === '|') {
return a || b;
} else if (op === '&') {
return a && b;
} else if (op === '^') {
return a !== b;
}
}
With the 2 examples given it works for the first one, but not the second...
console.log(countEval('1^0|0|1', false)); // 2, correct
console.log(countEval('0&0&0&1^1|0', true)); // 30, should be 10!?!?!
The Bug
Your program is not taking into account overlap.
Example
Consider your program when s = '1|1|1|1'.
In one of the depth-first search iterations, your algorithm will make the reduction s = (1|1)|1|1. Then in a deeper recursive level in the same search, your algorithm will make the reduction s = (1|1)|(1|1). Now s is fully reduced, so you increment the length of combos.
In a different depth-first search iteration, your algorithm will first make the reduction s = 1|1|(1|1). Then in a deeper recursive level in the same search, your algorithm will make the reduction s = (1|1)|(1|1). Now s is fully reduced, so you increment the length of combos.
Notice that for both cases, s was parenthesized the same way, thus your program does not take into account overlap.
A Better Solution
A lot of times, when a problem is asking the number of ways something can be done, this is usually a big indicator that dynamic programming could be a potential solution. The recurrence relation to this problem is a bit tricky.
We just need to pick a "principle" operator, then determine the number of ways the left and right side could evaluate to true or false. Then, based on the "principle" operator and the goal boolean, we can derive a formula for the number of ways the expression could evaluate to the goal boolean given that the operator we picked was the "principle" operator.
Code
function ways(expr, res, i, j, cache, spaces) {
if (i == j) {
return parseInt(expr[i]) == res ? 1 : 0;
} else if (!([i, j, res] in cache)) {
var ans = 0;
for (var k = i + 1; k < j; k += 2) {
var op = expr[k];
var leftTrue = ways(expr, 1, i, k - 1, cache);
var leftFalse = ways(expr, 0, i, k - 1, cache);
var rightTrue = ways(expr, 1, k + 1, j, cache);
var rightFalse = ways(expr, 0, k + 1, j, cache);
if (op == '|') {
if (res) {
ans += leftTrue * rightTrue + leftTrue * rightFalse + leftFalse * rightTrue;
} else {
ans += leftFalse * rightFalse;
}
} else if (op == '^') {
if (res) {
ans += leftTrue * rightFalse + leftFalse * rightTrue;
} else {
ans += leftTrue * rightTrue + leftFalse * rightFalse;
}
} else if (op == '&') {
if (res) {
ans += leftTrue * rightTrue;
} else {
ans += leftFalse * rightFalse + leftTrue * rightFalse + leftFalse * rightTrue;
}
}
}
cache[[i, j, res]] = ans;
}
return cache[[i, j, res]];
}
function countEval(expr, res) {
return ways(expr, res ? 1 : 0, 0, expr.length - 1, {});
}

Expanding use of Javascript array.reduce helper method

Background
I am following a course on Udemy which goes over all of the ES6 features. In one of the lessons the instructor talks about using the reduce helper method to solve the popular balance parenthesis interview question.
I can solve this without the reduce method. Although with the reduce method it does get the job done in less code. I have been asked to find the depth of the parenthesis in an interview before and was wondering if this could all be done in the same method using reduce.
I do not know why this addition to the question confuses me so much but I would like to learn.
Problem
I have been trying to figure it out for a while and it might be my lack of understanding how reduce works.
Example
This uses reduce to return true of false regarding if the parenthesis or open and closed evenly.
function balanceParens(string) {
return !string.split("").reduce((counter, char) => {
// Handle if parens open and close out of order
if (counter < 0) { return counter; }
// Add 1 for each open in order
if (char === "(") { return ++counter; }
// subtract 1 for each close in order
if (char === ")") { return --counter; }
// handle use case if char is not a paren
return counter;
}, 0);
}
console.log(balanceParens("((()))"));
Question
How would I return the max depth of the parenthesis using the reduce helper method.
You could maintain current depth and max depth while reducing.
function maxDepth(string) {
return string.split("").reduce(({current, max}, char) => {
// Handle if parens open and close out of order
if (current < 0) return {current, max}
// Add 1 for each open in order
if (char === "(") return { current: current + 1, max: Math.max(max, current + 1)}
// subtract 1 for each close in order
if (char === ")") return { current: current - 1, max}
return {current, max}
}, {current: 0, max: 0}).max;
}
console.log(maxDepth("(((()))(((())))()(((((()))))))"));
Here is a compact version that returns NaN when the parentheses are not balanced. It uses nested functions in a functional style:
function maxDepth(string) {
return ( ([depth, max]) => depth ? NaN : max )
([...string].reduce(([depth, max], ch) =>
(newDepth => [newDepth, newDepth < 0 ? NaN : Math.max(max, newDepth)])
(depth + (ch === "(") - (ch === ")"))
, [0, 0]));
}
console.log(maxDepth("(((()))(((())))()(((((()))))))"));
This should answer it!
function balanceParens(string) {
let max = 0;
let res = string.split("").reduce((counter, char) => {
// Handle if parens open and close out of order
if (counter < 0) {
return counter;
}
// Add 1 for each open in order
if (char === "(") {
if(++counter > max) {
max = counter;
}
return counter;
}
// subtract 1 for each close in order
if (char === ")") {
return --counter;
}
// handle use case if char is not a paren
return counter;
}, 0);
console.log("Max depth was :", max);
return !res;
}
console.log(balanceParens("((()(((())))))((((()))))"));

Categories