Javascript regex that matches one pattern in string but exclude another - javascript

I have two possible strings that I need to match:
+/-90000
and
+9000 / -80000
I need to recognise the two patterns separately so wrote some regex for this. The first single number string I can match like so:
/\+\/\-{1}/g
And i wrote this for the second:
/(\+(?=[0-9]+){1}|\-(?=[0-9]+){1}|\/(?=\s){1})/g
The second would also partially match the first the first number i.e. the -90000. Is there a way that they can be improved so that they match exclusively?

You can use a single expression:
^(?:(\+\/-\s*\d+)|((\+\s*\d+)\s*\/\s*(-\s*\d+)))$
The only restriction you'll have to work with would be that in the second type of input, the positive number should come first.
You'll get the matched group in matches[1] if the input was of type 1, and in matches[2] if it was of type 2. For the type-2 input, further matches of each number gets stored in matches[3] and matches[4].
You can see the demo on regex101.

Here are two solutions with slightly different semantics.
With the first, if the string is type 1 the number will be in capture group 1 (result[1]) and if it's type 2 the numbers will be in capture groups 2 and 3 (and capture group 1 will be null). The test for type 1, then, is result[1] !== null.
var a = '+/-90000';
var b = '+9000 / -80000';
var result;
var expr1 = /\+(?:\/-(\d+)|(\d+) \/ -(\d+))/;
result = a.match(expr1);
// => [ '+/-90000', '90000', null, null ]
result = b.match(expr1);
// => [ '+9000 / -80000', null, '9000', '80000' ]
With the second, if the string is type 1 the number will be in capture group 1 (and capture group 2 will be null), and if it's type 2 the numbers will be in capture groups 2 and 3. The test for type 1 is result[1] === null.
var expr2 = /\+(\d+ )?\/ ?-(\d+)/;
result = a.match(expr2);
// => [ '+/-90000', null, '90000' ]
result = b.match(expr2);
// => [ '+9000 / -80000', '9000', '80000' ]

Related

How to match particular strings in an array using javascript?

i have an array of strings like below,
const arr = [
"list-domain/1/",
"list-domain/1/list/1",
"some-group/2/",
"some-group/2/list/2",
"list/3/",
"list/3/item/1"
];
as seen from above array i want to check if the array contains strings of kind
"list-domain/1/" or "some-group/2/" or "list/3/"
here the numbers after / can be anything. meaning in "list-domain/1" here 1 can be 2 or 3 anynumber i have to check if the string contains list-domain or some-group or list followed by / and any number followed by /
so for the above array the expected output is true.
now consider array below,
const arr1 = [
"list-domain/1/list/1",
"some-group/2/list/2",
"list/3/item/1"
];
for arr1 the expected output is false althought there is strings list-domain/1, some-group/2 and list/3 but they are followed by someother strings too like list-domain/1/list/1, some-group/2/list/2, list/3/item/1
so i want to find the exact strings numbers can be anything after /. could someone help me how to do this. thanks.
EDIT:
i want it to return true if the string has "list/1/" or
any string followed by "list/1" but return false if "list/1" is followed by someother string like below
"list-domain/1/list/1/item/1"
true in cases below
"list-domain/1/list/1"
"list/2"
"some-group/3/list/3"
This gives you the required output using Regular Expression (RegExp):
arr.some(a=> new RegExp(/^(list-domain|some-group|list)\/[0-9]+\/$/g).test(a))
If you can count on the shape of pattern being always a string identifier followed by a numerical id a parser might look as follows:
const arr = [
"list-domain/1/",
"list-domain/1/list/1",
"some-group/2/",
"some-group/2/list/2",
"list/3/",
"list/3/item/1"
];
const parse = (str) => {
const parts = str.split('/');
return parts.reduce((parsed, part, i, parts) => {
if (part === '') return parsed;
if (i % 2 === 0) {
parsed[part] = null;
} else {
parsed[parts[i-1]] = part;
}
return parsed;
}, {});
}
arr.forEach(s => {
console.log(parse(s));
})
// returns:
// {list-domain: "1"}
// {list-domain: "1", list: "1"}
// {some-group: "2"}
// {some-group: "2", list: "2"}
// {list: "3"}
// {list: "3", item: "1"}
It returns an object where keys are the string identifiers and values numerical ids. However, as I said, it relies on the exact shape of the string and regular alteration of an identifier and a value.
Use a regular expression. Here is an example.
const rx = /^(([^\/]+)\/(\d+)\/)+$/
// or without the groups
const rxNoGroups = /^([^\/]+\/\d+\/)+$/
const found = items.filter(it => rx.test(it))
This regex tests
start of input
not-a-slash
slash
number
slash
end of input or next group
Tailor it to your needs. I coded it so your non-number words can still contain numbers, so foo/2/bar3/4/. You can also limit number of repetitions.
You can also just test that the string ends with /.

