Using parser combinator to parse simple math expression - javascript

I'm using parsimmon to parse a simple math expression and I'm failing to parse a simple math expression that follows order of operation (i.e. */ has higher precedence than +-).
Even if you are not familiar with this library please help me solve precedence problem without left recursion and infinite recursion.
Thank you.
I used TypeScript:
"use strict";
// Run me with Node to see my output!
import * as P from "parsimmon";
import {Parser} from "parsimmon";
// #ts-ignore
import util from "util";
///////////////////////////////////////////////////////////////////////
// Use the JSON standard's definition of whitespace rather than Parsimmon's.
let whitespace = P.regexp(/\s*/m);
// JSON is pretty relaxed about whitespace, so let's make it easy to ignore
// after most text.
function token(parser: Parser<string>) {
return parser.skip(whitespace);
}
// Several parsers are just strings with optional whitespace.
function word(str: string) {
return P.string(str).thru(token);
}
let MathParser = P.createLanguage({
expr: r => P.alt(r.sExpr2, r.sExpr1, r.number),
sExpr1: r => P.seqMap(r.iExpr, P.optWhitespace, r.plusOrMinus, P.optWhitespace, r.expr, (a, s1, b, s2, c) => [a, b, c]),
sExpr2: r => P.seqMap(r.iExpr, P.optWhitespace, r.multiplyOrDivide, P.optWhitespace, r.expr, (a, s1, b, s2, c) => [a, b, c]),
iExpr: r => P.alt(r.iExpr, r.number), // Issue here! this causes infinite recursion
// iExpr: r => r.number // this will fix infinite recursion but yields invalid parse
number: () =>
token(P.regexp(/[0-9]+/))
.map(Number)
.desc("number"),
plus: () => word("+"),
minus: () => word("-"),
plusOrMinus: r => P.alt(r.plus, r.minus),
multiply: () => word("*"),
divide: () => word("/"),
multiplyOrDivide: r => P.alt(r.multiply, r.divide),
operator: r => P.alt(r.plusOrMinus, r.multiplyOrDivide)
});
///////////////////////////////////////////////////////////////////////
let text = "3 / 4 - 5 * 6 + 5";
let ast = MathParser.expr.tryParse(text);
console.log(util.inspect(ast, {showHidden: false, depth: null}));
This is my repo

Currently the grammar implemented by your parser looks like this (ignoring white space):
expr: sExpr2 | sExpr1 | number
sExpr1: iExpr plusOrMinus expr
sExpr2: iExpr multiplyOrDivide expr
// Version with infinite recursion:
iExpr: iExpr | number
// Version without infinite recursion:
iExpr: number
It's pretty easy to see that iExpr: iExpr is a left-recursive production and the cause of your infinite recursion. But even if Parsimmon could handle left-recursion, there just wouldn't be any point in that production. If it didn't mess up the parser, it just wouldn't do anything at all. Just like an equation x = x does not convey any information, a production x: x doesn't make a grammar match anything it didn't match before. It's basically a no-op, but one that breaks parsers that can't handle left recursion. So removing it is definitely the right solution.
With that fixed, you now get wrong parse trees. Specifically you'll get parse trees as if all your operators were of the same precedence and right-associative. Why? Because the left-side of all of your operators is iExpr and that can only match single numbers. So you'll always have leaves as the left child of an operator node and the tree always grows to the right.
An unambiguous grammar to correctly parse left-associative operators can be written like this:
expr: expr (plusOrMinus multExpr)?
multExpr: multExpr (multiplyOrDivide primaryExpr)?
primaryExpr: number | '(' expr ')'
(The | '(' expr ')' part is only needed if you want to allow parentheses, of course)
This would lead to the correct parse trees because there's no way for a multiplication or division to have an unparenthesized addition or subtraction as a child and if there are multiple applications of operators of the same precedence ,such as 1 - 2 - 3, the outer subtraction would contain the inner subtraction as its left child, correctly treating the operator as left-associative.
Now the problem is that this grammar is left recursive, so it's not going to work with Parsimmon. One's first thought might be to change the left recursion to right recursion like this:
expr: multExpr (plusOrMinus expr)?
multExpr: primaryExpr (multiplyOrDivide multExpr)?
primaryExpr: number | '(' expr ')'
But the problem with that is that now 1 - 2 - 3 wrongly associates to the right instead of the left. Instead, the common solution is to remove the recursion altogether (except the one from primaryExpr back to expr of course) and replace it with repetition:
expr: multExpr (plusOrMinus multExpr)*
multExpr: primaryExpr (multiplyOrDivide primaryExpr)*
primaryExpr: number | '(' expr ')'
In Parsimmon you'd implement this using sepBy1. So now instead of having a left operand, an operator and a right operand, you have a left operand and then arbitrarily many operator-operand pairs in an array. You can create a left-growing tree from that by simply iterating over the array in a for-loop.

