Format a string in blocks of 3 digits - javascript

How would I go about reformating a string into blocks of 3 digits separated by dash (-) in JavaScript?
ex:
let myString = "00 64 33 3-44-23 982- 23-1-0"
produce desired output as:
myString = "006-433-344-239-822-310"

With regex:
("00 64 33 3-44-23 982- 23-1-0").replace(/\D/g,"").replace(/(\d{3})(?!$)/g,'$1-')
Explanation:
\D - remove all non numeric characters
(\d{3})(?!$)
(\d{3}) - match 3 numbers
(?!$) - but not the last 3
To turn 0 - 22 1985--324 into 022-198-53-24:
("0 - 22 1985--324")
.replace(/\D/g,"") // strip numbers
.replace( // do specific format
/^(\d{3})(\d{3})(\d{2})(\d{2})$/g,
'$1-$2-$3-$4'
)

From what you've said in the comments I believe you want groups of 3 where possible and groups of 2 at the end if there aren't enough numbers. I believe this will do it:
[
'1-2 3 45---67 890',
'12345678901',
'123456789012',
'1234567890123',
'12345678901234',
'1',
'12',
'123',
'1234',
'12345',
'123456',
'1234567'
].forEach(function(str) {
console.log(str.replace(/\D/g, '').replace(/(\d\d\d?)(?=\d\d)/g, '$1-'));
});
The /\D/g removes all the non-digits. The capture group (\d\d\d?) will attempt to grab 3 digits if it can (RegExps are greedy so they grab as much as possible) but if it can't grab 3 digits that ? makes the third digit optional. The lookahead (?=\d\d) requires there to be at least 2 more digits after the initial capture. The result of this is that if there are 5 or more digits remaining in the string this will result in 3 digits being included in the capture but if there are only 4 remaining it will apply the ? to grab just 2 digits.
When making multiple matches using the g flag the string is consumed from start to finish in one run. Each time it makes a match it doesn't go back to the start again, it just carries on. This means that matches can never overlap. Lookaheads provides a way to check what's coming up next without including it in the match, so that you can effectively rewind.
The key to understanding this RegExp is the observation that wherever we insert a - in the result there must always be at least 2 more digits after it. In English it translates as 'grab 3 digits if we can (or 2 if we can't) so long as there are 2 more digits yet to come'.

First you can use 'replace' to remove any non-digit characters with regex.
let myString = "00 64 33 3-44-23 982- 23-1-0"
myString = myString.replace(/\D/g, "")
// "006433344239822310"
Secondly, 'match' any digits (0-9) between 1 and 3 times. The match function conveniently returns an array with an item for each match.
myString = myString.match(/\d{1,3}/g)
// [006,433,344,239,822,310]
Lastly, join the array using "-" as the separator.
myString = myString.join("-");
// 006-433-344-239-822-310
And if you would like to chain each step together..
myString = myString.replace(/\D/g, "").match(/\d{1,3}/g).join("-");
Note that if the string has any leftover digits, they will be left in their own block on the end of the string. This is due to the 1-3 match.
For example..
"00 64 33 3-44-23 982- 23-1-0 24" // before - with 2 extra digits
"006-433-344-239-822-310-24" // after - the extra digits are maintained

Here's a non-regexp solution:
joinGroups(joinDigits(partition3(extract(string))))
joinGroups is just
// Join groups of digits with a hyphen.
const joinGroups = a => a.join('-');
joinDigits is easy enough too:
// Join elements of subarrays together into strings.
const joinDigits = groups = groups.map(group => group.join(''));
extract is easy too:
// Extract all digits from a string and return as array.
const extract = input => input.match(/\d/g);
partition3 is a little harder. There are many samples here on SO and elsewhere. See the implementation in the snippet for one idea.
const joinGroups = a => a.join('-');
const joinDigits = groups => groups.map(group => group.join(''));
const extract = input => input.match(/\d/g);
const partition = n =>
a => Array.from(
{length: Math.floor((a.length - 1) / n) + 1},
(_, i) => a.slice(i * n, (i + 1) * n));
const partition3 = partition(3);
const data = "00 64 33 3-44-23 982- 23-1-0";
console.log(joinGroups(joinDigits(partition3(extract(data)))));

