Java regex for matching multiple keys in a string - java

Consider an input string like
Number ONE=1 appears before TWO=2 and THREE=3 comes before FOUR=4 and FIVE=5
and the regular expression
\b(TWO|FOUR)=([^ ]*)\b
Using this regular expression, the following code can extract the 2 specific key-value pairs out of the 5 total ones (i.e., only some predefined key-value pairs should be extracted).
public static void main(String[] args) throws Exception {
String input = "Number ONE=1 appears before TWO=2 and THREE=3 comes before FOUR=4 and FIVE=5";
String regex = "\\b(TWO|FOUR)=([^ ]*)\\b";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
System.out.println("\t" + matcher.group(1) + " = " + matcher.group(2));
}
}
More specifically, the main() method above prints
TWO = 2
FOUR = 4
but every time find() is invoked, the whole regular expression is evaluated for the part of the string remaining after the latest match, left to right.
Also, if the keys are not mutually distinct (or, if a regular expression with overlapping matches was used in the place of each key), there will be multiple matches. For instance, if the regex becomes
\b(O.*?|T.*?)=([^ ]*)\b
the above method yields
ONE = 1
TWO = 2
THREE = 3
If the regex was not fully re-evaluated but each alternative part was somehow examined once (or, if an appropriately modified regex was used), the output would have been
ONE = 1
TWO = 2
So, two questions:
Is there a more efficient way of extracting a selected set of unique keys and their values, compared to the original regular expression?
Is there a regular expression that can match every alternative part of the OR (|) sub-expression exactly once and not evaluate it again?

Java Returns a Match Position: You can Use Dynamically-Generated Regex on Remaining Substrings
With the understanding that it can be generalized to a more complex and useful scenario, let's take a variation on your first example: \b(TWO|FOUR|SEVEN)=([^ ]*)\b
You can use it like this:
Pattern regex = Pattern.compile("\\b(TWO|FOUR|SEVEN)=([^ ]*)\\b");
Matcher regexMatcher = regex.matcher(yourString);
if (regexMatcher.find()) {
String theMatch = regexMatcher.group();
String FoundToken = = regexMatcher.group(1);
String EndPosition = regexMatcher.end();
}
You could then:
Test the value contained by FoundToken
Depending on that value, dynamically generate a regex testing for the remaining possible tokens. For instance, if you found FOUR, your new regex would be \\b(TWO|SEVEN)=([^ ]*)\\b
Using EndPosition, apply that regex to the end of the string.
Discussion
This approach would serve your goal of not re-evaluating parts of the OR that have already matched.
It also serves your goal of avoiding duplicates.
Would that be faster? Not in this simple case. But you said you are dealing with a real problem, and it will be a valid approach in some cases.

Related

A regular expression captures more text than it needs

