Replace substrings using regex - java

So I've been trying to make this simple encryption program but I can't seem to figure out some things. The phrase I need to enter is
This is a very big morning.
When I enter it though it returns the string
This is a ag',rery dug>?/ijeb..w ssadorninjeb..w
Instead I return
This is a ajedg>P/..w',rery dg>P/ijedg>P/..w ssadorninjedg>P/..w
I don't understand why and how to fix it? I've been learning java for around a month now so I'm still fresh and if there's a similar question that's already been answered please link me there and I'll delete this post.
Here's the code:
import static java.lang.System.out;
import java.util.Scanner;
class Encryption {
public static void main(String[] args) {
Scanner userInput = new Scanner(System.in);
Crypto user1 = new Crypto();
out.print("Please enter in a sentence: ");
String user = userInput.nextLine();
user1.encrypt(user);
out.print(user1.getEncrypt());
}
}
public Crypto() { }
public String myEn;
public void encrypt(String Sentence) {
myEn = Sentence.replaceAll("v","ag',r")
.replaceAll("m" , "ssad")
.replaceAll("g" , "jeb..w")
.replaceAll("b" , "dg>P/");
}
public String getEncrypt() {
return myEn;
}
}

The reason you're getting the different output is because the chained replaces take the return value of the previous replaces. So in your case, if there was a v, it would be changed with ag',r which has a g in it. That g would then trigger replaceAll("g" , "jeb..w").
To avoid this from happening, you should change the order of the replaces:
Sentence.replaceAll("g" , "jeb..w")
.replaceAll("b" , "dg>P/")
.replaceAll("v","ag',r")
.replaceAll("m" , "ssad");
However, the first two replace statements can't be fixed because one replaces b with a string that has a g in it, and vice-versa, so you might want to change the characters you're replacing.

When you replace the g its replacement contains a b so then when you replace the b's you get all the b's from the g replacement also replaced. and also for the v's
What you could do is
Sentence.replaceAll("g" , "jeb..w")
.replaceFirst("b" , "dg>P/") // as no g's before b's and only want to replace the first
.replaceFirst("v","ag',r")
.replaceFirst("m" , "ssad");
But this only works for this one sentence.
What you could do:
Create a map of all your characters to be replaced and there replacement.
Create a list of the indices of each character that is to be replaced on the original string.
Reverse the order of the list (highest to lowest)
Starting from the first index in the list (the last character to be replaced) replace the character at that index with its replacement from the map
Repeat 4 working backwards down the string.

Firstly, you should use replace(), not replaceAll(). Both replace all matches found, but replaceAll() uses regex to match and replace() uses plain text.
Next, your replacements are getting in the way of each other, so use a StringBuffer and work from the eighth and end backwards replacing one character at a time.
Decryption should likewise work one character at a time starting from the left.

As others already told you problem is that you are replacing characters in few iterations (replaceAll calls) instead of one. If you want to prevent replacing characters that ware used to replace other characters you can use appendReplacement and appendTail from Matcher class.
Here is how you can do it like
// We need to store somewhere info about original and its replacement
// so we will use Map
Map<String, String> replacements = new HashMap<>();
replacements.put("v", "ag',r");
replacements.put("m", "ssad");
replacements.put("g", "jeb..w");
replacements.put("b", "dg>P/");
// we need to create pattern which will find characters we want to replace
Pattern pattern = Pattern.compile("[vmgb]");//this will do
Matcher matcher = pattern.matcher("This is a very big morning.");
StringBuffer sb = new StringBuffer();// this will be used to create
// string with replaced characters
// lets start replacing process
while (matcher.find()) {//first we need to find our characters
// then pick from map its replacement
String replacement = replacements.get(matcher.group());
// and pass it to appendReplacement method
matcher.appendReplacement(sb, replacement);
// we repeat this process until original string has no more
// characters to replace
}
//after all we need to append to StringBuffer part after last replacement
matcher.appendTail(sb);
System.out.println(sb);
Output:
This is a ag',rery dg>P/ijeb..w ssadorninjeb..w.
Voilà.

Related

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();

Android Java, replace each letter in a text [duplicate]

