How to extrude and execute the contents between brackets? - javascript

I need to execute an expression according to Order of Operations, and I can't figure out how to resolve the side-by-side brackets.
I am receiving an expression as a string ->
"(add (multiply 4 5) (add 13 1))"
I can't figure out how to resolve this side by side case.
How I have it working for a case where everything is nested by grabbing the innermost brackets and then solving the next like this:
// Find innermost equasion
const findInnerEq = (str) =>
str.substring(str.lastIndexOf("(") + 1, str.lastIndexOf(")"));
// Find Brackets RegEx
const brakets = /(?<=\().*(?=\))/g;
// Changing RegEx function
const changeRE = (str) =>
str.slice(str.lastIndexOf("(") + 1, str.indexOf(")"));
// Return calculation
const calculate = (str) => {
let exp = findInnerEq(str).toLowerCase().split(" ");
let calc = exp
.slice(1)
.map((element) => parseInt(element))
switch (exp[0]) {
case "add":
if (calc.length > 2){
return calc.reduce((acc, cur) => acc + cur, 0);
}
return calc[0] + calc[1]
break;
case "multiply":
return calc.reduce((acc, cur) => acc * cur, 1);
break;
default:
console.log("Please enter valid expression");
process.exit();
}
};
// Recursive function
const calculator = (str) => {
let curString;
if (str.match(brakets)) {
curString = str;
let changingReg = `\(${changeRE(curString)}\)`;
curString = curString.replace(changingReg, calculate(curString));
return calculator(curString);
} else {
return str;
}
};
console.log(calculator("(add 2 (multiply 2 2))"));
console.log(calculator("(add (multiply 2 2) (add 2 2)"));
How can I deal with the side-by-side brackets?

Well, it looks like a classic problem from CS, and you are approaching it wrong.
What you should do instead, is use stacks, pushing items to them until you meet ')', which will tell you to execute the action on the stack collected so far.
Here is one of explanations https://orkhanhuseyn.medium.com/what-are-stack-based-calculators-cf2dbe249264

try this:
let arr = str.split("(").join("|:|:|:|").split(")").join("|:|:|:|").split("|:|:|:|")
//arr is now an array that has been split at both ( and )
// the middle value: arr[arr.length/2]
//evaluate it, and then combine it with arr[arr.length/2+1] and arr[arr.length/2-1]
//continue the cycle until your whole expression is evaluated

Related

How to repeat a letter inside a string? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed last month.
Improve this question
lets say the input is
let word = 'I Lo1ve Co4ding'
the output would be I Loove Coooooding
so repeating x letters after putting a number after
Still don't understand how it works and how to replace it mid string.
You can use the callback function argument in String.prototype.replace, and call String.prototype.repeat on the first matching group (letter) and pass the second matching group (number) plus 1.
const expandStr = (str) =>
str.replace(/([a-z])(\d+)/gi, (g, g1, g2) => g1.repeat(+g2 + 1));
console.log(expandStr('I Lo1ve Co4ding'));
Caveat
As suggested in the comments, you may use the following:
/(\p{L})(\p{N}+)/gu
In place of:
/([a-z])(\d+)/gi
Explanation:
\p{L} – matches a single code point in the category "letter"
\p{N} – matches any kind of numeric character in any script
\p{S} – symbols e.g. emoji
const expandStr = (str) =>
str.replace(/(\p{L}|\p{S})(\p{N}+)/gu, (g, g1, g2) => g1.repeat(+g2 + 1));
console.log(expandStr('I Lo1ve Co4ding'));
console.log(expandStr('I give 📽️ I saw 👍1!')); // Works with emoji
Please refer to "Unicode Categories" to learn more.
Alternative pattern: /([^(\p{N}|\s)])(\p{N}+)/gu
Tokenizing
Here is a more traditional example that incorporates loops. It does not use regular expressions, but follows a tokenizer (parser) approach.
Note: Keep in mind that this naïve example does not account for Unicode.
const
NUMBER_RANGE_START = '0' .charCodeAt(0), // 48
NUMBER_RANGE_END = '9' .charCodeAt(0), // 57
LETTER_UPPER_RANGE_START = 'A' .charCodeAt(0), // 65
LETTER_UPPER_RANGE_END = 'Z' .charCodeAt(0), // 90
LETTER_LOWER_RANGE_START = 'a' .charCodeAt(0), // 97
LETTER_LOWER_RANGE_END = 'z' .charCodeAt(0), // 122
WHITESPACE_CARRIAGE_RETURN = '\r'.charCodeAt(0), // 13
WHITESPACE_LINE_FEED = '\n'.charCodeAt(0), // 10
WHITESPACE_SPACE = ' ' .charCodeAt(0), // 32
WHITESPACE_TAB = '\t'.charCodeAt(0); // 9
const codeInRange = (code, start, end) => code >= start && code <= end;
const charInRange = (char, start, end) => codeInRange(char.charCodeAt(0), start, end);
const isNumber = (char) =>
charInRange(char, NUMBER_RANGE_START, NUMBER_RANGE_END);
const isUpperLetter = (char) =>
charInRange(char, LETTER_UPPER_RANGE_START, LETTER_UPPER_RANGE_END);
const isLowerLetter = (char) =>
charInRange(char, LETTER_LOWER_RANGE_START, LETTER_LOWER_RANGE_END);
const isLetter = (char) => isLowerLetter(char) || isUpperLetter(char);
const isWhiteSpace = (char) => {
switch (char.charCodeAt(0)) {
case WHITESPACE_CARRIAGE_RETURN:
case WHITESPACE_LINE_FEED:
case WHITESPACE_SPACE:
case WHITESPACE_TAB:
return true;
default:
return false;
}
};
const expandStr = (str) => {
const result = [];
let index, char, prevChar, count, step;
for (index = 0; index < str.length; index++) {
char = str[index];
if (
isNumber(char) &&
(
isLetter(prevChar) ||
(
!isWhiteSpace(prevChar) &&
!isNumber(prevChar)
)
)
) {
count = parseInt(char, 10);
for (step = 0; step < count; step++) {
result.push(prevChar);
}
} else {
result.push(char);
}
prevChar = char;
}
return result.join('');
};
console.log(expandStr('I Lo1ve Co4ding')); // I Loove Coooooding
A little bit longer way as from Mr.Polywhirl. But I think foreach loops make for good readability and you will see how it works.
const word = 'I Lo1ve Co4ding'
function rewrite(w) {
const arr = [...w];
let n = [];
arr.forEach((s,i) => {
if(! isNaN(s) && s != ' ') {
n.push(arr[i-1].repeat(s));
} else {
n.push(s);
}
});
return n.join('');
}
console.log( rewrite(word) );