Regex pattern validation for variable number of number pairs

I am trying to verify that a string is in the correct format...
Number pairs, seperated by a hyphen, each number with a max of 3 digits
No negative numbers
Pairs are seperated by a |
Pattern:
XXX-XXX
XXX-XXX|XXX-XXX|...
Example valid input:
var string1 = "18-200"; // Single entry
var string2 = "18-200|36-90"; // Multiple entries
Example invalid input:
var string3 = "18-2000"; // Failed because the second number has 4 digits
var string4 = "1-1-1-1"; // Failed because there are 4 alternatives, not 2
var string5 = "-20-100"; // Failed because it starts with a negative
I've come up with the following pattern:
^[0-9]+(-[0-9]+)+$
But the expression doesn't match all of the criteria, for example 1-1-1-1 is still okay.
Assuming that:
You always match from the start of the supplied string to the end
You always match in pairs like XXX-XXX
Multiple pairs are separated by |
Then we could write a simplified regex...
^\d-\d(\|\d-\d)*$
Which we can then expand to arrive at...
^\d{1,3}-\d{1,3}(?:\|\d{1,3}-\d{1,3})*$
^ : Start of string
\d{1,3} : Match a number 1 to 3 times
- : Match literally
\d{1,3} : Match a number 1 to 3 times
(?: : Start of non-capturing group
\| : Match literally
\d{1,3} : Match a number 1 to 3 times
- : Match literally
\d{1,3} : Match a number 1 to 3 times
) : End of group
* : Quantifier for 0 or more occurrences of the non-capturing group
$ : End of string
Which we can then test in JS using match to verify...
var regex = /^\d{1,3}-\d{1,3}(?:\|\d{1,3}-\d{1,3})*$/;
var testStrings = [
`1-12`,
`1-12|88-100`,
`1-12|88-100|1-111`,
`1-1-1-1`,
`1-12|`,
`1-12|1-1-2`,
];
for (let num in testStrings) {
console.log(testStrings[num].match(regex));
}
Output, as expected:
["1-12", index: 0, input: "1-12", groups: undefined]
["1-12|88-100", index: 0, input: "1-12|88-100", groups: undefined]
["1-12|88-100|1-111", index: 0, input: "1-12|88-100|1-111", groups: undefined]
null
null
null
One possible approach (for each number up to 3 digits):
^[0-9]{1,3}-[0-9]{1,3}(?:\|[0-9]{1,3}-[0-9]{1,3})*$
Regex101 demo. Essentially, you just repeat the first part of the pattern, preceding it with escaped |, then (with *) make sure the whole group is repeated - or not.
Escaping is important, as | is a metacharacter. And when you use this string in Angular Validator (as it somehow turned out), you should escape the escaping character (\) in your string literal, so it becomes...
requiredPattern: "^[0-9]{1,3}-[0-9]{1,3}(?:\\|[0-9]{1,3}-[0-9]{1,3})*$"

How to extract only numbers from string except substring with special character in beginning of numbers