If your want to learn how to deal with left recursion, you can start from https://en.wikipedia.org/wiki/Parsing_expression_grammar
or more precisely https://en.wikipedia.org/wiki/Parsing_expression_grammar#Indirect_left_recursion
And then read more about PEG online.
But basically a standard way is to use cycles:
Expr ← Sum
Sum ← Product (('+' / '-') Product)*
Product ← Value (('*' / '/') Value)*
Value ← [0-9]+ / '(' Expr ')'
Your can find examples of this grammar everywhere.
If your want to stick to a more pleasant left-recursive grammars, you can read about packrat parser. And find another parser for yourself. Because, I'm no sure, but it looks like parsimmon is not one of them.
If you just what a working code, than you can go to https://repl.it/repls/ObviousNavyblueFiletype
I've implemented the above grammar using parsimmon API
Expr : r => r.AdditiveExpr,
AdditiveExpr: r => P.seqMap(
r.MultExpr, P.seq(r.plus.or(r.minus), r.MultExpr).many(),
left_association
),
MultExpr : r => P.seqMap(
r.UnaryExpr, P.seq(r.multiply.or(r.divide), r.UnaryExpr).many(),
left_association
),
UnaryExpr : r => P.seq(r.minus, r.UnaryExpr).or(r.PrimaryExpr),
PrimaryExpr : r => P.seqMap(
r.LPAREN, r.Expr, r.RPAREN, // without parens it won't work
(lp,ex,rp) => ex // to remove "(" and ")" from the resulting AST
).or(P.digits),
plus : () => P.string('+').thru(p => p.skip(P.optWhitespace)),
minus : () => P.string('-').thru(p => p.skip(P.optWhitespace)),
multiply : () => P.string('*').thru(p => p.skip(P.optWhitespace)),
divide : () => P.string('/').thru(p => p.skip(P.optWhitespace)),
We also need to use parentheses, or else why would you need recursion? Value ← [0-9]+ will suffice. Without parentheses, there is no need to reference Expr inside grammar. And if we do, it would not consume any input, it would have no sense whatsoever, and it would hang in infinite recursion.
So let's also add:
LPAREN : () => P.string('(').thru(p => p.skip(P.optWhitespace)),
RPAREN : () => P.string(')').thru(p => p.skip(P.optWhitespace)),
I also added unary expressions, just for completeness.
And now for the most interesting part - production functions. Without them the result for, let' say:
(3+4+6+(-7*5))
will look like this:
[[["(",[["3",[]],[["+",["4",[]]],["+",["6",[]]],["+",[["(",[[["-","7"],[["*","5"]]],[]],")"],[]]]]],")"],[]],[]]
With them, it'll be:
[[[["3","+","4"],"+","6"],"+",[["-","7"],"*","5"]]]
Much nicer.
So for left associative operators we will need this:
// input (expr1, [[op1, expr2],[op2, expr3],[op3, expr4]])
// -->
// output [[[expr1, op1, expr2], op2, expr3], op3, expr4]
const left_association = (ex, rest) => {
// console.log("input", ex, JSON.stringify(rest))
if( rest.length === 0 ) return ex
if( rest.length === 1 ) return [ex, rest[0][0], rest[0][1]]
let node = [ex]
rest.forEach(([op, ex]) => {
node[1] = op;
node[2] = ex;
node = [node]
})
// console.log("output", JSON.stringify(node))
return node
}