Compare two sentences and identify wrong words in javascript

I want to compare two sentences. Here is the example:
Correct: Experts believe industrial development will help the economy.
Given: Xperts does believe that development won't economy.
Expected Output:
Experts Xperts does believe industrial that development will won't help the economy.
I have tried to compare string by splitting the both words and checking them.
let given= "Xperts does believe that development won't economy.";
let correct= "Experts believe industrial development will help the economy.";
function checkSentence(given,correct){
let final='';
for(i=0; i<given.length; i++){
if(given[i].trim()==correct[i].trim()){
final += given[i]+' ';
}else{
final += "<i>given[i]</i> <b>correct[i]</b>";
}
}
return final;
}
I would solve the problem recursively with the following steps:
Try to find the closest matching word in each sentence
If match found: Split each sentence at the matching word and run the same function on each side
If no match found: Both sentences have nothing in common and you can return each sentence formatted in the way you want
Note: I start and try to find the longest matching word because those are most indicative of sentence structure rather than searching for 'it' and 'and'
const correct= "Experts believe industrial development will help the economy.";
const given= "Xperts does believe development that won't economy.";
const correctArray = correct.split(" ")
const givenArray = given.split(" ")
// Returns [correctIndex, givenIndex] if match found or [-1, -1] if no match found
function findIndexOfLongestMatchingWord(correctArray, givenArray) {
// Create an array of word length and its index for the correct word array
const correctWordLengthIndexArray = correctArray.map((word, index) => [word.length, index]).sort(([length1, index1], [length2, index2]) => length2 - length1)
for(let matchingIndex = 0; matchingIndex < correctArray.length; matchingIndex++) {
const correctArrayIndex = correctWordLengthIndexArray[matchingIndex][1]
const correctArrayWord = correctArray[correctArrayIndex]
const foundIndexOfGivenWord = givenArray.findIndex(givenWord => givenWord === correctArrayWord)
if(foundIndexOfGivenWord != -1) return [correctArrayIndex, foundIndexOfGivenWord];
}
return [-1,-1]
}
function formatCorrectArray(correctArray) {
return correctArray.length == 0 ? "" : `<b>${correctArray.join(" ")}</b>`
}
function formatGivenArray(givenArray) {
return givenArray.length == 0 ? "" : `<i>${givenArray.join(" ")}</i>`
}
function findDifferenceRecursively(correctArray, givenArray) {
// If either array empty there is nothing to compare, return each one formatted
if(correctArray.length == 0 || givenArray.length == 0) {
return formatCorrectArray(correctArray) + formatGivenArray(givenArray)
}
const [correctIndex, givenIndex] = findIndexOfLongestMatchingWord(correctArray, givenArray);
if (correctIndex != -1) {
// Split each string at their index and run find difference on each side of the indexes;
const leftCorrect = correctArray.slice(0, correctIndex)
const rightCorrect = correctArray.slice(correctIndex + 1)
const leftGiven = givenArray.slice(0, givenIndex)
const rightGiven = givenArray.slice(givenIndex + 1)
// Run function on words on each side
return findDifferenceRecursively(leftCorrect, leftGiven) + ` ${correctArray[correctIndex]} ` + findDifferenceRecursively(rightCorrect, rightGiven)
} else {
return formatCorrectArray(correctArray) + formatGivenArray(givenArray)
}
}
const result = findDifferenceRecursively(correctArray, givenArray)
// Returns "<b>Experts</b><i>Xperts does</i> believe <b>industrial</b> development <b>will help the</b><i>that won't</i> economy. "
console.log(result)

