Need help extracting numbers from string in JavaScript - javascript

I need a rock solid RegExp to try and solve some issue with Raphael.js parseStringPath processing regarding Arc path commands and possible others (SnapSVG also inherits the problem). You see, arcTo path command accepts 7 coordinates and settings, but some strings might be malformed due to extreme optimization and the browser doesn't flag them, rather renders them properly. Check Raphael.js demo here.
Have a look at this example, I'm using the RegExp from Raphael.js and a very simplistic example with my own RegExp called incorrectReg, trying to break strings like 000 into [0,0,0] or 011 into [0,1,1].
let spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029",
pathValues = new RegExp(`(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[${spaces}]*,?[${spaces}]*`, `ig`),
incorectReg = new RegExp(`([${spaces}]*0(?=[a-z0-9])|([${spaces}]\\0)*0(?=[a-z0-9]*))`, `ig`); // THIS ONE
function action(){
let input = document.getElementById('input'),
output = document.getElementById('output'),
pathValue = input.getAttribute('d'),
segments = pathValue.replace(/([a-z])/gi,'|$1').split('|').filter(x=>x.trim()),
pathArray = []
segments.map(x=>{
let pathCommand = x[0],
pathParams = x.replace(pathCommand,'').trim()
pathArray.push( [pathCommand].concat(
pathParams.replace(',',' ')
.replace(pathValues,' $1 ')
.replace(incorectReg,'$1 ')
.split(' '))
.filter(x=>x)
);
})
output.setAttribute('d',pathArray.map(x=>x.join(' ')).join(''))
console.table(pathArray)
}
svg {max-width:49%}
<button onclick="action()">Extract</button>
<hr>
<svg viewBox="0 0 16 16">
<path id="input" d="M2,0a2 2 0 00,-2 2a2 2 0 002 2a.5.5 0 011 0z" stroke="red" stroke-width="1px" fill="none"></path>
</svg>
<svg viewBox="0 0 16 16">
<path id="output" d="M0 0" stroke="green" stroke-width="1" fill="none"></path>
</svg>
As you can see in your browser console, we already solve the 000 group (which is obviously not a valid number, boolean, or anything specific), we just have to solve 011 and 11, where all these groups are in fact a string of booleans.
So again, the arcTo path command works with
arcTo -> ['A', rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y]
// str, float, float, float, boolean (0|1), boolean (0|1), float, float
I need a better incorrectReg RegExp and a combination of solutions to properly handle mainly arcTo, and other similar cases. Open to any suggestion.
Thank you

According to the discussion below OP, I propose not to use regexp, but rather a proper parser (or lexer or tokenizer or how to correctly call it).
You can
write your own parser (nice excercise)
use something existing, e.g. I have successfully tried
svg-path-parser.
I am not even sure if such "super"regexp is possible to create.. Anyway you can use "sub"regexp in the parsing process :-)

