javascript regex look behind to replace a character - javascript

I have a long strings of code that look something like
hs2^Ȁ_^HELLO_x_fs2^Ȁ_^WORLD_x_gn3^Ȁ_^HOME_x_gs3^Ȁ…
I need to do a replace. A hex character is used repeatedly Ȁ and there’s always a ^ in front of it. I need to change the number that appears before each ^Ȁ Reduce those numbers by 1. So the final result will be…
hs1^Ȁ_^HELLO_x_fs1^Ȁ_^WORLD_x_gn2^Ȁ_^HOME_x_gs2^Ȁ…
I’m really only dealing with two numbers here, 2 or 3, so the code would read something like this…
If (any number directly before ^Ȁ ==2) change it to 1
else if (any number directly before ^Ȁ ==3) change it to 2
I’ve heard of something called a “lookback” or “look behind” is that what’s needed here?

You can use replace with a callback function, which will be used to replace each occurrence using your own logic:
var str = "hs2^Ȁ_^HELLO_x_fs2^Ȁ_^WORLD_x_gn3^Ȁ_^HOME_x_gs3^Ȁ";
var res = str.replace(/\d(?=\^Ȁ)/g, num => --num);
console.log(res);
In the regex above, you'll notice this: (?=...). It's a positive lookahead, as suggested by #revo. It allows you to match ^Ȁ, but avoid passing it to your callback function. Only the digit (\d) will be passed, and thus, replaced.

Related

Applying currency format using replace and a regular expression