I have some strings with equations in the following format ((a+b)/(c+(d*e))).
I also have a text file that contains the names of each variable, e.g.:
a velocity
b distance
c time
etc...
What would be the best way for me to write code so that it plugs in velocity everywhere a occurs, and distance for b, and so on?
Don't use String#replaceAll in this case if there is slight chance part you will replace your string contains substring that you will want to replace later, like "distance" contains a and if you will want to replace a later with "velocity" you will end up with "disvelocityance".
It can be same problem as if you would like to replace A with B and B with A. For this kind of text manipulation you can use appendReplacement and appendTail from Matcher class. Here is example
String input = "((a+b)/(c+(d*e)))";
Map<String, String> replacementsMap = new HashMap<>();
replacementsMap.put("a", "velocity");
replacementsMap.put("b", "distance");
replacementsMap.put("c", "time");
StringBuffer sb = new StringBuffer();
Pattern p = Pattern.compile("\\b(a|b|c)\\b");
Matcher m = p.matcher(input);
while (m.find())
m.appendReplacement(sb, replacementsMap.get(m.group()));
m.appendTail(sb);
System.out.println(sb);
Output:
((velocity+distance)/(time+(d*e)))
This code will try to find each occurrence of a or b or c which isn't part of some word (it doesn't have any character before or after it - done with help of \b which represents word boundaries). appendReplacement is method which will append to StringBuffer text from last match (or from beginning if it is first match) but will replace found match with new word (I get replacement from Map). appendTail will put to StringBuilder text after last match.
Also to make this code more dynamic, regex should be generated automatically based on keys used in Map. You can use this code to do it
StringBuilder regexBuilder = new StringBuilder("\\b(");
for (String word:replacementsMap.keySet())
regexBuilder.append(Pattern.quote(word)).append('|');
regexBuilder.deleteCharAt(regexBuilder.length()-1);//lets remove last "|"
regexBuilder.append(")\\b");
String regex = regexBuilder.toString();
I'd make a hashMap mapping the variable names to the descriptions, then iterate through all the characters in the string and replace each occurrance of a recognised key with it's mapping.
I would use a StringBuilder to build up the new string.
Using a hashmap and iterating over the string as A Boschman suggested is one good solution.
Another solution would be to do what others have suggested and do a .replaceAll(); however, you would want to use a regular expression to specify that only the words matching the whole variable name and not a substring are replaced. A regex using word boundary '\b' matching will provide this solution.
String variable = "a";
String newVariable = "velocity";
str.replaceAll("\\b" + variable + "\\b", newVariable);
See http://docs.oracle.com/javase/tutorial/essential/regex/bounds.html
For string str, use the replaceAll() function:
str = str.toUpperCase(); //Prevent substitutions of characters in the middle of a word
str = str.replaceAll("A", "velocity");
str = str.replaceAll("B", "distance");
//etc.

Java Regex Metacharacters returning extra space while spliting