I want to get the whole value 97.47 but the regular expression splits it by 9 and by 7.47 adding it to different fields
This is the regular expression that is used
private static final Pattern COMMISSION_PATTERN =
Pattern.compile(
"(total\\[((?:(?<totalFixed>\\d+)(\\s*(\\+)\\s*)?)?" +
"((?<totalPercent>\\d+(\\.\\d{1,2})?)\\s*%)?" +
"(\\s*min\\s*(?<totalMin>\\d+))?" +
"(\\s*max\\s*(?<totalMax>\\d+))?" +
"(\\s*round\\s*(?<totalRound>\\d+))?)?\\])?(\\s*)" +
"(partner\\[(?:(\\s*negative:\\s*(?<partnerNegative>(true|false))?\\s*,\\s*)?" +
"((?<partnerFixed>\\d+)(\\s*(\\+)\\s*)?)?" +
"((?<partnerPercent>\\d+(\\.\\d{1,2})?)\\s*%)?" +
"(\\s*min\\s*(?<partnerMin>\\d+))?" +
"(\\s*max\\s*(?<partnerMax>\\d+))?" +
"(\\s*round\\s*(?<partnerRound>\\d+))?" +
"(\\s*mode\\s*(?<partnerMode>\\w+))?)?\\])?");
The following value arrives in the method
"total[0] partner[97.47%]"
it is parsed in this way:
String sCommission = "total[0] partner[97.47%]";
for (String comm : sCommission.split("\n")) {
Matcher matcher = COMMISSION_PATTERN.matcher(comm.trim());
if (matcher.matches()) {
String sPartnerFixed = matcher.group("partnerFixed");//9
String sPartnerPercent = matcher.group("partnerPercent"); //7.47
And it should be:
String sPartnerFixed = matcher.group("partnerFixed"); //null
String sPartnerPercent = matcher.group("partnerPercent"); //97.47
I can't figure out where the error is in the regular expression
The (\s*(\+)\s*)? part in the ((?<partnerFixed>\d+)(\s*(\+)\s*)?)? part is optional, and \d+ in the partnerFixed group becomes "adjacent" (it can be backtracked into) to the (?<partnerPercent>\d+(?:\.\d{1,2})?) part of the regex (where \d+ also is required and matches one or more digits). So, this behavior you have is expected, unless you tell the regex engine to clearly have an obligatory pattern between these two number matching parts.
A possible solution would be a word boundary after \d+ in the (?<partnerFixed>\d+) part, i.e. replace "((?<partnerFixed>\\d+)(\\s*(\\+)\\s*)?)?" with "((?<partnerFixed>\\d+\\b)(\\s*(\\+)\\s*)?)?".
A more sophisticated and more precise way to solve this issue is to make some part of the (\s*(\+)\s*)? pattern obligatory. That is, you do not expect a match for partnerFixed if there is a single streak of digits optionally followed with . and one or two digits. If there is a partnerFixed number, what should it be separated with from the next value? I think there should be a whitespace or + enclosed with optional whitespaces, just deducing it from the pattern.
In this latter case, you can replace "((?<partnerFixed>\\d+)(\\s*(\\+)\\s*)?)?" with "((?<partnerFixed>\\d+)(\\s+|\\s*\\+\\s*))?".
See this regex demo.

How to add a space after certain characters using regex Java

I have a string consisting of 18 digits Eg. 'abcdefghijklmnopqr'. I need to add a blank space after 5th character and then after 9th character and after 15th character making it look like 'abcde fghi jklmno pqr'. Can I achieve this using regular expression?
As regular expressions are not my cup of tea hence need help from regex gurus out here. Any help is appreciated.
Thanks in advance
Regex finds a match in a string and can't preform a replacement. You could however use regex to find a certain matching substring and replace that, but you would still need a separate method for replacement (making it a two step algorithm).
Since you're not looking for a pattern in your string, but rather just the n-th char, regex wouldn't be of much use, it would make it unnecessary complex.
Here are some ideas on how you could implement a solution:
Use an array of characters to avoid creating redundant strings: create a character array and copy characters from the string before
the given position, put the character at the position, copy the rest
of the characters from the String,... continue until you reach the end
of the string. After that construct the final string from that
array.
Use Substring() method: concatenate substring of the string before
the position, new character, substring of the string after the
position and before the next position,... and so on, until reaching the end of the original string.
Use a StringBuilder and its insert() method.
Note that:
First idea listed might not be a suitable solution for very large strings. It needs an auxiliary array, using additional space.
Second idea creates redundant strings. Strings are immutable and final in Java, and are stored in a pool. Creating
temporary strings should be avoided.
Yes you can use regex groups to achieve that. Something like that:
final Pattern pattern = Pattern.compile("([a-z]{5})([a-z]{4})([a-z]{6})([a-z]{3})");
final Matcher matcher = pattern.matcher("abcdefghijklmnopqr");
if (matcher.matches()) {
String first = matcher.group(0);
String second = matcher.group(1);
String third = matcher.group(2);
String fourth = matcher.group(3);
return first + " " + second + " " + third + " " + fourth;
} else {
throw new SomeException();
}
Note that pattern should be a constant, I used a local variable here to make it easier to read.
Compared to substrings, which would also work to achieve the desired result, regex also allow you to validate the format of your input data. In the provided example you check that it's a 18 characters long string composed of only lowercase letters.
If you had a more interesting examples, with for example a mix of letters and digits, you could check that each group contains the correct type of data with the regex.
You can also do a simpler version where you just replace with:
"abcdefghijklmnopqr".replaceAll("([a-z]{5})([a-z]{4})([a-z]{6})([a-z]{3})", "$1 $2 $3 $4")
But you don't have the benefit of checking because if the string doesn't match the format it will just not replaced and this is less efficient than substrings.
Here is an example solution using substrings which would be more efficient if you don't care about checking:
final Set<Integer> breaks = Set.of(5, 9, 15);
final String str = "abcdefghijklmnopqr";
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
if (breaks.contains(i)) {
stringBuilder.append(' ');
}
stringBuilder.append(str.charAt(i));
}
return stringBuilder.toString();

How to make a regular expression match based on a condition?

I'm trying to make a conditional regex, I know that there are other posts on stack overflow but there too specific to the problem.
The Question
How can I create a regular expression that only looks to match something given a certain condition?
An example
An example of this would be if we had a list of a string(this is in java):
String nums = "42 36 23827";
and we only want to match if there are the same amount of x's at the end of the string as there are at the beginning
What we want in this example
In this example, we would want a regex that checks if there are the same amount of regex's at the end as there are in the beginning. The conditional part: If there are x's at the beginning, then check if there are that many at the end, if there are then it is a match.
Another example
An example of this would be if we had a list of numbers (this is in java) in string format:
String nums = "42 36 23827";
and we want to separate each number into a list
String splitSpace = "Regex goes here";
Pattern splitSpaceRegex = Pattern.compile(splitSpace);
Matcher splitSpaceMatcher = splitSpaceRegex.matcher(text);
ArrayList<String> splitEquation = new ArrayList<String>();
while (splitSpaceMatcher.find()) {
if (splitSpaceMatcher.group().length() != 0) {
System.out.println(splitSpaceMatcher.group().trim());
splitEquation.add(splitSpaceMatcher.group().trim());
}
}
How can I make this into an array that looks like this:
["42", "36", "23827"]
You could try making a simple regex like this:
String splitSpace = "\\d+\\s+";
But that exludes the "23827" because there is no space after it.
and we only want to match if there are the same amount ofx`'s at the end of the string as there are at the beginning
What we want in this example
In this example, we would want a regex that checks if it is the end of the string; if it is then we don't need the space, otherwise, we do. As #YCF_L mentioned we could just make a regex that is \\b\\d\\b but I am aiming for something conditional.
Conclusion
So, as a result, the question is, how do we make conditional regular expressions? Thanks for reading and cheers!
There are no conditionals in Java regexes.
I want a regex that checks if there are the same amount of regex's at the end as there are in the beginning. The conditional part: If there are x's at the beginning, then check if there are that many at the end, if there are then it is a match.
This may or may not be solvable. If you want to know if a specific string (or pattern) repeats, that can be done using a back reference; e.g.
^(\d+).+\1$
will match a line consisting of an arbitrary number digits, any number of characters, and the same digits matched at the start. The back reference \1 matches the string matched by group 1.
However if you want the same number of digits at the end as at the start (and that number isn't a constant) then you cannot implement this using a single (Java) regex.
Note that some regex languages / engines do support conditionals; see the Wikipedia Comparison of regular-expression engines page.
I would like to use split which accept regex like so :
String[] split = nums.split("\\s+"); // ["42", "36", "23827"]
If you want to use Pattern with Matcher, then you can use String \b\d+\b with word boundaries.
String regex = "\\b\\d+\\b";
By using word boundaries, you will avoid cases where the number is part of the word, for example "123 a4 5678 9b" you will get just ["123", "4578"]
I do not see the "conditional" in the question. The problem is solvable with a straight forward regular expression: \b\d+\b.
regex101 demo
A fully fledged Java example would look something like this:
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Ideone {
public static void main(String args[]) {
final String sample = "123 45 678 90";
final Pattern pattern = Pattern.compile("\\b\\d+\\b");
final Matcher matcher = pattern.matcher(sample);
final ArrayList<String> results = new ArrayList<>();
while (matcher.find()) {
results.add(matcher.group());
}
System.out.println(results);
}
}
Output: [123, 45, 678, 90]
Ideone demo

java regex pattern string format

I am exploring Regular expressions.
Problem statement : Replace String between # and # with the values provided in replacements map.
import java.util.regex.*;
import java.util.*;
public class RegExTest {
public static void main(String args[]){
HashMap<String,String> replacements = new HashMap<String,String>();
replacements.put("OldString1","NewString1");
replacements.put("OldString2","NewString2");
replacements.put("OldString3","NewString3");
String source = "#OldString1##OldString2#_ABCDEF_#OldString3#";
Pattern pattern = Pattern.compile("\\#(.+?)\\#");
//Pattern pattern = Pattern.compile("\\#\\#");
Matcher matcher = pattern.matcher(source);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(buffer, "");
buffer.append(replacements.get(matcher.group(1)));
}
matcher.appendTail(buffer);
System.out.println("OLD_String:"+source);
System.out.println("NEW_String:"+buffer.toString());
}
}
Output: ( Caters to my requirement but does not know who group(1) command works)
OLD_String:#OldString1##OldString2#_ABCDEF_#OldString3#
NEW_String:NewString1NewString2_ABCDEF_NewString3
If I change the code as below
Pattern pattern = Pattern.compile("\\#(.+?)\\#");
with
Pattern pattern = Pattern.compile("\\#\\#");
I am getting below error:
Exception in thread "main" java.lang.IndexOutOfBoundsException: No group 1
I did not understand difference between
"\\#(.+?)\\#" and `"\\#\\#"`
Can you explain the difference?
The difference is fairly straightforward - \\#(.+?)\\# will match two hashes with one or more chars between them, while \\#\\# will match two hashes next to each other.
A more powerful question, to my mind, is "what is the difference between \\#(.+?)\\# and \\#.+?\\#?"
In this case, what's different is what is (or isn't) getting captured. Brackets in a regex indicate a capture group - basically, some substring you want to output separately from the overall matched string. In this case, you're capturing the text in between the hashes - the first pattern will capture and output it separately, while the second will not. Try it yourself - asking for matcher.group(1) on the first will return that text, while the second will produce an exception, even though they both match the same text.
.+? Tells it to match (one or more of) anything lazily (until it sees a #). So as soon as it parses one instance of something, it stops.
I think the \#\# would match ## so i think the error is because it only matches that one ## and then there's only a group 0, no group 1. But not 100% on that part.

java regular expression

Can anyone please help me do the following in a java regular expression?
I need to read 3 characters from the 5th position from a given String ignoring whatever is found before and after.
Example : testXXXtest
Expected result : XXX
You don't need regex at all.
Just use substring: yourString.substring(4,7)
Since you do need to use regex, you can do it like this:
Pattern pattern = Pattern.compile(".{4}(.{3}).*");
Matcher matcher = pattern.matcher("testXXXtest");
matcher.matches();
String whatYouNeed = matcher.group(1);
What does it mean, step by step:
.{4} - any four characters
( - start capturing group, i.e. what you need
.{3} - any three characters
) - end capturing group, you got it now
.* followed by 0 or more arbitrary characters.
matcher.group(1) - get the 1st (only) capturing group.
You should be able to use the substring() method to accomplish this:
string example = "testXXXtest";
string result = example.substring(4,7);
This might help: Groups and capturing in java.util.regex.Pattern.
Here is an example:
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Example {
public static void main(String[] args) {
String text = "This is a testWithSomeDataInBetweentest.";
Pattern p = Pattern.compile("test([A-Za-z0-9]*)test");
Matcher m = p.matcher(text);
if (m.find()) {
System.out.println("Matched: " + m.group(1));
} else {
System.out.println("No match.");
}
}
}
This prints:
Matched: WithSomeDataInBetween
If you don't want to match the entire pattern rather to the input string (rather than to seek a substring that would match), you can use matches() instead of find(). You can continue searching for more matching substrings with subsequent calls with find().
Also, your question did not specify what are admissible characters and length of the string between two "test" strings. I assumed any length is OK including zero and that we seek a substring composed of small and capital letters as well as digits.
You can use substring for this, you don't need a regex.
yourString.substring(4,7);
I'm sure you could use a regex too, but why if you don't need it. Of course you should protect this code against null and strings that are too short.
Use the String.replaceAll() Class Method
If you don't need to be performance optimized, you can try the String.replaceAll() class method for a cleaner option:
String sDataLine = "testXXXtest";
String sWhatYouNeed = sDataLine.replaceAll( ".{4}(.{3}).*", "$1" );
References
https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/String.html
http://www.vogella.com/tutorials/JavaRegularExpressions/article.html#using-regular-expressions-with-string-methods

Categories