Related

Use Regex for phone number

I'm working on a Regex.
let phones = ['321-1234567','+355 321 1234567','0103 1234500', '00 355 3211234567' ]
I want to have the following results:
3211234567
+3553211234567
+3551031234500
+3553211234567
I have implemented this:
phones.forEach(phone => {
phone = phone.replace(/^0+/,'+355').replace(/[^+\d]+/g, '')
console.log(phone)
})
Output:
3211234567
+3553211234567
+3551031234500
+3553553211234567 --->wrong , it should be: +3553211234567
and it works only for the three first elements of array, but it doesn't work for the last one (the case when we should replace those two zeros with + ).
So, when the phone number starts with a zero, replace that first zero with +355, when it starts with 00, replace those two zeros with + .
How can I do that using a Regex, or should I use conditions like if phone.startsWith()?
My question is not a duplication of: Format a phone number (get rid of empty spaces and replace the first digit if it's 0)
as the solution there doesn't take in consideration the case when the phone number starts with 00 355 .
let phones = ['321-1234567','+355 321 1234567','0103 1234500', '00 355 3211234567' ]
phones = phones.map(r => r
.replace(/^00/,'+')
.replace(/^0/,'+355')
.replace(/[^+\d]+/g, '')
)
console.log(phones)
You can use
let phones = ['321-1234567','+355 321 1234567','0103 1234500', '00 355 3211234567' ]
for (const phone of phones) {
console.log(
phone.replace(/^0{1,2}/, (x) => x=='00'?'+':'+355')
.replace(/(?!^\+)\D/g, ''))
}
Details:
.replace(/^0{1,2}/, (x) => x=='00'?'+':'+355') - matches 00 or 0 at the start of string, and if the match is 00, replacement is +, else, replacement is +355 (here, x stands for the whole match value and if ? then : else is a ternary operator)
.replace(/(?!^\+)\D/g, '') removes any non-digit if it is not + at the start of string.
Regex details:
^0{1,2} - ^ matches start of string and 0{1,2} matches one or two zero chars
(?!^\+)\D - (?!^\+) is a negative lookahead that fails the match if the char immediately to the right is + that is located at the start of the string (due to ^ anchor), and \D matches any char other than a digit.
your only problem is in this line replace(/^0+/,'+355') replace this with replace(/^0+/,'+')
phones.forEach(phone => {
phone = phone.replace(/^0+/,'+').replace(/[^+\d]+/g, '')
console.log(phone)
})

Javascript regex for money with max length

I want validate a money string with numbers max length 13 with 2 decimal. I have a comma as decimal separator and a period as a thousands separator.
I have this regex:
/^(\d{1}\.)?(\d+\.?)+(,\d{2})?$/
For sintax is valid but not for max length. What I need to add to this regex?
For example, these strings must be valid:
1.000.000.000.000
1.000.000.000.000,00
1
1,00
123,45
And these must be invalid:
10.000.000.000.000
10.000.000.000.000,00
10.000.000.000.000.000
10.000.000.000.000.000,00
Maybe try to assert position is not followed by 18 digits/dots using a negative lookahead:
^(?![\d.]{18})\d{1,3}(?:\.\d{3})*(?:,\d\d?)?$
See an online demo. Here is assumed you would also allow a single digit decimal.
^ - Open line anchor.
(?![\d.]{18}) - Negative lookahead to prevent 18 digits/dots ahead.
\d{1,3} - One-to-three digits.
(?:\.\d{3})* - A non-capture group of a literal dot followed by three digits with a 0+ multiplier.
(?:,\d\d?)? - Optional non-capture group of a comma followed by either 1 or two digits. Remove the question mark to make the 2nd decimal non-optional.
$ - End line anchor.
You may use this regex for validation:
^(?=[\d.]{1,17}(?:,\d{2})?$)\d{1,3}(?:\.\d{3})*(?:,\d{2})?$
RegEx Demo
RegEx Details:
^: Start
(?=[\d.]{1,17}(?:,\d{2})?$): Lookahead to match dot or digit 1 to 17 times followed by optional comma and 2 digits
\d{1,3}: Match 1 to 3 digits
(?:\.\d{3})*: Match . followed by 3 digits. Repeat this group 0 or more times
(?:,\d{2})?: Match optional , followed 2 decimal digits
$: End
90% of the time there is a better solution than using regex. It's probably best just to convert your strings into a real number then compare vs. your limit (ie 9999999999999.99).
// Pass a string
function convertStr(string) {
/*
Remove all '.'
Replace all ',' with '.'
*/
let numStr = string.split('.').join('').replaceAll(',', '.');
// Convert modified string into a real number
let realNum = parseFloat(numStr);
/*
if converted string is a real number...
round it to two decimal places and return it
Otherwise return false
*/
return !Number.isNaN(realNum) ? Math.round((realNum + Number.EPSILON) * 100) / 100 : false;
}
// Pass a string and the maxed number
function numLimit(string, limit) {
// Get the result of convertString()
let number = convertStr(string);
// if the result is equal to or less than limit...
if (number <= limit) {
return true;
} else {
return false;
}
}
const limit = 9999999999999.99;
const valid = ['1.000.000.000.000',
'1.000.000.000.000,00', '1', '1,00', '123,45'
];
const invalid = ['10.000.000.000.000', '10.000.000.000.000,00', '10.000.000.000.000.000', '10.000.000.000.000.000,00'];
let validResults = valid.map(str => numLimit(str, limit));
let invalidResults = invalid.map(str => numLimit(str, limit));
console.log('valid: ' + validResults);
console.log('invalid: ' + invalidResults);

JavaScript Regex split at first letter?

Since many cases using Regex, differs from case to case, depending on what format your string is in, I'm having a hard time finding a solution to my problem.
I have an array containing strings in the format, as an example:
"XX:XX - XX:XX Algorithm and Data Structures"
Where "XX:XX - XX:XX" is timespan for a lecture, and X being a number.
I'm new to Regex and trying to split the string at the first letter occurring, like so:
let str = "08:15 - 12:50 Algorithm and Data Structures";
let re = //Some regex expression
let result = str.split(re); // Output: ["08:15 - 12:50", "Algorithm and Data Structures"]
I'm thinking it should be something like /[a-Z]/ but I'm not sure at all...
Thanks in advance!
The easiest way is probably to "mark" where you want to split and then split:
const str = '12 34 abcde 45 abcde'.replace(/^([^a-z]+)([a-z])/i, '$1,$2');
// '12 34 ,abcde 45 abcde'
str.split(',')
// [ '12 34 ', 'abcde 45 abcde' ]
This finds the place where the string starts, has a bunch of non a-z characters, then has an a-z characters, and puts a comma right in-between. Then you split by the comma.
You can also split directly with a positive look ahead but it might make the regex a bit less readable.
console.log(
"08:15 - 12:50 Algorithm and Data Structures".split(/ ([A-Za-z].*)/).filter(Boolean)
)
or, if it's really always XX:XX - XX:XX, easier to just do:
const splitTimeAndCourse = (input) => {
return [
input.slice(0, "XX:XX - XX:XX".length),
input.slice("XX:XX - XX:XX".length + 1)
]
}
console.log(splitTimeAndCourse("08:15 - 12:50 Algorithm and Data Structures"))
If you have a fixed length of the string where the time is, you can use this regex for example
(^.{0,13})(.*)
Check this here https://regex101.com/r/ANMHy5/1
I know you asked about regex in particular, but here is a way to this without regex...
Provided your time span is always at the beginning of your string and will always be formatted with white space between the numbers as XX:XX - XX:XX. You could use a function that splits the string at the white space and reconstructs the first three indexed strings into one chunk, the time span, and the last remaining strings into a second chunk, the lecture title. Then return the two chunks as an array.
let str = "08:15 - 12:50 Algorithm and Data Structures";
const splitString = (str) => {
// split the string at the white spaces
const strings = str.split(' ')
// define variables
let lecture = '',
timespan = '';
// loop over the strings
strings.forEach((str, i) => {
// structure the timespan
timespan = `${strings[0]} ${strings[1]} ${strings[2]}`;
// conditional to get the remaining strings and concatenate them into a new string
i > 2 && i < strings.length?lecture += `${str} `: '';
})
// place them into an array and remove white space from end of second string
return [timespan, lecture.trimEnd()]
}
console.log(splitString(str))
For that format, you might also use 2 capture groups instead of using split.
^(\d{1,2}:\d{1,2}\s*-\s*\d{1,2}:\d{1,2})\s+([A-Za-z].*)
The pattern matches:
^ Start of string
(\d{1,2}:\d{1,2}\s*-\s*\d{1,2}:\d{1,2}) Capture group 1, match a timespan like pattern
\s+ Match 1+ whitspac chars
([A-Za-z].*) Capture group 2, start with a char A-Za-z and match the rest of the line.
Regex demo
let str = "08:15 - 12:50 Algorithm and Data Structures";
let regex = /^(\d{1,2}:\d{1,2}\s*-\s*\d{1,2}:\d{1,2})\s+([A-Za-z].*)/;
let [, ...groups] = str.match(regex);
console.log(groups);
Another option using split might be asserting not any chars a-zA-Z to the left from the start of the string using a lookbehind (see this link for the support), match 1+ whitespace chars and asserting a char a-zA-Z to the right.
(?<=^[^a-zA-Z]+)\s+(?=[A-Za-z])
Regex demo
let str = "08:15 - 12:50 Algorithm and Data Structures";
let regex = /(?<=^[^a-zA-Z]+)\s+(?=[A-Za-z])/;
console.log(str.split(regex))

Javascript replace regex to accept only numbers, including negative ones, two decimals, replace 0s in the beginning, except if number is 0

The question became a bit long, but it explains the expected behaviour.
let regex = undefined;
const format = (string) => string.replace(regex, '');
format('0')
//0
format('00')
//0
format('02')
//2
format('-03')
//-3
format('023.2323')
//23.23
format('00023.2.3.2.3')
//23.23
In the above example you can see the expected results in comments.
To summarize. I'm looking for a regex not for test, for replace which formats a string:
removes 0s from the beginning if it's followed by any numbers
allows decimal digits, but just 2
allows negative numbers
allows decimal points, but just one (followed by min 1, max 2 decimal digits)
The last one is a bit difficult to handle as the user can't enter period at the same time, I'll have two formatter functions, one will be the input in the input field, and one for the closest valid value at the moment (for example '2.' will show '2.' in the input field, but the handler will receive the value '2').
If not big favour, I'd like to see explanation of the solution, why it works, and what's the purpose of which part.
Right now I'm having string.replace(/[^\d]+(\.\[^\d{1,2}])+|^0+(?!$)/g, ''), but it doesn't fulfill all the requirements.
You may use this code:
const arr = ['0', '00', '02', '-03', '023.2323', '00023.2.3.2.3', '-23.2.3.2.3']
var narr = []
// to remove leading zeroes
const re1 = /^([+-]?)0+?(?=\d)/
// to remove multiple decimals
const re2 = /^([+-]?\d*\.\d+)\.(\d+).*/
arr.forEach( el => {
el = el.replace(re1, '$1').replace(re2, '$1$2')
if (el.indexOf('.') >= 0)
el = Number(el).toFixed(2)
narr.push(el)
})
console.log(narr)
//=> ["0", "0", "2", "-3", "23.23", "23.23"]
If you aren't bound to the String#replace method, you can try this regex:
/^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2}))?$/
Inspect on regex101.com
It collects the parts of the number into capturing groups, as follows:
Sign: the sign of the number, +, - or undefined
Integer: the integer part of the number, without leading zeros
Decimal: the decimal part of the number, undefined if absent
This regex won't match if more then 2 decimal places present. To strip it instead, use this:
/^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2})\d*)?$/
Inspect on regex101.com
To format a number using one of the above, you can use something like:
let regex = /^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2}))?$/
const format = string => {
try{
const [, sign, integer, decimal = ''] = string.match(regex)
return `${(sign !== '-' ? '' : '-')}${integer}${(decimal && `.${decimal}`)}`
}catch(e){
//Invalid format, do something
return
}
}
console.log(format('0'))
//0
console.log(format('00'))
//0
console.log(format('02'))
//2
console.log(format('-03'))
//-3
console.log(format('023.23'))
//23.23
console.log(format('023.2323'))
//undefined (invalid format)
console.log(format('00023.2.3.2.3'))
//undefined (invalid format)
//Using the 2nd regex
regex = /^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2})\d*)?$/
console.log(format('0'))
//0
console.log(format('00'))
//0
console.log(format('02'))
//2
console.log(format('-03'))
//-3
console.log(format('023.23'))
//23.23
console.log(format('023.2323'))
//23.23
console.log(format('00023.2.3.2.3'))
//undefined (invalid format)
Another option is to use pattern with 3 capturing groups. In the replacement, use all 3 groups "$1$2$3"
If the string after the replacement is empty, return a single zero.
If the string is not empty, concat group 1, group 2 and group 3 where for group 3, remove all the dots except for the first one to keep it for the decimal and take the first 3 characters (which is the dot and 2 digits)
^([-+]?)0*([1-9]\d*)((?:\.\d+)*)|0+$
In parts
^ Start of string
( Capture group 1
[-+]? Match an optional + or -
) Close group
0* Match 0+ times a zero
( Capture group 2
[1-9]\d* Match a digit 1-9 followed by optional digits 0-9
) Close group
( Capture group 3
(?:\.\d+)* Repeat 0+ times matching a dot and a digit
) Close group
| Or
0+ Match 1+ times a zero
$ End of string
Regex demo
const strings = ['0', '00', '02', '-03', '023.2323', '00023.2.3.2.3', '-23.2.3.2.3', '00001234', '+0000100005.0001']
let pattern = /^([-+]?)0*([1-9]\d*)((?:\.\d+)*)|0+$/;
let format = s => {
s = s.replace(pattern, "$1$2$3");
return s === "" ? '0' : s.replace(pattern, (_, g1, g2, g3) =>
g1 + g2 + g3.replace(/(?!^)\./g, '').substring(0, 3)
);
};
strings.forEach(s => console.log(format(s)));