Predict JavaScript string from incorect input

I'm currently programming a Discord bot, and was wondering if it is possible to predict the wanted command if the input was incorrect.
For example, I have this list of words :
['help','meme','ping'],
and if the user inputs "hepl", would it somehow be possible to "guess" they meant to type help ?
One option would be to find a command whose levenshtein distance from the input is 2 or less:
// https://gist.github.com/andrei-m/982927
const getEditDistance=function(t,n){if(0==t.length)return n.length;if(0==n.length)return t.length;var e,h,r=[];for(e=0;e<=n.length;e++)r[e]=[e];for(h=0;h<=t.length;h++)r[0][h]=h;for(e=1;e<=n.length;e++)for(h=1;h<=t.length;h++)n.charAt(e-1)==t.charAt(h-1)?r[e][h]=r[e-1][h-1]:r[e][h]=Math.min(r[e-1][h-1]+1,Math.min(r[e][h-1]+1,r[e-1][h]+1));return r[n.length][t.length]};
const commands = ['help','meme','ping'];
const getCommand = (input) => {
if (commands.includes(input)) return input;
return commands.find(command => getEditDistance(input, command) <= 2);
};
console.log(getCommand('hepl'));
(2 is just a number, feel free to pick the tolerance you want - the higher it is, the more commands will be guessed at, but the more false positives there will be)
You can find hits and show many words in suggestion. If you want same you can use to show most hit word.
const words = ["help", "meme", "ping"];
const getHits = (word, wordToMatch, hits = 0) => {
if (!word.length || !wordToMatch.length) return hits;
let charW = word.slice(0, 1);
let index = wordToMatch.indexOf(charW);
if (index !== -1) {
return getHits(
word.slice(1),
String(wordToMatch.slice(0, index) + wordToMatch.substr(index + 1)),
hits + 1
);
}
return getHits(word.slice(1), wordToMatch, hits);
};
const getMatch = mword => {
return words.reduce((m, word) => {
m[word] = getHits(mword, word);
return m;
}, {});
};
const sort = obj => {
return Object.entries(obj).sort(
([_, value1], [__, value2]) => value2 - value1
);
};
console.log(getMatch("help"));
console.log(sort(getMatch("help")));
console.log(getMatch("me"));
console.log(sort(getMatch("me")));
.as-console-row {color: blue!important}