my plan is extract numbers from string except numbers with special character. What I mean?
Please imagine a following (like Excel formula):
=$A12+A$345+A6789
I need to extract numbers where in beginning of them doesn't exist any character $, so result of right regex should be:
12
6789
I made some investigation where I used a following regex:
/[A-Z][0-9]+(?![/W])/g
which extracts:
A12
A6789
I was thinking to use nested regex (to extract numbers from that result additionally) but I have no idea if it possible. My source code in javascript so far:
http://jsfiddle.net/janzitniak/fvczu7a0/7/
Regards
Jan
const regex = /(?<ref>\$?[A-Z]+(?<!\$)[0-9]+)/g;
const str = `=$A12+A$345+A6789`;
const refs = [...(str.matchAll(regex) || [])].map(result => result.groups.ref);
console.log(refs)
Matches any string containing A-Z once or more that is preceded by a $ zero or one times, followed by 0-9 once or more but not preceded by a $, all followed by + zero or one times.
You ignore all matched groups, but capture the one you want, referenced as ref (you can call it whatever you want).
Output:
["$A12","A6789"]
If you want just the number part, you can use:
const regex = /\$?[A-Z]+(?<!\$)(?<num>[0-9]+)/g;
const str = `=$A12+A$345+A6789`;
const nums = [...(str.matchAll(regex) || [])].map(result => +result.groups.num);
console.log(nums)
Output:
[12, 6789]
const charSequence = '=$A12+A$345+A6789';
const numberList = (charSequence
.split(/\$\d+/) // - split at "'$' followed by one or more numbers".
.join('') // - join array of split results into string again.
.match(/\d+/g) || []) // - match any number-sequence or fall back to empty array.
.map(str => +str); // - typecast string into number.
//.map(str => parseInt(str, 10)); // parse string into integer.
console.log('numberList : ', numberList);
.as-console-wrapper { min-height: 100%!important; top: 0; }
#ibraheem can you help me once again please? How can I increment ref output if I want to have the following result ["$A13","A6790"]? - JanZitniak 23 mins ago
... the split/join/match approach can be iterated very fast, thus it proves to be quite flexible.
const charSequence = '=$A13+A$345+A6790';
const numberList = (charSequence
.split(/\$\d+/) // - split at "'$' followed by one or more numbers".
.join('') // - join array of split results into string again.
.match(/\$*[A-Z]\d+/g) || []); // - match any sequence of an optional '$' followed
// by 1 basic latin uppercase character followed
// by one or more number character(s).
console.log('numberList : ', numberList);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter thank you for your quick response about increment but on start I have const charSequence = '=$A12+A$345+A6789'; and as output I need ["$A13","A6790"]. – JanZitniak
... ok, finally one is going to get a full picture of the entire problem ... which is (1) getting rid of the not necessary patterns ...(2) matching numbers within specific patterns AND somehow remember the latter (3) increment such numbers AND somehow rework them into their remembered/recallable pattern.
const anchorSequence = '=$A12+A$345+A6789';
const listOfIncrementedAnchorCoordinates = [...(anchorSequence
// - split at "'$' followed by one or more numbers".
.split(/\$\d+/)
// - join array of split results into string again.
.join('')
// - match any sequence of an optional '$' followed by 1 basic latin
// uppercase character followed by one or more number character(s)
// and store each capture into a named group.
.matchAll(/(?<anchor>\$*[A-Z])(?<integer>\d+)/g) || [])
// map each regexp result from a list of RegExpStringIterator entries.
].map(({ groups }) => `${ groups.anchor }${ (+groups.integer + 1) }`);
console.log('listOfIncrementedAnchorCoordinates : ', listOfIncrementedAnchorCoordinates);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter if you are interest(...ed) in another problem I have one. How can I change const anchorSequence = '=$A12+A$345+A6789'; to following output ["B$345","B6789"]? I mean to change letter to next one in alphabetical order (if it is A then change to B, if it is B change to C and so on) if letter doesn't start with $. In my example it should change only A$345 and A6789. – JanZitniak
... with a little thinking effort it was not that hard to iterate/refactor the version before to this last one ...
const anchorSequence = '=$A12+A$345+A6789';
const listOfIncrementedColumns = [...(anchorSequence
// - split at "'$' followed by 1 basic latin uppercase character
// followed by one or more number character(s)".
.split(/\$[A-Z]\d+/)
// - join array of split results into string again.
.join('')
// - match any sequence of 1 basic latin uppercase character
// followed by an optional '$' followed by one or more number
// character(s) and store each capture into a named group.
.matchAll(/(?<column>[A-Z])(?<anchor>\$*)(?<row>\d+)/g) || [])
// map each regexp result from a list of RegExpStringIterator entries.
].map(({ groups }) => [
// - be aware that "Z" (charCode:90) will be followed by "[" (charCode:91)
// - thus, the handling of this edge case still needs to be implemented.
String.fromCharCode(groups.column.charCodeAt(0) + 1),
groups.anchor,
groups.row
].join(''));
console.log('listOfIncrementedColumns : ', listOfIncrementedColumns);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Regex to match all combinations of a given string