Related

Javascript For Loop Refactor Due to ESLint Rules and Best Practices

I need assistance refactoring my code to ES6 best practices and passing the below ESLinting rules. Below is my entire method. The for loop here is the issue.
formatNumber(val) {
if (!isFinite(val)) {
return val;
}
const valFloat = val.toString();
const decPos = valFloat.indexOf('.');
const intPart = (decPos === -1) ? valFloat : valFloat.substr(0, decPos);
const formattedNum = [];
// this needs refactoring
for (const ii in intPart) {
if (ii % 3 === 0 && ii !== 0) {
formattedNum.push();
}
formattedNum.push(intPart[intPart.length - 1 - ii]);
}
formattedNum.reverse();
return (decPos === -1) ? formattedNum.join('') : formattedNum.join('') + valFloat.slice(decPos, valFloat.length);
}
ESLint: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.(guard-for-in)
ESLint: for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.(no-restricted-syntax)
First off, it's not particularly good fun mentally parsing code: it would be helpful if you could say what your code is doing, rather than leaving it to us to parse, especially when it's doing something as odd as looping through the keys in an array, doing a calculation on the key, then using the inverse value from the array. Most unconventional!
The problem is with a for..in loop, as explained in the ESLint documentation. This ESLint rule requires that, if you use a for..in loop, you run Object.prototype.hasOwnProperty on each key, to make sure it's part of the object you want, rather than coming from some other code that's added a property to Object.prototype (or, in this case, also String.prototype). This is sensible defensive coding.
So you could do this:
for (const ii in intPart) {
if (!Object.prototype.hasOwnProperty.call(intPart, ii)) {
continue;
}
...
But that to me is not a nice solution, and it's actually possible to refactor your code in a nicer, more easy to follow way that also deals with this problem:
const formattedNum = intPart
.split('') // split the string into an array of digits
.reduceRight((acc, val, idx) => { // loop through the array with a reducer function, starting at the right
acc.unshift(val); // add the digit to the beginning of the output array
if (
idx && // if it's not the first item in the array (where idx === 0)
((intPart.length - idx) % 3 === 0) // and it is a multiple of three from the end of the array
) {
acc.unshift(','); // add a comma to the beginning of the array
}
return acc;
}, []);
Your current proposed program only "formats" a number based on (presumably) your locale. My advise is not to fix a program that is on the
wrong path entirely. Consider Number.prototype.toLocaleString and the need for your formatNumber function disappears -
num.toLocaleString([locales [, options]])
Using locales parameter -
var number = 123456.789;
// German uses comma as decimal separator and period for thousands
console.log(number.toLocaleString('de-DE'));
// → 123.456,789
// Arabic in most Arabic speaking countries uses Eastern Arabic digits
console.log(number.toLocaleString('ar-EG'));
// → ١٢٣٤٥٦٫٧٨٩
// India uses thousands/lakh/crore separators
console.log(number.toLocaleString('en-IN'));
// → 1,23,456.789
// the nu extension key requests a numbering system, e.g. Chinese decimal
console.log(number.toLocaleString('zh-Hans-CN-u-nu-hanidec'));
// → 一二三,四五六.七八九
// when requesting a language that may not be supported, such as
// Balinese, include a fallback language, in this case Indonesian
console.log(number.toLocaleString(['ban', 'id']));
// → 123.456,789
Using options parameter -
var number = 123456.789;
// request a currency format
console.log(number.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' }));
// → 123.456,79 €
// the Japanese yen doesn't use a minor unit
console.log(number.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' }))
// → ¥123,457
// limit to three significant digits
console.log(number.toLocaleString('en-IN', { maximumSignificantDigits: 3 }));
// → 1,23,000
// Use the host default language with options for number formatting
var num = 30000.65;
console.log(num.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}));
// → "30,000.65" where English is the default language, or
// → "30.000,65" where German is the default language, or
// → "30 000,65" where French is the default language

Truncate Text with Pattern

I want to truncate text in a pattern, this is a function to highlight text from an array containing matched indexes and text, but I want to truncate the text which doesn't include the part with match, see code below
const highlight = (matchData, text) => {
var result = [];
var matches = [].concat(matchData);
var pair = matches.shift();
for (var i = 0; i < text.length; i++) {
var char = text.charAt(i);
if (pair && i == pair[0]) {
result.push("<u>");
}
result.push(char);
if (pair && i == pair[1]) {
result.push("</u>");
truncatedIndex = i;
pair = matches.shift();
}
}
return result.join("");
};
console.log(
highlight(
[[23, 29], [69, 74]],
"Some text that doesn't include the main thing, the main thing is the result, you may know I meant that"
)
);
// This returns the highlighted HTML - Result will be => "Some text that doesn't <u>include</u> the main thing, the main thing is the <u>result</u>, you may know I meant that"
But this returns whole text, I want to truncate other texts in the range, I want to truncate other text but not in range of 20 characters before and after the result so the text can be clean as well as understandable. Like
"... text that doesn't <u>include</u> the main thing ... the <u>result</u> you may know I ..."
I can't find out a way to make that. Help is appreciated.
I've modified your function considerably, to make it easier to understand, and so that it works...
Instead of using an array of arrays, which I find cumbersome to deal with, I modified to use an array of objects. The objects are simple:
{
start: 23,
end: 30
}
Basically, it just adds names to the indices you had previously.
The code should be relatively easy to follow. Here's a line-by-line explanation:
Armed with the new structure, you can use a simple substring command to snip the appropriate piece of text.
Since we're in a loop, and I don't want two sets of ellipses between matches, I check to see if we're on the first pass through and only add an ellipses before the match on the first pass.
The text before the piece we've snipped is the 20 characters before the start of the match, or the number of characters to the beginning of the string. Math.max() provides a easy way of getting the highest index available.
The text after the piece we've snippet is the 20 characters after the end of the match, or the number of characters to the end of the string. Math.min() provides a easy way of getting the lowest index available.
Concatentating them together, we get the match's new text. I'm using template literals to make that easier to read than a bunch of + " " + and whatnot.
const highlight = (matches, text) => {
let newText = '';
matches.forEach((match) => {
const piece = text.substring(match.start, match.end);
const preEllipses = newText.length === 0 ? '... ' : '';
const textBefore = text.substring(Math.max(0, match.start - 20), match.start);
const textAfter = text.substring(match.end, Math.min(text.length - 1, match.end + 20));
newText += `${preEllipses}${textBefore}<u>${piece}</u>${textAfter} ... `;
});
return newText.trim();
}
// Sample Usage
const result = highlight([{ start: 23, end: 30 }, { start: 69, end: 75 }], "Some text that doesn't include the main thing, the main thing is the result, you may know I meant that");
console.log(result);
document.getElementById("output").innerHTML = result;
// Result will be => "... e text that doesn't <u>include</u> the main thing, the ... e main thing is the <u>result</u>, you may know I mea ..."
<div id="output"></div>
Note that I am using simple string concatenation here, rather than putting parts into an array and using join. Modern JavaScript engines optimize string concatenation extremely well, to the point where it makes the most sense to just use it. See e.g., Most efficient way to concatenate strings in JavaScript?, and Dr. Axel Rauschmayer's post on 2ality.
Note
There's an update below that I think shows a better version of this same idea. But this is where it started.
Original Version
Here's another attempt, building a more flexible solution out of reusable parts.
const intoPairs = (xs) =>
xs .slice (1) .map ((x, i) => [xs[i], x])
const splitAtIndices = (indices, str) =>
intoPairs (indices) .map (([a, b]) => str .slice (a, b))
const alternate = Object.assign((f, g) => (xs, {START, MIDDLE, END} = alternate) =>
xs .map (
(x, i, a, pos = i == 0 ? START : i == a.length - 1 ? END : MIDDLE) =>
i % 2 == 0 ? f (x, pos) : g (x, pos)
),
{START: {}, MIDDLE: {}, END: {}}
)
const wrap = (before, after) => (s) => `${before}${s}${after}`
const truncate = (count) => (s, pos) =>
pos == alternate.START
? s .length <= count ? s : '... ' + s .slice (-count)
: pos == alternate.END
? s .length <= count ? s : s .slice (0, count) + ' ...'
: // alternate.MIDDLE
s .length <= (2 * count) ? s : s .slice (0, count) + ' ... ' + s .slice (-count)
const highlighter = (f, g) => (ranges, str, flip = ranges[0][0] == 0) =>
alternate (flip ? g : f, flip ? f : g) (
splitAtIndices ([...(flip ? [] : [0]), ...ranges .flat() .sort((a, b) => a - b), str.length], str)
) .join ('')
const highlight = highlighter (truncate (20), wrap('<u>', '</u>'))
#output {padding: 0 1em;} #input {padding: .5em 1em 0;} textarea {width: 50%; height: 3em;} button, input {vertical-align: top; margin-left: 1em;}
<div id="input"> <textarea id="string">Some text that doesn't include the main thing, the main thing is the result, you may know I meant that</textarea> <input type="text" id="indices" value="[23, 30], [69, 75]"/> <button id="run">Highlight</button></div><h4>Output</h4><div id="output"></div> <script>document.getElementById('run').onclick = (evt) => { const str = document.getElementById('string').value; const idxString = document.getElementById('indices').value; const idxs = JSON.parse(`[${idxString}]`); const result = highlight(idxs, str); console.clear(); document.getElementById('output').innerHTML = ''; setTimeout(() => { console.log(result); document.getElementById('output').innerHTML = result; }, 300)}</script>
This involves the helper functions intoPairs, splitAtIndices alternate, wrap and truncate. I think they are best show by examples:
intoPairs (['a', 'b', 'c', 'd']) //=> [['a', 'b'], ['b', 'c'], ['c', 'd']]
splitAtIndices ([0, 3, 7, 15], 'abcdefghijklmno') //=> ["abc", "defg", "hijklmno"]
// ^ ^ ^ ^ `---' `----' `--------'
// | | | | | | |
// 0 3 7 15 0 - 3 4 - 7 8 - 15
alternate (f, g) ([a, b, c, d, e, ...]) //=> [f(a), g(b), f(c), g(d), f(e), ...]
wrap ('<div>', '</div>') ('foo bar baz') //=> '<div>foo bar baz</div>
//chars---+ input---+ position---+ output--+
// | | | |
// V V V V
truncate (10) ('abcdefghijklmnop', ~START~) //=> '... ghijklmnop'
truncate (10) ('abcdefghijklmnop', ~END~) //=> 'abcdefghij ...'
truncate (10) ('abcdefghijklmnop', ~MIDDLE~) //=> 'abcdefghijklmnop'
truncate (10) ('abcdefghijklmnopqrstuvwxyz', ~MIDDLE~) //=> 'abcdefghij ... qrstuvwxyz'
All of these are potentially reusable, and I personally have intoPairs and wrap in my general utility library.
truncate is the only complex one, and that is mostly because it does triple duty, handling the first string, the last string, and all the others in three distinct manners. You first supply a count and the you give a string as well as the position (START, MIDDLE, END, stored as properties of alternate.) For the first string, it includes an ellipsis (...) and the last count characters. For the last one, it includes the first count characters and an ellipsis. For the middle ones, if the length is shorter than double count, it returns the whole thing; otherwise it includes the first count characters, an ellipsis and the last count characters. This behavior might be different from what you want; if so,
The main function is highlighter. It accepts two functions. The first one is how you want to handle the non-highlighted sections. The second is for the highlighted ones. It returns the style function you were looking for, one that accepts an array of two-element arrays of numbers (the ranges) and your input string, returning a string with the highlighted ranges and the non-highlighted ranges.
We use it to generate the highlight function by passing it truncate (20) and wrap('<u>', '</u>').
The intermediate forms might make it clearer what's going on.
We start with these indices:
[[23, 30], [69, 75]]]
and our 103-character string,
"Some text that doesn't include the main thing, the main thing is the result, you may know I meant that"
First we flatten the ranges, prepending a zero if the first range doesn't start there and appending the last index of the string, to get this:
[0, 23, 30, 69, 75, 102]
We pass that to splitAtIndices, along with our string, to get
[
"Some text that doesn't ",
"include",
" the main thing, the main thing is the ",
"result",
", you may know I meant that"
]
Then we map the appropriate functions over each of these strings to get
[
"... e text that doesn't ",
"<u>include</u>",
" the main thing, the main thing is the ",
"<u>result</u>",
", you may know I mea ..."
]
and join those together to get our final results:
"... e text that doesn't <ul>include</ul> the main thing, the main thing is the <ul>result</ul>, you may know I mea ..."
I like the flexibility this offers. It's easy to alter the highlight strategy as well as how you handle the unhighlighted parts -- just pass a different function to highlighter. It's also a useful breakdown of the work into reusable parts.
But there are two things I don't like.
First, I'm not thrilled with the handling of middle unhighlighted sections. Of course it's easy to change; but I don't know what would be appropriate. You might, for instance, want to change the doubling applied to the count there. Or you might have an entirely different idea.
Second, truncate is dependent upon alternate. We have to somehow pass signals from alternate to the two functions supplied to it to let them know where we are. My first pass involved passing the index and the entire array (the Array.prototype.map signature) to those functions. But that felt too coupled. We could make START, MIDDLE, and END into module-local properties, but then alternate and truncate would not be reusable. I'm not going to go back and try it now, but I think a better solution might be to pass four functions to highlighter: the function for the highlighted sections, and one each for start, middle, and end positions of the non-highlighted ones.
Update
I did go ahead and try that alternative I mentioned, and I think this version is cleaner, with all the complexity located in the single function highlighter:
const intoPairs = (xs) =>
xs .slice (1) .map ((x, i) => [xs[i], x])
const splitAtIndices = (indices, str) =>
intoPairs (indices) .map (([a, b]) => str .slice (a, b))
const wrap = (before, after) => (s) => `${before}${s}${after}`
const truncateStart = (count) => (s) =>
s .length <= count ? s : '... ' + s .slice (-count)
const truncateMiddle = (count) => (s) =>
s .length <= (2 * count) ? s : s .slice (0, count) + ' ... ' + s .slice (-count)
const truncateEnd = (count) => (s) =>
s .length <= count ? s : s .slice (0, count) + ' ...'
const highlighter = (highlight, start, middle, end) =>
(ranges, str, flip = ranges[0][0] == 0) =>
splitAtIndices ([...(flip ? [] : [0]), ...ranges .flat() .sort((a, b) => a - b), str.length], str)
.map (
(s, i, a) =>
(flip
? (i % 2 == 0 ? highlight : i == a.length - 1 ? end : middle)
: (i == 0 ? start : i % 2 == 1 ? highlight : i == a.length - 1 ? end : middle)
) (s)
) .join ('')
const highlight = highlighter (
wrap('<u>', '</u>'),
truncateStart(20),
truncateMiddle(20),
truncateEnd(20)
)
console .log (
highlight (
[[23, 30], [69, 75]],
"Some text that doesn't include the main thing, the main thing is the result, you may know I meant that"
)
)
console .log (
highlight (
[[23, 30], [86, 92]],
"Some text that doesn't include the main thing, because you see, the main thing is the result, you may know I meant that"
)
)
There is some real complexity built into highlighter, but I think it's fairly intrinsic to the problem. On each iteration, we have to choose one of our four functions based on the index, the length of the array, and whether the first range started at zero. This bit here simply chooses the function based on all that:
(flip
? (i % 2 == 0 ? highlight : i == a.length - 1 ? end : middle)
: (i == 0 ? start : i % 2 == 1 ? highlight : i == a.length - 1 ? end : middle)
)
where the flip boolean simply reports whether the first range starts at 0, a is the array of substrings to handle., and i is the current index in the array. If you see a cleaner way of choosing the function, I'd love to know.
If we wanted to write a gloss for this sort of highlighting, we could easily write
const truncatingHighlighter = (count, start, end) =>
highlighter (
wrapp(start, end),
truncateStart(count),
truncateMiddle(count),
truncateEnd(count)
)
const highlight = truncatingHighlighter (20, '<u>', '</u>')
I definitely think this is a superior solution.