Just for clarity and serving the community, I will post a working solution, it might help somebody in the future.
Unfortunately the incorrectReg RegExp, good or bad cannot work because it can also change other values as well (EG: M0,11 returns ["M",0,1,1] with the RegExp provided by TheFourthBird), so yea Jan, you were right!
Here's a working solution, please feel free to edit or add more clarity if you like. Once we all agree on a rock solid solution, I will submit a PR to Raphael right after.
let spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029",
pathValues = new RegExp(`(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[${spaces}]*,?[${spaces}]*`, `ig`),
incorrectReg = new RegExp(`(?<=[01${spaces}]+)([01])[${spaces}]*`, `g`); // FIXED ONE
function action(){
let input = document.getElementById('input'),
output = document.getElementById('output'),
pathValue = input.getAttribute('d'),
segments = pathValue.replace(/([a-z])/gi,'|$1').split('|').filter(x=>x.trim()),
pathArray = []
segments.map(x=>{
let pathCommand = x[0],
pathParams = x.replace(pathCommand,'').trim();
pathParams = pathParams.replace(',',' ')
.replace(pathValues,' $1 ')
/* .replace(incorrectReg,' $& ') */
.split(' ').filter(x=>x);
if ( pathCommand.toLowerCase() === 'a' && pathParams.length < 7){
for (let i=0, ln = pathParams.length; i<ln; i++){
if ( (i === 3 || i === 4) && pathParams[i].length > 1 ) {
pathParams = pathParams.slice(0,i) // first part of array
.concat(pathParams[i][0]) // extract largeArcFlag OR sweepFlag
.concat(
pathParams[i].slice(1).replace(/(\-\d|\-\.\d|\.\d*(?=\.))/g,'|$1').split('|'), // get sweepFlag
pathParams.slice(i+1)) // continue after flags
.filter(x=>x) // remove added empty "space" items
ln = pathParams.length // update length
}
}
if (pathParams.length === 7) {
pathArray.push([pathCommand].concat(pathParams.splice(0, 7)));
} else {
throw Error(`arcTo requires 7 coordinates, only ${pathParams.length + ' given: ['+pathParams.join(',')}]`)
}
} else {
pathArray.push( [pathCommand].concat(pathParams) );
}
})
output.setAttribute('d',pathArray.map(x=>x.join(' ')).join(''))
// console.log(pathArray)
}
svg {max-width:49%}
<button onclick="action()">Extract</button>
<hr>
<svg viewBox="0 0 16 16">
<path id="input" d="M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2zm7.5 11h-4a.5.5 0 01-.5-.5v-4a.5.5 0 011 0v2.793l4.146-4.147a.5.5 0 01.708.708L6.707 10H9.5a.5.5 0 010 1z" fill="red"></path>
</svg>
<svg viewBox="0 0 16 16">
<path id="output" d="M0 0" fill="green"></path>
</svg>

Related

GLPK.js no primal feasible solution when a solution exists