Trying to write this in a more elegant way. Can I use a ternary in this function? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I was working on this practice problem, and solved it, but I want a more elegant way of writing this code:
// Usually when you buy something, you're asked whether your credit card number, phone number or answer to your most secret question is still correct. However, since someone could look over your shoulder, you don't want that shown on your screen. Instead, we mask it.
// Your task is to write a function maskify, which changes all but the last four characters into '#'.
const maskify = (cc) => {
let ccArray = Array.from(cc);
let length = cc.length;
let lastFour = cc.slice(-4);
let newArray = [];
if (length <= 4) {
return cc;
} else if (length > 4) {
let index = length - 4;
ccArray.splice(index, 4);
ccArray.forEach(n => {
newArray.push('#');
return newArray;
});
return newArray.concat(lastFour).join('');
}
}
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('1'));
// 1
console.log(maskify('11111'));
// #1111
There are many different approaches:
function maskify(cc) {
return "#".repeat(Math.max(0, cc.length-4)) + cc.slice(-4);
}
function maskify(cc) {
return Array.from(cc, (char, index) =>
index < cc.length - 4 ? "#" : char
).join('')
}
function maskify(cc) {
return cc.replace(/.(?=.{4})/g, "#");
}
This might be among the easiest ways to achieve that goal:
const maskify = (cc) => {
return ("################################"+cc.slice(-4)).slice(-cc.length);
}
console.log(maskify('4556364607935616'));
console.log(maskify('1'));
console.log(maskify('11111'));
Just make sure the "################################" is long enough to cover all your use-cases.
To make it dynamic and work for any length of a string, it gets only slightly more complicated:
const maskify = (cc) => {
return ([...cc].map(x=>'#').join('')+cc.slice(-4)).slice(-cc.length);
}
console.log(maskify('4556364607935616'));
console.log(maskify('1'));
console.log(maskify('11111'));
You can also use a regular expression:
const maskify = (cc) => {
return (cc.replace(/./g,'#')+cc.slice(-4)).slice(-cc.length);
}
console.log(maskify('4556364607935616'));
console.log(maskify('1'));
console.log(maskify('11111'));
Generally speaking, I think your code is fine. Since "more elegant" is a little nebulous, I'll concern myself with 1) time complexity and 2) memory complexity
1) Time Complexity
Generally, you're fine here. No extra loops, and no intensive operations. There is a minor improvement to remove the final .join since that will have to loop over the final array.
2) Memory complexity
Some room for improvement here. Currently, the function creates a bunch of extra arrays. This likely won't be an issue for something as small as a credit card number, but could bite you if you applied a similar logic in a different situation.
Annotated original:
const maskify = (cc) => {
let ccArray = Array.from(cc); // Array 1
let length = cc.length;
let lastFour = cc.slice(-4); // Array 2
let newArray = []; // Array 3
if (length <= 4) {
return cc;
} else if (length > 4) {
let index = length - 4;
ccArray.splice(index, 4);
// Loop 1
ccArray.forEach(n => {
newArray.push('#');
return newArray;
});
// Loop 2
return newArray.concat(lastFour).join(''); // Array 4
}
}
Improved version:
const maskify = (cc) => {
let ccArray = Array.from(cc); // Array 1
let length = cc.length;
// Loop 1
let resultString = ccArray.reduce((result, original, index) => {
result += (index >= length - 4 ? original : '#');
}, "");
return resultString;
}
Array Reduce Documentation
As others have noted, there are many other ways to write this method. Since the question was specifically about how the existing code could be better, I tried to focus this answer on that.
Here's a way you can do it using destructuring assignment and single ternary expression
const maskify = ([ a = '', b = '', c = '', d = '', ...rest ]) =>
rest.length === 0
? a + b + c + d
: '#' + maskify ([ b, c, d, ...rest ])
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('1'));
// 1
console.log(maskify('11111'));
// #1111
Destructuring creates intermediate values which can be avoided by using a secondary parameter, i, which represents the index of the current computation
const maskify = (str, i = 0) =>
4 + i >= str.length
? str.substr (i)
: '#' + maskify (str, i + 1)
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('1'));
// 1
console.log(maskify('11111'));
// #1111
Now it's easy to make 4 an argument to our function as well
const maskify = (str, n = 4, i = 0) =>
n + i >= str.length
? str.substr (i)
: '#' + maskify (str, n, i + 1)
console.log(maskify('4556364607935616'));
// ############5616
console.log(maskify('4556364607935616', 6));
// ##########935616
console.log(maskify('1'));
// 1
console.log(maskify('11111', 10));
// 11111

Writing a function f which would satisfy the following test

I have a question which I'm stuck on and need help solving.
Below is a test which needs to be solved. I've managed to put together a solution which works for 85% of the coverage but its the last 15% I'm stuck on.
describe("f", function() {
it("should work", function() {
expect(f("l")).toEqual("fl");
expect(f()("l")).toEqual("fol");
expect(f()()("l")).toEqual("fool");
expect(f()()("t")).toEqual("foot");
expect(f()()()()()("l")).toEqual("foooool");
// And so on such that the number of calls continues
// to increase the number of letter "o" in the string
// until the function is called with a string.
// BONUS: also the function should be stateless:
var a = f()();
expect(a("A")).toEqual("fooA");
expect(a()()()("B")).toEqual("foooooB");
expect(a()("C")).toEqual("foooC");
});
});
The solution:
function f(input) {
let result = 'f'
let cf = (c) => {
if (c) {
return result + c
}
result += 'o'
return cf
}
return cf(input)
}
which works for all but the last bonus test.
You have to create a new f() instance whenever the returned function is called:
const f = (char, prev = "f") => char ? (prev + char) : (char2 => f(char2, prev + "o"));
If that hidden second param is a bit to cheaty here is a curried version:
const concat = a => b => b ? (a + b) : concat(a + "o");
const f = concat("f");

Categories