How to print an n-bit number with a custom n-length character set in JavaScript without using toString

In the same way we have the hex "number" using the characters 123456789abcdef, and you can simply do integer.toString(16) to go from integer to hex:
> (16).toString(16)
'10'
... I would like to instead use a custom character set, and a custom base. So for hex say I wanted to use the characters 13579acegikmoqsu, then it would be something like this:
> (16).toString(16, '13579acegikmoqsu')
'ik'
I don't actually know what the output value would be in this case, just made that up. But I am looking for how to do this in JavaScript.
Another example outside of hex would be a, for example, base 6 number converted to a string using the character set and123, so it would be something like this:
> (16).toString(6, 'and123')
'a3d'
I don't know what the value is in this case here either, I don't know how to calculate it. Basically wondering how to do this in JavaScript, not necessarily using this toString api, preferably it would be a bit more low-level so I could also understand the logic behind it.
Likewise, it would be helpful to know how to reverse it, so to go from a3d => 16 as in this pseudo-example.
You could map the character values of the integer value as index
function toString(n, characters) {
var radix = characters.length;
return Array
.from(n.toString(radix), v => characters[parseInt(v, radix)])
.join('');
}
console.log(toString(16, '13579acegikmoqsu')); // 31
A version without toString and parseInt.
function toString(n, characters) {
var radix = characters.length,
temp = [];
do {
temp.unshift(n % radix);
n = Math.floor(n / radix);
} while (n)
return temp
.map(i => characters[i])
.join('');
}
console.log(toString(16, '13579acegikmoqsu')); // 31