I am using the glpk.js library in an Angular application to solve an ILP problem. I have been using the library for some time now and it usually works well. I have encountered similar issues in the past, but I was able to sidestep them without finding out why they occurred. It might very well be the case, that I am not using the library correctly as their documentation is quite lacking.
I construct a "base" ILP problem and then I iterate over some array, construct additional constraints depending on each element of my array and try to solve the base ILP with the new constrains for each element.
I know there is a solution for each of the ILPs, but the solver returns PROBLEM HAS NO PRIMAL FEASIBLE SOLUTION for all but one of the ILPs.
My base ILP (in human readable format):
p0 >= 0
p1 >= 0
p2 >= 0
p3 >= 0
p4 >= 0
p5 >= 0
p6 >= 0
p7 >= 0
p0 +p1 +p2 +p3 +p4 +p5 +p6 +p7 >= 1
p1 -p0 -rise0 = 0
p2 +p3 -p1 -rise1 = 0
p4 -p2 -rise2 = 0
p6 -p4 -rise3 = 0
p10 -p6 -p5 -rise4 = 0
p5 -p3 -rise5 = 0
where the objective function is to minimise the sum of the p-variables.
when I apply the following additional constraints, the solver returns a solution (p10 = 1, all other p = 0):
rise0 = 0
rise1 = 0
rise2 = 0
rise3 = 0
rise4 = 1
rise5 = 0
p0 = 0
when I apply the following additional constraints, the solver returns no solution, even if p0 = 1, all other p = 0, solves the ILP:
rise0 = -1
rise1 = 0
rise2 = 0
rise3 = 0
rise4 = 0
rise5 = 0
p0 = 1
all the other sets of constrains also contain some rise with a negative value, which seems to cause the issue.
I am using the following configuration as input to the solver (JSON for the second example ILP):
{
"name":"p0",
"objective": {
"direction":1,
"name":"region",
"vars": [
{"name":"p0","coef":1},
{"name":"p1","coef":1},
{"name":"p2","coef":1},
{"name":"p3","coef":1},
{"name":"p4","coef":1},
{"name":"p5","coef":1},
{"name":"p6","coef":1},
{"name":"p7","coef":1}
]
},
"subjectTo": [
{"name":"c0","vars":[{"name":"p0","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c1","vars":[{"name":"p1","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c2","vars":[{"name":"p2","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c3","vars":[{"name":"p3","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c4","vars":[{"name":"p4","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c5","vars":[{"name":"p5","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c6","vars":[{"name":"p6","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c7","vars":[{"name":"p7","coef":1}],"bnds":{"type":2,"ub":0,"lb":0}},
{"name":"c8","vars":[{"name":"p0","coef":1},{"name":"p1","coef":1},{"name":"p2","coef":1},{"name":"p3","coef":1},{"name":"p4","coef":1},{"name":"p5","coef":1},{"name":"p6","coef":1},{"name":"p7","coef":1}],"bnds":{"type":2,"ub":0,"lb":1}},
{"name":"c9","vars":[{"name":"p1","coef":1},{"name":"p0","coef":-1},{"name":"rise0","coef":-1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c10","vars":[{"name":"p2","coef":1},{"name":"p3","coef":1},{"name":"p1","coef":-1},{"name":"rise1","coef":-1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c11","vars":[{"name":"p4","coef":1},{"name":"p2","coef":-1},{"name":"rise2","coef":-1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c12","vars":[{"name":"p6","coef":1},{"name":"p4","coef":-1},{"name":"rise3","coef":-1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c13","vars":[{"name":"p7","coef":1},{"name":"p6","coef":-1},{"name":"p5","coef":-1},{"name":"rise4","coef":-1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c14","vars":[{"name":"p5","coef":1},{"name":"p3","coef":-1},{"name":"rise5","coef":-1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c15","vars":[{"name":"rise0","coef":1}],"bnds":{"type":5,"ub":-1,"lb":-1}},
{"name":"c16","vars":[{"name":"rise1","coef":1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c17","vars":[{"name":"rise5","coef":1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c18","vars":[{"name":"rise2","coef":1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c19","vars":[{"name":"rise3","coef":1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c20","vars":[{"name":"rise4","coef":1}],"bnds":{"type":5,"ub":0,"lb":0}},
{"name":"c21","vars":[{"name":"p0","coef":1}],"bnds":{"type":5,"ub":1,"lb":1}}
],
"binaries":[],
"generals": ["p0","p1","p2","p3","p4","p5","p6","p7","rise0","rise1","rise2","rise3","rise4","rise5"]
}
I assumed all integers (including negative) are allowed as solutions. But the only logical explanation to my problem seems to be that this is not the case. How can I enable negative integers as possible solutions?
I should have just researched the library more. There is an issue in the repository of the library that answers my question.
It really is the case that variables are bound to non-negative integers.
Variables with negative values can be expressed by splitting them into a positive and a negative part.
In my case it means I have to create a rise0plus and a rise0minus variable and modify my constrains as follows (for each rise variable):
p1 - p0 -rise0plus +rise0minus = 0
...
rise0plus -rise0minus = -1

Convert numbers to degrees in Angular 8

I'm getting 25 from the API which must be converted to -45 and the end result must be:
<div style="transform: rotate(-45deg);">
My current code looks like this:
<div style="transform: rotate({{data.angle}}deg);">
Basically, I need to convert
0 to -90
25 to -45
50 to 0
75 to 45
100 is 90
FYI, I'm creating a speedometer. You may see the HTML demo: https://jsfiddle.net/bcgzrdfL/
This is more of a mathematics problem than programming one. Linear equation y = a*x + b satisfies your transformation conditions. After solving it, given your inputs you'll get the following function:
toDegrees(input: number): number {
return 1.8 * input - 90;
}
And the usage in the template:
<div style="transform: rotate({{toDegrees(data.angle)}}deg);">

Parse textarea and display average via JavaScript

I've been searching and haven't found any way to do this in JavaScript or if there is a better way.
My form has a text area field that specific string is entered such as:
1/5/8 18 31.2 0 1847550953 13013135 5598945 3.00e-01
1/5/9 18 34.2 0 1748942583 6401826 5598945 3.00e-01
1/5/10 18 34.6 0 1847550953 13013135 5598945 3.00e-01
1/5/11 18 34.4 0 1847550953 13013135 5598945 3.00e-01
The data comes in this format but the numbers may be different. What I'm trying to do is have a script that grabs what is in the 3rd column so in this example the 31.2, 34.2, 34.6 and 34.4 then takes those numbers, gives me their average by adding them up and dividing by 4 and then displaying the result in a different textarea box.
I'm also wondering if it can be done in a single script or does it need two scripts. One to parse then the other to calculate and display the average in a textarea.
split the lines and map to match the third column. Then you can find the average and put the result in another textarea:
const input = `1/5/8 18 31.2 0 1847550953 13013135 5598945 3.00e-01
1/5/9 18 34.2 0 1748942583 6401826 5598945 3.00e-01
1/5/10 18 34.6 0 1847550953 13013135 5598945 3.00e-01
1/5/11 18 34.4 0 1847550953 13013135 5598945 3.00e-01`;
const thirdRowMatches = input.split('\n')
.map(line => line.split(/ +/)[2])
const avg = thirdRowMatches.reduce((a, str) => a + Number(str), 0) / thirdRowMatches.length;
document.querySelector('#textarea2').value = avg;
<textarea id="textarea2"></textarea>

Node.js with Geddy: Does geddy.string.uuid(x) make sure the string is unique?

I am following a tutorial on making a simple app with Node.js and Geddy. For each new entry it sets the entry id with geddy.string.uuid(10) and doesn't seem to run any other checks. My concern is when creating a new entry this could generate the same ID as a previous entry and the save script in the tutorial will overwrite/update an entry if the id already exists.
I know this would be a rare occurrence if it could happen, but I feel it is not responsible to leave this to chance if it can. I know in PHP I would be running a check of some sort to make sure it is unique, and just wanted to know if maybe Geddy was doing this already for me being the tutorial seems to not worry about it?
Thank you.
There is no real chance of a uuid occurring twice.
Technically, there is a one in 340,282,366,920,938,463,463,374,607,431,768,211,456 (16 to the 32nd power) chance that there will be a duplicate. Geddy's uuid function (like most others) uses 32 hex digits (128 bits) of random bits. See http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates.
Basically, you're not going to get a duplicate.
As a side note, it's interesting that you're passing 10 uuid here: geddy.string.uuid(10). In the latest version of getty string.js, uuid does not take an argument, so the 10 will be ignored.
148 this.uuid = function () {
149 var chars = _UUID_CHARS, uuid = [], rnd=0, r;
150 for (var i = 0; i < 36; i++) {
151 if (i==8 || i==13 || i==18 || i==23) {
152 uuid[i] = '-';
153 } else {
154 if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0;
155 r = rnd & 0xf;
156 rnd = rnd >> 4;
157 uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
158 }
159 }
160 return uuid.join('');
161 };
If the 10 in your version of geddy is limiting the number of hex digits to 10, then you only have a 1 in 1,099,511,627,776 (1 trillion) chance of a collision. That's still quite unlikely, though it could be brute forced to find a duplicate id.

Regex to match SVG path data in javascript

var string = "M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z";
var regex = "[a-zA-Z][0-9, /-/.]*";
var array = string.match(regex);
Can anyone help me with my regular expression to match individual intructions (array[0] == "M-84.1487,-15.8513"; array[1] == "a22.4171,22.4171 0 1 0 0,31.7026";)
Thanks a lot
According to the BNF description you can identify an instruction as a letter followed by anything that is not a letter.
var pathData = "M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z";
var pathSegmentPattern = /[a-z][^a-z]*/ig;
var pathSegments = pathData.match(pathSegmentPattern);
Of course you may want to trim the results, but that shouldn't be too hard.
Also try not to name your variables so meaningless (string, regex, array). In my opinion that's worse than naming them a, b or c.

Categories