There is one project challenge on freecodecamp about building a calculator and I just managed to pass all the tests but when looking back on my code, the section dealing with the operations are barely readable. I've read some articles online regarding how to reduce the complexity of conditionals and the principles to keep in mind for more easy-to-understand logic.
However, figuring out the achievable logics for this task in javascript is quite challenging to me now. I was trying to meet two conditions with this section of code as follows:
User Story #13: If 2 or more operators are entered consecutively, the operation performed should be the last operator entered (excluding the negative (-) sign). For example, if 5 + * 7 = is entered, the result should be 35 (i.e. 5 * 7); if 5 * - 5 = is entered, the result should be -25 (i.e. 5 * (-5)).
User Story #14: Pressing an operator immediately following = should start a new calculation that operates on the result of the previous evaluation.
Here is the link to the page for this particular challenge and
this is the link to the code I wrote by far.
Is there any tips and advice on refining the code or other approaches for coping with this part?
handleOperation(event){
const {value}=event.target
const displayLength=this.state.display.length
const condition1=this.state.display=="+"||this.state.display=="-"||this.state.display=="×"||this.state.display=="÷"||this.state.display==""
const condition2=/^\d*\.?\d*$/.test(this.state.input)&&!/=/.test(this.state.display)
const condition3=this.state.input=="-"&&(this.state.display.charAt(displayLength-2)=="+"||this.state.display.charAt(displayLength-2)=="-"||this.state.display.charAt(displayLength-2)=="×"||this.state.display.charAt(displayLength-2)=="÷")
const condition4=this.state.input=="-"&&value=="-"&&!/=/.test(this.state.display)
const condition5=this.state.input=="-"&&value!=="-"&&!/=/.test(this.state.display)
const condition6=this.state.input!=="-"&&value!=="-"&&!/=/.test(this.state.display)
const condition7=this.state.input!=="-"&&value=="-"&&!/=/.test(this.state.display)
const condition8=/=/.test(this.state.display)
console.log(this.state.display.replace(/=/,"$'"))
if(condition1){
this.setState({
input:value,
display:value
})
}else if(condition2){
this.setState({
input:value,
display:this.state.display+value
})
}else if(condition3){
this.setState({
input:value,
display:this.state.display.replace(/[\+\-×÷]-$/,value)
})
}
else if(condition4){
this.setState({
input:value,
display:this.state.display.replace(/(?<=\d)-$/,"--")
})
}else if(condition5){
this.setState({
input:value,
display:this.state.display.replace(/(?<=\d)-/,value)
})
}else if(condition6){
this.setState({
input:value,
display:this.state.display.substring(0,displayLength-1)+value
})
}else if(condition7){
this.setState({
input:value,
display:this.state.display+value
})
} else if(condition8){
this.setState({
input:value,
display:this.state.display.substring(this.state.display.indexOf("=")+1)+value
})
}
}
Break down the process to the basic steps:
get the operation(s) from the string
get the numbers from the string
do the operation
Here's a snippet for this:
const calcs = [
"5 + 15",
"5 - 5",
"5 - - 5",
"5 / + 5",
"5 / - 5",
"5 / * + 5",
"5 / + * 5",
]
const container = document.getElementById("container");
// getting the operation(s) from the string
const getOperation = (calc) => {
const regex = /d*([+|\-|\*|\/]+)d*/g
const listOfOperations = calc.match(regex)
let operation = listOfOperations.pop()
let nextIsNegative = false
// if the operation is "-" and it wasn't the last item
if (listOfOperations.length && operation === "-") {
operation = listOfOperations.pop()
nextIsNegative = true
}
return {
operation,
nextIsNegative,
}
}
// getting the numbers from the string
const getNumbers = (calc) => {
const regex = /\d+/g
return calc.match(regex)
}
// doing the calculation
const doOperation = ({
operation,
nextIsNegative
}, [num1, num2]) => {
const operationObj = {
"+": (a, b) => a + b,
"-": (a, b) => a - b,
"*": (a, b) => a * b,
"/": (a, b) => a / b,
}
const n1 = Number(num1)
const n2 = nextIsNegative ? Number(num2) * -1 : Number(num2)
return operationObj[operation](n1, n2)
}
(function(calcs) {
const html = calcs.map((calc, i) => {
const operation = getOperation(calc)
const numbers = getNumbers(calc)
const result = doOperation(operation, numbers)
return `
<div id="in${i}">${calc}</div>
<div id="operation${i}">${JSON.stringify(operation)}</div>
<div id="result${i}">${ result }</div>`
})
container.innerHTML = html.join('')
})(calcs);
#container {
display: grid;
grid-template-columns: 80px 1fr 80px;
}
<div id="container"></div>
Related
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
from api i get this strings:
"18% increased Area of Effect", "25% increased Area of Effect", "19% increased Area of Effect"
i want to make like this:
"(18%-25%) increased Area of Effect"
const filtredArray = array.filter((item) => item.implicit_mods);
let implicitsList = [];
filtredArray.forEach((item) => implicitsList.push(item.implicit_mods));
implicitsList.forEach((implisit) => {
if (implisit) {
implisit.forEach((implicit) => {
if (implicit) {
if (!/\d/.test(implicit)) {
//implicits without number
if (!uniqueImplicitMap.get(implicit)) {
addToMap(uniqueImplicitMap, implicit);
}
} else {
// implicits with number
let value = implicit.match(/\d+/g);
let replaceImplicit = implicit.replace(/\d+/g, '#');
if (value.length >= 2) {
addToMap(uniqueImplicitMap, replaceImplicit, [
value[0],
value[1],
]);
} else {
addToMap(uniqueImplicitMap, replaceImplicit, value);
}
}
}
});
}
});
}
function addToMap(map, key, value) {
if (!map.get(key)) {
if (value) {
map.set(key, [value, value]);
} else {
map.set(key);
}
}
if (map.get(key) && value) {
if (value[0] > map.get(key)[0]) {
map.get(key).splice(0, 1, value);
} else if (value < map.get(key)[0] && value < map.get(key)[1]) {
map.get(key).splice(1, 1, value);
}
}
}
i can do it if i have only one number in string
but if string like this:
"10% chance to gain Unholy Might for 3 seconds on Kill"
code break.
help me please
I do not completely understand your code. What I think you want is if you get a array of strings from the API like ["18% increased Area of Effect", "25% increased Area of Effect", "19% increased Area of Effect"], and you want to make that into (18%-25%) increased Area of Effect.
If so, you can just run a .map onto the array to find the percent with symbols, and get the percent. With that is is quite easy to generate a string with ES6 template literals.
Example Code
JS Fiddle for snippet.
// Select elements on HTML
const
before = document.querySelector(".previous"),
before2 = document.querySelector(".previous2"),
after = document.querySelector(".after"),
after2 = document.querySelector(".after2");
// Sample API text
const beforeText = ["18% increased Area of Effect", "25% increased Area of Effect", "19% increased Area of Effect"];
const before2Text = ["10% chance to gain Unholy Might for 3 seconds on Kill", "18% chance to gain Unholy Might for 3 seconds on Kill", "26% chance to gain Unholy Might for 3 seconds on Kill"];
// Display before paresed strings
before.innerText = beforeText.join(" -=- ");
before2.innerText = before2Text.join(" -=- ");
// Display parsed strings
after.innerText = parser(beforeText);
after2.innerText = parser(before2Text);
function parser(arrText) {
const PERCENT_REGEX = /[0-9]{1,2}%/;
// Get percents of strings.
const percents = arrText.map(elem => {
const percentStr = elem.match(PERCENT_REGEX)[0];
return parseInt(
// Remove percent symbol
percentStr.substring(0, percentStr.length - 1), 10
);
});
const chanceToDoWhat = arrText[0].replace(PERCENT_REGEX, "");
return `(${Math.min(...percents)}%-${Math.max(...percents)}%)${chanceToDoWhat}`;
}
Before Parse:<br>
<code class="previous"></code>
<br>
<br>
Before Parse 2:<br>
<code class="previous2"></code>
<br>
<br>
After Parse:<br>
<code class="after"></code>
<br>
<br>
After Parse 2:<br>
<code class="after2"></code>
I'm working with numbers like 0.3333333333333333, 0.1111111111111111, 0.6666666666666666 and I'd like to round them to the nearest hundredth while keeping numbers like 0.14285714285714285 intact.
Sorry if this question's been asked. It's gotta be a common question, but I can't seem to come up with the right keywords.
There may be a mathematical way to detect those, but you can detect those on the string version of the number using a regular expression like this: /^0\.(\d)\1+$/. That says
^0. Must start with 0.
(\d) Followed by a digit (we capture this digit)
\1+ Followed by the same digit (\1 refers back to the capture group) one or more times (+
Then grab just the first four characters of the string if you want to truncate (0.6666666666666666 becomes 0.66) or the first five and convert back to number and round (0.6666666666666666 becomes 0.67).
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444,
];
for (const number of numbers) {
const str = String(number);
if (/^0\.(\d)\1+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(`Unchanged; ${number}`);
}
}
(Convert back to number if you need the number value.)
In modern environments, you can make that expression a bit clearer by using a named capture group rather than the traditional anonymous version above: /^0\.(?<digit>\d)\k<digit>+$/ In that, (?<digit>\d) is the capture group named "digit" and \d<digit> is a backreference to that capture group.
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444
];
for (const number of numbers) {
const str = String(number);
if (/^0\.(?<digit>\d)\k<digit>+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(str);
}
}
There's question what you want done with 0.1444444444444444 (do you want 0.1444444444444444 or 0.14)? All your examples repeat starting at the ., but you're rounding to hundreths. Now, those are two separate things, but people are interpreting your question to say that 0.1444444444444444 should become 0.14. If so, it's a trivial change to allow any digit in the tens place: Just add \d after . in the expression: /^0\.\d(\d)\1+$/
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444,
0.1555555555555555,
];
for (const number of numbers) {
const str = String(number);
if (/^0\.\d(\d)\1+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(str);
}
}
Since your question is not so clear, I took it as follows:
If there is a repeating chunk of digits, keep the pattern twice and
truncate the rest. If there is not repeating digits, rounding to hundreths.
For example, the following is what you get by running the proposed code. The suffix ... is to indicate the repeated digits.
0.14285714285714285 => 0.142857142857... (repeating pattern = 142857)
0.1444444444444444 => 0.144... (repeating pattern = 4)
0.3333333333333333 => 0.33... (repeating pattern = 3)
0.1428824114288241 => 0.1428824114288241... (repeating pattern = 14288241)
0.1288241128824112 => 0.12882411288241... (repeating pattern = 1288241)
0.12128824112882411 => 0.1212882411288241... (repeating pattern = 1288241)
0.1231231231231231 => 0.123123... (repeating pattern = 123)
0.101010101010101 => 0.1010... (repeating pattern = 10)
0.12300123123123123 => 0.12300123123... (repeating pattern = 123)
0.4254250042542542 => 0.42542500425425... (repeating pattern = 425)
0.1232435213443346 => 0.12 (repeating pattern = None)
I had to create the test case to make sure the code works for various patterns. The nums array contains the input and the expected answer.
You can use the code as
const {result, pattern} = testRepeatingDigits (0.1444444)
If you want to round the answer, you can modify the code where it returns the number string with ....
If you give me your requirement I can always edit and improve the answer.
Here is the complete code that you can run and see.
/**
* Returns the logest repeating substring from the beginning.
*/
function findLongestSubstring (str) {
let candidate = "";
for (let i = 1; i <= str.length - i; i++) {
if (str.indexOf(str.substring(0, i), i) === i)
candidate = str.substring(0, i);
}
return candidate;
}
/**
* Rotate the substring and find the left most matched point
*/
function rotateAndMoveLeft (str, substr, fromIndex) {
const rotate = (str) => `${str[str.length-1]}${str.slice(0, str.length-1)}`;
const lastIndex = substr.length - 1;
let rotatedStr = substr;
let pos;
// console.log(`str=${str}, substr=${substr}, fromIndex=${fromIndex}`);
for (pos = fromIndex - 1; pos >= 0; pos--) {
if (rotatedStr[lastIndex] === str[pos]) {
rotatedStr = rotate(rotatedStr);
} else {
pos++;
break;
}
}
const from = pos !== -1 ? pos : 0;
return {
subStr: rotatedStr,
from,
numMoved: fromIndex - from
};
}
function shrinkPattern (pattern) {
const _shrink = (head, tail) => {
if (tail.length === 0)
return head;
return tail.split(head).every(item => item.length === 0) ?
head : _shrink(`${head}${tail[0]}`, tail.slice(1));
}
return _shrink(pattern[0], pattern.slice(1));
}
function testRepeatingDigits (num) {
const str = num.toString();
const idx = str.indexOf('.');
if (idx < 0)
return false;
const digitStr = str.substring(idx + 1);
const [...digits] = digitStr;
// the first guess of repeating pattern from the right-most digit
const init = [...findLongestSubstring(digits.slice(0).reverse().join(''))].reverse().join('');
// no repeating patterns found
if (init.length === 0)
return {
result: (Math.round(num * 100) / 100).toString(),
pattern: "None"
};
// rotate the first guessed pattern to the left to find the beginning of the repeats
const searchFrom = digitStr.length - (init.length * 2);
const { subStr, from, numMoved } = searchFrom > 0 ?
rotateAndMoveLeft(digitStr, init, searchFrom) : { subStr: init, from: 0, numMoved: 0 };
// shrink the pattern to minimum
const pattern = shrinkPattern(subStr);
// truncate the digits overflows the two repeatings of the pattern
return {
result: `${str.substring(0, idx+1)}${digitStr.substring(0, from + pattern.length * 2)}...`,
pattern
};
}
// test cases
const nums = [{
num: 0.14285714285714285, // rep: 142857, truncated: [14285]
str: '0.142857142857...'
},
{
num: 0.1444444444444444, // rep: 4, truncated: [4444444444444]
str: '0.144...'
},
{
num: 0.3333333333333333, // rep: 3, truncated: [33333333333333]
str: '0.33...'
},
{
num: 0.1428824114288241, // rep: 14288241, truncated: []
str: '0.1428824114288241...'
},
{
num: 0.1288241128824112, // 1288241, [12]
str: '0.12882411288241...'
},
{
num: 0.12128824112882411, // 1288241, [1]
str: '0.1212882411288241...'
},
{
num: 0.1231231231231231, // 123, [1]
str: '0.123123...'
},
{
num: 0.1010101010101010, // 10, [101010101010]
str: '0.1010...'
},
{
num: 0.12300123123123123, // 123, [123123]
str: '0.12300123123...'
},
{
num: 0.4254250042542542, // 425, [42]
str: '0.42542500425425...'
},
{
num: 0.1232435213443346, // no repeat
str: '0.12'
},
];
nums.forEach(({ num, str }) => {
const { result, pattern } = testRepeatingDigits(num);
console.log(`${num} => ${result} (repeating pattern = ${pattern}) ${result === str ? 'OK' : 'Incorrect!'} `);
});
Not perfectly clear but here is my take.
It feels like you would like to test the floating part of given number against a repeating pattern. So perhaps you can do like;
function truncAtRepeat(n){
var [is,fs] = String(n).split("."),
index = (fs + fs).indexOf(fs,1);
return index === fs.length ? n
: parseFloat(is + "." + fs.slice(0,index));
}
console.log(truncAtRepeat(1.177177177177177));
console.log(truncAtRepeat(1.17717717717717));
console.log(truncAtRepeat(3.14159265359));
use a list and iterate over each number in the string and find the repeating numbers
def find_repeating_numbers(string):
# create a list of numbers
numbers = [int(x) for x in string]
# create a list of repeating numbers
repeating_numbers = []
# iterate through the list of numbers
for i in range(len(numbers)):
# if the number is already in the list of repeating numbers, continue
if numbers[i] in repeating_numbers:
continue
# if the number is not in the list of repeating numbers, check if it is repeated
else:
# if the number is repeated, add it to the list of repeating numbers
if numbers.count(numbers[i]) > 1:
repeating_numbers.append(numbers[i])
# return the list of repeating numbers
return repeating_numbers
data=[0.14285714285714285,0.1444444444444444,0.3333333333333333
,0.1428824114288241,0.1288241128824112,0.12128824112882411,0.1231231231231231
,0.101010101010101,0.12300123123123123,0.4254250042542542,0.1232435213443346
]
# print the list of repeating numbers
#print(find_repeating_numbers('14285714285714285'))
for item in data:
item=re.sub('0.','',str(item))
result=find_repeating_numbers(item)
repeating_number=''.join([str(n) for n in result])
print(item,repeating_number)
output:
14285714285714285 142857
1444444444444444 4
3333333333333333 3
1428824114288241 1428
1288241128824112 1284
12128824112882411 1284
1231231231231231 123
1
123123123123123 123
42542542542542 425
1232435213443346 1234
I am struggling with infinite loop problem while Array exercise implementation which needs to be done with Java Script functional way:
I have a code which creates an array and fills its values with numbers which fulfil condition:
Each array element has a value,
which we draw from the range <100, 200> until the sum of digits is
a number having exactly two dividers, not counting 1 and this one
numbers.
I have a code like below:
const generateNumber = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + Math.floor(min);
const unities = number => number % 10;
const hundreds = number => Math.floor((number % 1000) / 100);
const tens = number => Math.floor((number % 100) / 10);
const sumDigits = (number) => unities(number) + hundreds(number) + tens(number);
const countNumberFactors = number => Array
.from(Array(number + 1), (_, i) => i)
.filter(i => number % i === 0)
.slice(1, -1)
.length;
const generateNumberUntilConditionNotAchieve = (min, max) => {
let number = generateNumber(min, max);
const digitsSum = sumDigits(number);
while (countNumberFactors(digitsSum) === 2) {
number = generateNumber(min, max)
}
return number;
}
const generateArray = (minArrSize, maxArrSize, minItemValue, maxItemValue) =>
Array(generateNumber(minArrSize, maxArrSize))
.fill(0)
.map(
() => generateNumberUntilConditionNotAchieve(minItemValue,
maxItemValue));
const main = () => {
const generatedArray = generateArray(1, 5, 100, 200);
console.log("Array -> " + generatedArray);
}
main();
For small minArraySize and maxArraySize values sometimes I am receiving desirable result but for params like <10, 100> my IDE is freezing. On online editor with pasted above code, I am receiving information about the infinite loop on line:
while (countNumberFactors(digitsSum) === 2)
I tried to investigate a root cause by trial and error but I did not find out a solution. I will be grateful for suggestions on how to solve the above infinite loop problem.
You are changing number but checking digitsSum. All you need to do to fix this is add digitsSum = sumDigits(number) in the while loop. e.g.
const generateNumberUntilConditionNotAchieve = (min, max) => {
let number = generateNumber(min, max);
const digitsSum = sumDigits(number);
while (countNumberFactors(digitsSum) === 2) {
number = generateNumber(min, max);
digitsSum = sumDigits(number);
}
return number;
}
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}