JavaScript - Matching alphanumeric patterns with RegExp

I'm new to RegExp and to JS in general (Coming from Python), so this might be an easy question:
I'm trying to code an algebraic calculator in Javascript that receives an algebraic equation as a string, e.g.,
string = 'x^2 + 30x -12 = 4x^2 - 12x + 30';
The algorithm is already able to break the string in a single list, with all values on the right side multiplied by -1 so I can equate it all to 0, however, one of the steps to solve the equation involves creating a hashtable/dictionary, having the variable as key.
The string above results in a list eq:
eq = ['x^2', '+30x', '-12', '-4x^2', '+12x', '-30'];
I'm currently planning on iterating through this list, and using RegExp to identify both variables and the respective multiplier, so I can create a hashTable/Dictionary that will allow me to simplify the equation, such as this one:
hashTable = {
'x^2': [1, -4],
'x': [30, 12],
' ': [-12]
}
I plan on using some kind of for loop to iter through the array, and applying a match on each string to get the values I need, but I'm quite frankly, stumped.
I have already used RegExp to separate the string into the individual parts of the equation and to remove eventual spaces, but I can't imagine a way to separate -4 from x^2 in '-4x^2'.
You can try this
(-?\d+)x\^\d+.
When you execute match function :
var res = "-4x^2".match(/(-?\d+)x\^\d+/)
You will get res as an array : [ "-4x^2", "-4" ]
You have your '-4' in res[1].
By adding another group on the second \d+ (numeric char), you can retrieve the x power.
var res = "-4x^2".match(/(-?\d+)x\^(\d+)/) //res = [ "-4x^2", "-4", "2" ]
Hope it helps
If you know that the LHS of the hashtable is going to be at the end of the string. Lets say '4x', x is at the end or '-4x^2' where x^2 is at end, then we can get the number of the expression:
var exp = '-4x^2'
exp.split('x^2')[0] // will return -4
I hope this is what you were looking for.
function splitTerm(term) {
var regex = /([+-]?)([0-9]*)?([a-z](\^[0-9]+)?)?/
var match = regex.exec(term);
return {
constant: parseInt((match[1] || '') + (match[2] || 1)),
variable: match[3]
}
}
splitTerm('x^2'); // => {constant: 1, variable: "x^2"}
splitTerm('+30x'); // => {constant: 30, variable: "x"}
splitTerm('-12'); // => {constant: -12, variable: undefined}
Additionally, these tool may help you analyze and understand regular expressions:
https://regexper.com/
https://regex101.com/
http://rick.measham.id.au/paste/explain.pl