I want to split string using regex instead of StringTokenizer. I am using String.split(regex);
Regex contains meta characters and when i am using \[ it is returning extra space in returning array.
import java.util.Scanner;
public class Solution{
public static void main(String[] args) {
Scanner i= new Scanner(System.in);
String s= i.nextLine();
String[] st=s.split("[!\\[,?\\._'#\\+\\]\\s\\\\]+");
System.out.println(st.length);
for(String z:st)
System.out.println(z);
}
}
When i enter input [a\m]
It returns array length as 3 and
a m
Space is also there before a.
Can anyone please explain why this is happening and how can i correct it. I don't want extra space in resulting array.
Since the [ is at the beginning of the string, when split removes [, there appear two elements after the first split step: the empty string that is at the beginning of the string, and the rest of the string. String#split does not return trailing empty elements only (as it is executed with limit=0 by default).
Remove the characters you split against from the start (using a .replaceAll("^[!\\[,?._'#+\\]\\s\\\\]+", note the ^ at the beginning of the pattern). Here is a sample code you can leverage:
String[] st="[a\\m]".replaceAll("^[!\\[,?._'#+\\]\\s\\\\]+", "")
.split("[!\\[,?._'#+\\]\\s\\\\]+");
System.out.println(st.length);
for(String z:st) {
System.out.println(z);
}
See demo
As an addition to Wiktor Stribiżew’s answer, you may do the same without having to specify the pattern twice, by dealing with the java.util.regex package directly. Removing this redundancy may avoid potential errors and may also be more efficient as the pattern doesn’t need to be parsed twice:
Pattern p = Pattern.compile("[!\\[,?\\._'#\\+\\]\\s\\\\]+");
Matcher m = p.matcher(s);
if(m.lookingAt()) s=m.replaceFirst("");
String[] st = p.split(s);
for(String z:st)
System.out.println(z);
To be able to use the same pattern, i.e. without having to use the anchor ^ for removing a leading separator, we first check via lookingAt() whether the pattern really matches at the beginning of the text before removing the first occurrence. Then, we proceed with the split operation, but reusing the already prepared Pattern.
Regarding your issue mentioned in a comment, the split operation will always return at least one element, the input string, when there is no match, even when the string is empty. If you wish to have an empty array then, the only solution is to replace the result explicitly:
if(st.length==1 && s.equals[0]) st=new String[0];
or, if you only want to treat an empty string specially, you may check this beforehand:
if(s.isEmpty()) st=new String[0];
else {
// the code as shown above
}

Looking for method to remove spaces on sides, change all letters to small with first letter as capital letter

I have been trying for a while to make a method which takes an user input and changes it so that potential spaces infront and after the text should be removed. I tried .trim() but doesnt seem to work on input strings with two words. also I didnt manage to make both first and second word have the first letter as Capital.
If user inputs the following string I want all separate words to have all small letters except for the first in the word. e.g: Long Jump
so if user inputs:
"LONG JuMP"
or
" LoNg JUMP "
change it to
"Long Jump"
private String normalisera(String s) {
return s.trim().substring(0,1).toUpperCase() + s.substring(1).toLowerCase();
}
I tried the method above but didnt work with two words, only if the input was one. It should work with both
To remove all spaces extra spaces you can do something like this
string = string.trim().replaceAll(" +", " ");
The above code will call trim to get rid of the spaces at the start and end, then use regex to replace everything that has 2 or more spaces with a single space.
To capitalize the first word, if you're using Apache's commons-lang, you can use WordUtils.capitalizeFully. Otherwise, you'll need to use a homebrewed solution.
Simply iterate through the String, and if the current character is a space, mark the next character to be uppercased. Otherwise, make it lowercase.
Split your problems into smaller ones:
You need to be able to:
iterate over all words and ignore all whitespaces (you can use Scanner#next for that)
edit single word into new form (create helper method like String changeWord(String){...})
create new String which will collect edited versions of each word (you can use StringBuilder or better StringJoiner with delimiter set as one space)
So your general solution can look something like:
public static String changeWord(String word) {
//code similar to your current solution
}
public static String changeText(String text) {
StringJoiner sj = new StringJoiner(" ");// space will be delimiter
try(Scanner sc = new Scanner(text)){
while (sc.hasNext()) {
sj.add(changeWord(sc.next()));
}
}
return sj.toString();
}
Since Strings are immutable and you cannot make in place changes you need to store it in a separate variable and then do your manipulations like this:
String s = " some output ";
String sTrimmed = s.trim();
System.out.println(s);
System.out.println(sTrimmed);
Change your code like this for the rest of your code as well.

How to count the number of sub string from a split in java

I have this piece of code in my java class
mystring = mysuperstring.split("/");
I want to know how many sub-string is created from the split.
In normal situation, if i want to access the first sub-string i just write
mystring[0];
Also, i want to know if mystring[5] exist or not.
I want to know how many sub-string is created from the split.
Since mystring is an array, you can simply use mystring.length to get the number of substrings.
Also, i want to know if mystring[5] exist or not.
To do this:
if (mystring.length >= 6) { ... }
mystring = mysuperstring.split("/");
int size = mystring.length;
remember that arrays are zero indexed, so where length = 5, the last element will be indexed with 4.
It's a simple way to count the sub-strings
word.split('/').length;
You can see an example of this implementation here.
Try this one & tell me if it works.
import java.util.regex.Pattern;
public class CountSubstring {
public static int countSubstring(String subStr, String str){
// the result of split() will contain one more element than the delimiter
// the "-1" second argument makes it not discard trailing empty strings
return str.split(Pattern.quote(subStr), -1).length - 1;
}
public static void main(String[] args){
System.out.println(countSubstring("th", "the three truths"));
System.out.println(countSubstring("abab", "ababababab"));
System.out.println(countSubstring("a*b", "abaabba*bbaba*bbab"));
}
}
I think you must read about split and array, please find the links.
if you read about split function it returns Array of String.
and now you should read about Array and its size
I wanted to demo a dumy token validation. So I tried to split by dot (.). It did not worked. So, I wonder and checked out an hour with no luck. After a while, when randomly trying I added escape character before dot and thank god it worked :D.
The reason is split takes string as regex. I checked why when writing an answer here:
You are splitting on the regex ., which means "any character"
int len = accessToken.split("\\.").length;
String I wanted to check
String accessToken = Bearer 1111.1111.11111 // demonstration purpose dumy
Output:
3

Categories