I am trying to understand some code where a number is converted to a currency format. Thus, if you have 16.9 it converts to $16.90. The problem with the code is if you have an amount over $1,000, it just returns $1, an amount over $2,000 returns $2, etc. Amounts in the hundreds show up fine.
Here is the function:
var _formatCurrency = function(amount) {
return "$" + parseFloat(amount).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,')
};
(The reason the semicolon is after the bracket is because this function is in itself a statement in another function. That function is not relevant to this discussion.)
I found out that the person who originally put the code in there found it somewhere but didn't fully understand it and didn't test this particular scenario. I myself have not dealt much with regular expressions. I am not only trying to fix it, but to understand how it is working as it is now.
Here's what I've found out. The code between the backslash after the open parenthesis and the backslash before the g is the pattern. The g means global search. The \d means digit, and the (?=\d{3})+\. appears to mean find 3 digits plus a decimal point. I'm not sure I have that right, though, because if that was correct shouldn't it ignore numbers like 5.4? That works fine. Also, I'm not sure what the '$1,' is for. It looks to me like it is supposed to be placed where the digits are, but wouldn't that change all the numbers to $1? Also, why is there a comma after the 1?
Regarding your comment
I was hoping to just edit the regex so it would work properly.
The regex you are currently using is obviously not working for you so I think you should consider alternatives even if they are not too similar, and
Trying to keep the code change as small as possible
Understandable but sometimes it is better to use a code that is a little bit bigger and MORE READABLE than to go with compact and hieroglyphical.
Back to business:
I'm assuming you are getting a string as an argument and this string is composed only of digits and may or may not have a dot before the last 1 or 2 digts. Something like
//input //intended output
1 $1.00
20 $20.00
34.2 $34.20
23.1 $23.10
62516.16 $62,516.16
15.26 $15.26
4654656 $4,654,656.00
0.3 $0.30
I will let you do a pre-check of (assumed) non-valids like 1. | 2.2. | .6 | 4.8.1 | 4.856 | etc.
Proposed solution:
var _formatCurrency = function(amount) {
amount = "$" + amount.replace(/(\d)(?=(\d{3})+(\.(\d){0,2})*$)/g, '$1,');
if(amount.indexOf('.') === -1)
return amount + '.00';
var decimals = amount.split('.')[1];
return decimals.length < 2 ? amount + '0' : amount;
};
Regex break down:
(\d): Matches one digit. Parentheses group things for referencing when needed.
(?=(\d{3})+(\.(\d){0,2})*$). Now this guy. From end to beginning:
$: Matches the end of the string. This is what allows you to match from the end instead of the beginning which is very handy for adding the commas.
(\.(\d){0,2})*: This part processes the dot and decimals. The \. matches the dot. (\d){0,2} matches 0, 1 or 2 digits (the decimals). The * implies that this whole group can be empty.
?=(\d{3})+: \d{3} matches 3 digits exactly. + means at least one occurrence. Finally ?= matches a group after the main expression without including it in the result. In this case it takes three digits at a time (from the end remember?) and leaves them out of the result for when replacing.
g: Match and replace globally, the whole string.
Replacing with $1,: This is how captured groups are referenced for replacing, in this case the wanted group is number 1. Since the pattern will match every digit in the position 3n+1 (starting from the end or the dot) and catch it in the group number 1 ((\d)), then replacing that catch with $1, will effectively add a comma after each capture.
Try it and please feedback.
Also if you haven't already you should (and SO has not provided me with a format to stress this enough) really really look into this site as suggested by Taplar
The pattern is invalid, and your understanding of the function is incorrect. This function formats a number in a standard US currency, and here is how it works:
The parseFloat() function converts a string value to a decimal number.
The toFixed(2) function rounds the decimal number to 2 digits after the decimal point.
The replace() function is used here to add the thousands spearators (i.e. a comma after every 3 digits). The pattern is incorrect, so here is a suggested fix /(\d)(?=(\d{3})+\.)/g and this is how it works:
The (\d) captures a digit.
The (?=(\d{3})+\.) is called a look-ahead and it ensures that the captured digit above has one set of 3 digits (\d{3}) or more + followed by the decimal point \. after it followed by a decimal point.
The g flag/modifier is to apply the pattern globally, that is on the entire amount.
The replacement $1, replaces the pattern with the first captured group $1, which is in our case the digit (\d) (so technically replacing the digit with itself to make sure we don't lose the digit in the replacement) followed by a comma ,. So like I said, this is just to add the thousands separator.
Here are some tests with the suggested fix. Note that it works fine with numbers and strings:
var _formatCurrency = function(amount) {
return "$" + parseFloat(amount).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
};
console.log(_formatCurrency('1'));
console.log(_formatCurrency('100'));
console.log(_formatCurrency('1000'));
console.log(_formatCurrency('1000000.559'));
console.log(_formatCurrency('10000000000.559'));
console.log(_formatCurrency(1));
console.log(_formatCurrency(100));
console.log(_formatCurrency(1000));
console.log(_formatCurrency(1000000.559));
console.log(_formatCurrency(10000000000.559));
Okay, I want to apologize to everyone who answered. I did some further tracing and found out the JSON call which was bringing in the amount did in fact have a comma in it, so it is just parsing that first digit. I was looking in the wrong place in the code when I thought there was no comma in there already. I do appreciate everyone's input and hope you won't think too bad of me for not catching that before this whole exercise. If nothing else, at least I now know how that regex operates so I can make use of it in the future. Now I just have to go about removing that comma.
Have a great day!
Assuming that you are working with USD only, then this should work for you as an alternative to Regular Expressions. I have also included a few tests to verify that it is working properly.
var test1 = '16.9';
var test2 = '2000.5';
var test3 = '300000.23';
var test4 = '3000000.23';
function stringToUSD(inputString) {
const splitValues = inputString.split('.');
const wholeNumber = splitValues[0].split('')
.map(val => parseInt(val))
.reverse()
.map((val, idx, arr) => idx !== 0 && (idx + 1) % 3 === 0 && arr[idx + 1] !== undefined ? `,${val}` : val)
.reverse()
.join('');
return parseFloat(`${wholeNumber}.${splitValues[1]}`).toFixed(2);
}
console.log(stringToUSD(test1));
console.log(stringToUSD(test2));
console.log(stringToUSD(test3));
console.log(stringToUSD(test4));

Get id from url

I have the following example url: #/reports/12/expense/11.
I need to get the id just after the reports -> 12. What I am asking here is the most suitable way to do this. I can search for reports in the url and get the content just after that ... but what if in some moment I decide to change the url, I will have to change my algorythm.
What do You think is the best way here. Some code examples will be also very helpfull.
It's hard to write code that is future-proof since it's hard to predict the crazy things we might do in the future!
However, if we assume that the id will always be the string of consecutive digits in the URL then you could simply look for that:
function getReportId(url) {
var match = url.match(/\d+/);
return (match) ? Number(match[0]) : null;
}
getReportId('#/reports/12/expense/11'); // => 12
getReportId('/some/new/url/report/12'); // => 12
You should use a regular expression to find the number inside the string. Passing the regular expression to the string's .match() method will return an array containing the matches based on the regular expression. In this case, the item of the returned array that you're interested in will be at the index of 1, assuming that the number will always be after reports/:
var text = "#/reports/12/expense/11";
var id = text.match(/reports\/(\d+)/);
alert(id[1]);
\d+ here means that you're looking for at least one number followed by zero to an infinite amount of numbers.
var text = "#/reports/12/expense/11";
var id = text.match("#/[a-zA-Z]*/([0-9]*)/[a-zA-Z]*/")
console.log(id[1])
Regex explanation:
#/ matches the characters #/ literally
[a-zA-Z]* - matches a word
/ matches the character / literally
1st Capturing group - ([0-9]*) - this matches a number.
[a-zA-Z]* - matches a word
/ matches the character / literally
Regular expressions can be tricky (add expensive). So usually if you can efficiently do the same thing without them you should. Looking at your URL format you would probably want to put at least a few constraints on it otherwise the problem will be very complex. For instance, you probably want to assume the value will always appear directly after the key so in your sample report=12 and expense=11, but report and expense could be switched (ex. expense/11/report/12) and you would get the same result.
I would just use string split:
var parts = url.split("/");
for(var i = 0; i < parts.length; i++) {
if(parts[i] === "report"){
this.reportValue = parts[i+1];
i+=2;
}
if(parts[i] === "expense"){
this.expenseValue = parts[i+1];
i+=2;
}
}
So this way your key/value parts can appear anywhere in the array
Note: you will also want to check that i+1 is in the range of the parts array. But that would just make this sample code ugly and it is pretty easy to add in. Depending on what values you are expecting (or not expecting) you might also want to check that values are numbers using isNaN

capture with regex in javascript

I have a string like "ListUI_col_order[01234567][5]". I'd like to capture the two numeric sequences from the string. The last part between the square brackets may contain 2 digits, while the first numeric sequence always contains 8 digits (And the numbers are dynamically changing of course.) Im doing this in javascript and the code for the first part is simple: I get the only 8digit sequence from the string:
var str = $(this).attr('id');
var unique = str.match(/([0-9]){8}/g);
Getting the second part is a bit complicated to me. I cannot simply use:
var column = str.match(/[0-9]{1,2}/g)
Because this will match '01', '23', '45', '67', '5' in our example, It's clear. Although I'm able to get the information what I need as column[4], because the first part always contains 8 digits, but I'd like a nicer way to retrieve the last number.
So I define the contex and I can tell the regex that Im looking for a 1 or 2 digit number which has square brackets directly before and after it:
var column = str.match(/\[[0-9]{1,2}\]/g)
// this will return [5]. which is nearly what I want
So to get Only the numeric data I use parenthesis to capture only the numbers like:
var column = str.match(/\[([0-9]){1,2}\]/g)
// this will result in:
// column[0] = '[5]'
// column[1] = [5]
So my question is how to match the '[5]' but only capture the '5'? I have only the [0-9] between the parenthesis, but this will still capture the square brackets as well
You can get both numbers in one go :
var m = str.match(/\[(\d{8})\]\[(\d{1,2})\]$/)
For your example, this makes ["[01234567][5]", "01234567", "5"]
To get both matches as numbers, you can then do
if (m) return m.slice(1).map(Number)
which builds [1234567, 5]
Unfortunately, JavaScript does not support the lookbehind necessary to do this. In other languages such as PHP, it'd be as simple as /(?<=\[)\d{1,2}(?=\])/, but in JavaScript I am not aware of any way to do this other than use a capturing subpattern as you are here, and getting that index from the result array.
Side-note, it's usually better to put the quantifier inside the capturing group - otherwise you're repeating the group itself, not its contents!

How to check if a string contains a number in JavaScript?

I don't get how hard it is to discern a string containing a number from other strings in JavaScript.
Number('') evaluates to 0, while '' is definitely not a number for humans.
parseFloat enforces numbers, but allow them to be tailed by abitrary text.
isNaN evaluates to false for whitespace strings.
So what is the programatically function for checking if a string is a number according to a simple and sane definition what a number is?
By using below function we can test whether a javascript string contains a number or not. In above function inplace of t, we need to pass our javascript string as a parameter, then the function will return either true or false
function hasNumbers(t)
{
var regex = /\d/g;
return regex.test(t);
}
If you want something a little more complex regarding format, you could use regex, something like this:
var pattern = /^(0|[1-9][0-9]{0,2}(?:(,[0-9]{3})*|[0-9]*))(\.[0-9]+){0,1}$/;
Demo
I created this regex while answering a different question awhile back (see here). This will check that it is a number with atleast one character, cannot start with 0 unless it is 0 (or 0.[othernumbers]). Cannot have decimal unless there are digits after the decimal, may or may not have commas.. but if it does it makes sure they are 3 digits apart, etc. Could also add a -? at the beginning if you want to allow negative numbers... something like:
/^(-)?(0|[1-9][0-9]{0,2}(?:(,[0-9]{3})*|[0-9]*))(\.[0-9]+){0,1}$/;
There's this simple solution :
var ok = parseFloat(s)==s;
If you need to consider "2 " as not a number, then you might use this one :
var ok = !!(+s==s && s.length && s.trim()==s);
You can always do:
function isNumber(n)
{
if (n.trim().length === 0)
return false;
return !isNaN(n);
}
Let's try
""+(+n)===n
which enforces a very rigid canonical way of the number.
However, such number strings can be created by var n=''+some_number by JS reliable.
So this solution would reject '.01', and reject all simple numbers that JS would stringify with exponent, also reject all exponential representations that JS would display with mantissa only. But as long we stay in integer and low float number ranges, it should work with otherwise supplied numbers to.
No need to panic just use this snippet if name String Contains only numbers or text.
try below.
var pattern = /^([^0-9]*)$/;
if(!YourNiceVariable.value.match(pattern)) {//it happen while Name Contains only Charectors.}
if(YourNiceVariable.value.match(pattern)) {//it happen while Name Contains only Numbers.}
This might be insane depending on the length of your string, but you could split it into an array of individual characters and then test each character with isNaN to determine if it's a number or not.
A very short, wrong but correctable answer was just deleted. I just could comment it, besides it was very cool! So here the corrected term again:
n!=='' && +n==n'
seems good. The first term eliminates the empty string case, the second one enforces the string interpretataion of a number created by numeric interpretation of the string to match the string. As the string is not empty, any tolerated character like whitespaces are removed, so we check if they were present.

Filter out second set of digits with regex

In my HTML markup, there will be a series of elements with the following naming scheme:
name="[].timeEntries[].Time"
Between both sets of brackets, there will be numbers with at least one possibly two digits. I need to filter out the second set of digits.
Disclaimer: This is my first time getting to know regex.
This is my pattern so far:
var re = /\[\d{1,2}\].timeEntries\[(\d{1,2})\]\.Time/;
I am not sure if I should use the * or + character to indicate two possible digits.
Is replace() the right method for this?
Do I need to escape the period '.' ?
Any other tips you can offer are appreciated.
For example, if I come across an element with
name="[10].timeEntries[9].Time"
I would like to put just the 9 into a variable.
I am not sure if I should use the * or + character to indicate two possible digits.
Neither, use {1,2}
\[\d{1,2}\]\.timeEntries\[(\d{1,2})\]\.Time
Example
This indicates explicitly 1 or 2 digits.
Also, yes, you should escape the .'s
You can use it like this:
var re = /\[\d{1,2}\]\.timeEntries\[(\d{1,2})\]\.Time/;
var myNumber = "[0].timeEntries[47].Time".match(re)[1];
Now myNumber will contain 47.
One final word of warning, myNumber contains the string "47". If your intention is to use it as a number you'll need to either use parseInt or use +:
var myNumber = +"[0].timeEntries[47].Time".match(re)[1];
You're pretty close.
There are a lot of ways you could do this - especially depending on how solid the format of that text will be.
You could use replace:
var re = /\[\d+\]\.timeEntries\[([\d]+)\]\.Time/;
var digits = element_name.replace(re, '$1');
If you know it will always be the second set of digits, you could use match
You could also use indexOf and/or split and some other string functions... In some cases that can be faster (but I think in your case, the regex is fine and probably easier to follow)

Categories