Eval alternative

This code works as a calculator, but the scratch pad at codeacademy tells me that eval is evil. Is there another way to do the same thing without using eval?
var calculate = prompt("Enter problem");
alert(eval(calculate));
eval evaluates the string input as JavaScript and coincidentally JavaScript supports calculations and understands 1+1, which makes it suitable as a calculator.
If you don't want to use eval, which is good, you have to parse that string yourself and, finally, do the computation yourself (not exactly yourself though). Have a look at this math processor, which does what you want.
Basically what you do is:
Read the input string char by char (with this kind of problem it's still possible)
Building a tree of actions you want to do
At the end of the string, you evaluate the tree and do some calculations
For example you have "1+2/3", this could evaluate to the following data structure:
"+"
/ \
"1" "/"
/ \
"2" "3"
You could then traverse that structure from top to bottom and do the computations.
At first you've got the "+", which has a 1 on the left side and some expression on the right side,
so you have to evaluate that expression first. So you go to the "/" node, which has two numeric children. Knowing that, you can now compute 2/3 and replace the whole "/" node with the result of that. Now you can go up again and compute the result of the "+" node: 1 + 0.66. Now you replace that node with the result and all you've got left is the result of the expression.
Some pseudo code on how this might look in your code:
calculation(operator, leftValue, rightValue):
switch operator {
case '+': return leftValue + rightValue
case '-': return 42
}
action(node):
node.value = calculation(node.operator, action(node.left) action(node.right))
As you might have noticed, the tree is designed in such a way that it honors operator precedence. The / has a lower level than the +, which means it get's evaluated first.
However you do this in detail, that's basically the way to go.
You can use the expression parser that is included in the math.js library:
http://mathjs.org
Example usage:
mathjs.evaluate('1.2 / (2.3 + 0.7)'); // 0.4
mathjs.evaluate('5.08 cm in inch'); // 2 inch
mathjs.evaluate('sin(45 deg) ^ 2'); // 0.5
mathjs.evaluate('9 / 3 + 2i'); // 3 + 2i
mathjs.evaluate('det([-1, 2; 3, 1])'); // -7
You can use eval safely for a simple arithmetic calculator by filtering the input- if you only accept digits, decimal points and operators (+,-,*,/) you won't get in much trouble. If you want advanced Math functions, you are better off with the parser suggestions.
function calculate(){
"use strict";
var s= prompt('Enter problem');
if(/[^0-9()*+\/ .-]+/.test(s)) throw Error('bad input...');
try{
var ans= eval(s);
}
catch(er){
alert(er.message);
}
alert(ans);
}
calculate()
I write some functions when I had a problem like this. Maybe this can help:
data = [
{id:1,val1:"test",val2:"test2",val2:"test3"},
{id:2,val1:"test",val2:"test2",val2:"test3"},
{id:3,val1:"test",val2:"test2",val2:"test3"}
];
datakey = Object.keys(data[0]);
// here's a fix for e['datakey[f]'] >> e[x]
vix = function(e,f){
a = "string";
e[a] = datakey[f];
x = e.string;
end = e[x];
delete e.string;
return end;
};
// here's a fix to define that variable
vox = function(e,f,string){
a = "string";
e[a] = datakey[f];
x = e.string;
end = e[x] = string;
delete e.string;
};
row = 2 // 3th row ==> {id:3,val1:"test",val2:"test2",val2:"test3"}
column = 1 //datakey 2 ==> val1
vox(data[row],column,"new value");
alert(data[2].val1); //the value that we have changed

Categories

Resources