I am trying to make a regex to matches all the combinations of a given string. For example of the string is "1234", answers would include:
"1"
"123"
"4321"
"4312"
Nonexamples would include:
"11"
"11234"
"44132"
If it matters, the programming language I am using is javascript.
Thank you for any help.
You may use this lookahead based assertions in your regex:
^(?!(?:[^1]*1){2})(?!(?:[^2]*2){2})(?!(?:[^3]*3){2})(?!(?:[^4]*4){2})[1234]+$
RegEx Demo
Here we have 4 lookahead assertions:
(?!(?:[^1]*1){2}): Assert that we don't have more than one instance of 1
(?!(?:[^2]*2){2}): Assert that we don't have more than one instance of 2
(?!(?:[^3]*3){2}): Assert that we don't have more than one instance of 3
(?!(?:[^4]*4){2}): Assert that we don't have more than one instance of 4
We use [1234]+ to match any string with these 4 characters.
A combination of group captures using character classes and negative look-ahead assertions using back-references would do the trick.
Let's begin with simply matching any combination of 1, 2, 3, and 4 using a character class,[1-4], and allowing any length from 1 to 4 characters. {1,4}.
const regex = /^[1-4]{1,4}$/;
// Create set of inputs from 0 to 4322
const inputs = Array.from(new Array(4323), (v, i) => i.toString());
// Output only values that match criteria
console.log(inputs.filter((input) => regex.test(input)));
When that code is run, it's easy to see that although only numbers consisting of some combination of 1, 2, 3, and 4 are matched, it also is matching numbers with repeating combinations (e.g. 11, 22, 33, 112, etc). Obviously, this was not what was desired.
To prevent repeating characters requires a reference to previously matched characters and then a negation of them from any following matched characters. Negative look-aheads, (?!...) using a back-reference, \1-9, can accomplish this.
Building on the previous example with a subset of the inputs (limiting to a max length of two characters for the moment) would now incorporate a group match surrounding the first character, ([1-4]), followed by a negative look-ahead with a back-reference to the first capture, (?!\1), and finally a second optional character class.
const regex = /^([1-4])(?!\1)[1-4]?$/;
// Create set of inputs from 0 to 44
const inputs = Array.from(new Array(45), (v, i) => i.toString());
// Output only values that match criteria
console.log(inputs.filter((input) => regex.test(input)));
This matches the desired characters with no repetition!
Expanding this pattern to include back-references for each of the previously matched characters up to the desired max length of 4 yields the following expression.
const regex = /^([1-4])((?!\1)[1-4])?((?!\1|\2)[1-4])?((?!\1|\2|\3)[1-4])?$/;
// Create set of inputs from 0 to 4322
const inputs = Array.from(new Array(4323), (v, i) => i.toString());
// Output only values that match criteria
console.log(inputs.filter((input) => regex.test(input)));
Hope this helps!
You don't need to use regex for this. The snippet below does the following:
Loop over possible combinations (a => s) (1, 123, 4321, etc.)
Copy the current combination so as not to overwrite it (s2 = s)
Loop over the characters of test string (x => ch) (1234 => 1, 2, 3, 4)
Replace common characters in the combination string shared with the test string (s2.replace)
For example in the combination 1, the 1 will be replaced when the loop gets to the character 1 in 1234 resulting in an empty string
If the combination string's length reaches 0 (s2.length == 0) write the result to the console and break out of the loop (no point in continuing to attempt to replace on an empty string)
const x = "1234"
const a = ["1","123","4321","4312","11","11234","44132"]
a.forEach(function(s) {
var s2 = s
for(var ch of x) {
s2 = s2.replace(ch, '')
if(s2.length == 0) {
console.log(s);
break;
}
}
})
Results:
1
123
4321
4312

RegExp capturing group in capturing group

I want to capture the "1" and "2" in "http://test.com/1/2". Here is my regexp /(?:\/([0-9]+))/g.
The problem is that I only get ["/1", "/2"]. According to http://regex101.com/r/uC2bW5 I have to get "1" and "1".
I'm running my RegExp in JS.
You have a couple of options:
Use a while loop over RegExp.prototype.exec:
var regex = /(?:\/([0-9]+))/g,
string = "http://test.com/1/2",
matches = [];
while (match = regex.exec(string)) {
matches.push(match[1]);
}
Use replace as suggested by elclanrs:
var regex = /(?:\/([0-9]+))/g,
string = "http://test.com/1/2",
matches = [];
string.replace(regex, function() {
matches.push(arguments[1]);
});
In Javascript your "match" has always an element with index 0, that contains the WHOLE pattern match. So in your case, this index 0 is /1 and /2 for the second match.
If you want to get your DEFINED first Matchgroup (the one that does not include the /), you'll find it inside the Match-Array Entry with index 1.
This index 0 cannot be removed and has nothing to do with the outer matching group you defined as non-matching by using ?:
Imagine Javascript wrapps your whole regex into an additional set of brackets.
I.e. the String Hello World and the Regex /Hell(o) World/ will result in :
[0 => Hello World, 1 => o]

Categories