Regex for getting only the last N numbers in javascript

I've being trying to generate a regex for this string:
case1: test-123456789 should get 56789
case2: test-1234-123456789 should get 56789
case3: test-12345 should fail or not giving anything
what I need is a way to get only the last 5 numbers from only 9 numbers
so far I did this:
case.match(/\d{5}$/)
it works for the first 2 cases but not for the last one
You may use
/\b\d{4}(\d{5})$/
See the regex demo. Get Group 1 value.
Details
\b - word boundary (to make sure the digit chunks are 9 digit long) - if your digit chunks at the end of the string can contain more, remove \b
\d{4} - four digits
(\d{5}) - Group 1: five digits
$ - end of string.
JS demo:
var strs = ['test-123456789','test-1234-123456789','test-12345'];
var rx = /\b\d{4}(\d{5})$/;
for (var s of strs) {
var m = s.match(rx);
if (m) {
console.log(s, "=>", m[1]);
} else {
console.log("Fail for ", s);
}
}
You can try this:
var test="test-123456789";
console.log((test.match(/[^\d]\d{4}(\d{5})$/)||{1: null/*default value if not found*/})[1]);
This way supports default value for when not found any matching (look at inserted comment inline above code.).
You can use a positive lookbehind (?<= ) to assert that your group of 5 digits is preceeded by a group of 4 digits without including them in the result.
/(?<=\d{4})\d{5}$/
var inputs = [
"test-123456789", // 56789
"test-1234-123456789", // 56789
"test-12345", //fail or not giving anything
]
var rgx = /(?<=\d{4})\d{5}$/
inputs.forEach(str => {
console.log(rgx.exec(str))
})

Categories

Resources