Multiple string replacements without affecting substituted text in subsequent iterations - java

I've posted about letters earlier, but this is an another topic, I have a json response that contain 2 objects, from and to , from is what to change, and to is what it will be changed to .
My code is :
// for example, the EnteredText is "ab b test a b" .
EnteredString = EnteredText.getText().toString();
for (int i = 0; i < m_jArry.length(); i++) {
JSONObject jo_inside = m_jArry.getJSONObject(i);
String Original = jo_inside.getString("from");
String To = jo_inside.getString("to");
if(isMethodConvertingIn){
EnteredString = EnteredString.replace(" ","_");
EnteredString = EnteredString.replace(Original,To + " ");
} else {
EnteredString = EnteredString.replace("_"," ");
EnteredString = EnteredString.replace(To + " ", Original);
}
}
LoadingProgress.setVisibility(View.GONE);
SetResultText(EnteredString);
ShowResultCardView();
For example, the json response is :
{
"Response":[
{"from":"a","to":"bhduh"},{"from":"b","to":"eieja"},{"from":"tes","to":"neesj"}
]
}
String.replace() method won't work here, because first it will replace a to bhduh, then b to eieja, BUT here's the problem, it will convert b in bhduh to eieja, which i don't want to.
I want to perfectly convert the letters and "words" in the String according the Json, but that what i'm failing at .
New Code :
if(m_jArry.length() > 0){
HashMap<String, String> m_li;
EnteredString = EnteredText.getText().toString();
Log.i("TestAf_","Before Converting: " + EnteredString);
HashMap<String,String> replacements = new HashMap<String,String>();
for (int i = 0; i < m_jArry.length(); i++) {
JSONObject jo_inside = m_jArry.getJSONObject(i);
String Original = jo_inside.getString("from");
String To = jo_inside.getString("to");
if(isMethodConvertingIn){
//EnteredString = EnteredString.replace(" ","_");
replacements.put(Original,To);
Log.i("TestAf_","From: " + Original + " - To: " + To + " - Loop: " + i);
//EnteredString = EnteredString.replace(" ","_");
//EnteredString = EnteredString.replace(Original,To + " ");
} else {
EnteredString = EnteredString.replace("_"," ");
EnteredString = EnteredString.replace("'" + To + "'", Original);
}
}
Log.i("TestAf_","After Converting: " + replaceTokens(EnteredString,replacements));
// Replace Logic Here
// When Finish, Do :
LoadingProgress.setVisibility(View.GONE);
SetResultText(replaceTokens(EnteredString,replacements));
ShowResultCardView();
Output :
10-10 19:51:19.757 12113-12113/? I/TestAf_: Before Converting: ab a ba
10-10 19:51:19.757 12113-12113/? I/TestAf_: From: a - To: bhduh - Loop: 0
10-10 19:51:19.757 12113-12113/? I/TestAf_: From: b - To: eieja - Loop: 1
10-10 19:51:19.757 12113-12113/? I/TestAf_: From: o - To: neesj - Loop: 2
10-10 19:51:19.758 12113-12113/? I/TestAf_: After Converting: ab a ba

You question would be clearer if you gave the expected output for the function.
Assuming it is: ab b test a b >>>> bhduheieja eieja neesjt bhduh eieja
then see the following, the key point in the Javadoc being "This will not repeat"
http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/StringUtils.html#replaceEach(java.lang.String,%20java.lang.String[],%20java.lang.String[])
Replaces all occurrences of Strings within another String.
A null reference passed to this method is a no-op, or if any "search
string" or "string to replace" is null, that replace will be ignored.
This will not repeat. For repeating replaces, call the overloaded
method.
Example 1
import org.apache.commons.lang3.StringUtils;
public class StringReplacer {
public static void main(String[] args) {
String input = "ab b test a b";
String output = StringUtils.replaceEach(input, new String[] { "a", "b", "tes" },
new String[] { "bhduh", "eieja", "neesj" });
System.out.println(input + " >>>> " + output);
}
}
Example 2
import org.apache.commons.lang3.StringUtils;
public class StringReplacer {
public static void main(String[] args) {
String input = "this is a test string with foo";
String output = StringUtils.replaceEach(input, new String[] { "a", "foo" },
new String[] { "foo", "bar"});
System.out.println(input + " >>>> " + output);
}
}

Try following:
Solution 1:
Traverse the String characters one by one and move the new String to a new StringBuffer or StringBuilder, then call toString() to get the result. This will need you to implement string matching algorithm.
Solution 2 (Using Regex):
For this, you must know the domain of your string. For example, it is [a-zA-Z] then other arbitrary characters (not part of domain) can be used for intermediate step. First replace the actual characters with arbitrary one then arbitrary ones with the target. In example below, [!##] are the arbitrary characters. These can be any random \uxxxx value as well.
String input = "a-b-c";
String output = input.replaceAll("[a]", "!").replaceAll("[b]", "#").replaceAll("[c]", "#");
output = output.replaceAll("[!]", "bcd").replaceAll("[#]", "cde").replaceAll("[#]", "def");
System.out.println("input: " + input);
System.out.println("Expected: bcd-cde-def");
System.out.println("Actual: " + output);

Your issue is quite common. To sum things up :
String test = "this is a test string with foo";
System.out.println(test.replace("a", "foo").replace("foo", "bar"));
Gives : this is bar test string with bar
Expected by you : this is foo test string with bar
You can use StrSubstitutor from Apache Commons Lang
But first you will have to inject placeholders in your string :
String test = "this is a test string with foo";
Map<String, String> valuesMap = new HashMap<>();
valuesMap.put("a", "foo");
valuesMap.put("foo", "bar");
String testWithPlaceholder = test;
// Preparing the placeholders
for (String value : valuesMap.keySet())
{
testWithPlaceholder = testWithPlaceholder.replace(value, "${"+value+"}");
}
And then, use StrSubstitutor
System.out.println(StrSubstitutor.replace(testWithPlaceholder, valuesMap));
It gives : this is foo test string with bar

Here is an method which is strictly just Java. I tried not to use any Java 8 methods here.
public static String translate(final String str, List<String> from, List<String> to, int index) {
StringBuilder components = new StringBuilder();
String token, replace;
int p;
if (index < from.size()) {
token = from.get(index);
replace = to.get(index);
p = 0;
for (int i = str.indexOf(token, p); i != -1; i = str.indexOf(token, p)) {
if (i != p) {
components.append(translate(str.substring(p, i), from, to, index + 1));
}
components.append(replace);
p = i + token.length();
}
return components.append(translate(str.substring(p), from, to, index + 1)).toString();
}
return str;
}
public static String translate(final String str, List<String> from, List<String> to) {
if (null == str) {
return null;
}
return translate(str, from, to, 0);
}
Sample test program
public static void main(String []args) {
String EnteredString = "aa hjkyu batesh a";
List<String> from = new ArrayList<>(Arrays.asList("a", "b", "tes"));
List<String> to = new ArrayList<>(Arrays.asList("bhduh", "eieja", "neesj"));
System.out.println(translate(EnteredString, from, to));
}
Output:
bhduhbhduh hjkyu eiejabhduhneesjh bhduh
Explaination
The algorithm is recursive, and it simply does the following
If a pattern found in the string matches a pattern in the from list
if there is any string before that pattern, apply the algorithm to that string
replace the found pattern with the corresponding pattern in the to list
append the replacement to the new string
discard the pattern in the from list and repeat the algorithm for the rest of the string
Otherwise append the rest of the string to the new string

You could use split like:
String[] pieces = jsonResponse.split("},{");
then you just parse the from and to in each piece and apply them with replace() then put the string back together again. (and please get your capitalization of your variables/methods right - makes it very hard to read the way you have it)

Apache Commons StringUtils::replaceEach does this.
String[] froms = new String[] {"a", "b"};
String[] tos = new String[] {"b","c"};
String result = StringUtils.replaceEach("ab", froms, tos);
// result is "bc"

Why not keep it very simple (if the JSON is always in same format, EG: from the same system). Instead of replacing from with to, replace the entire markup:
replace "from":"*from*" with "from":"*to*"

Why not just change the actual "to" and "from" labels? That way, you don't run into a situation where "bhudh" becomes "eieja". Just do a string replace on "from" and "to".

Related

Java - parse user defined functions

I am working on a program, where I want users to define a simple functions like
randomInt(0,10)
or
randomString(10)
instead of static arguments. What is the best way to parse and process such functions ?
I have not found any examples of such problem, the parser does not have to be ultra-efficient, it will not be called often, but mainly I want to focus on good code readability and scalability.
Example of user input:
"This is user randomString(5) and he is randomInt(18,60) years old!"
Expected output(s):
"This is user phiob and he is 45 years old!"
"This is user sdfrt and he is 30 years old!"
One option is to use Spring SPEL. But it forces you to change the expression a little and use Spring library:
The expression can look like this:
'This is user ' + randomString(5) + ' and he is ' + randomInt(18,60) + ' years old!'
or this:
This is user #{randomString(5)} and he is #{randomInt(18,60)} years old!
or you can implement your own by having a custom TemplateParserContext.
And here is the code:
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SomeTest {
#Test
public void test() {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(
"This is user #{randomString(5)} and he is #{randomInt(18,60)} years old!",
new TemplateParserContext() );
//alternative
//Expression exp = parser.parseExpression(
// "'This is user ' + randomString(5) + ' and he is ' + randomInt(18,60) + ' years old!'");
// String message = (String) exp.getValue( new StandardEvaluationContext(this) );
String message = (String) exp.getValue( new StandardEvaluationContext(this) );
}
public String randomString(int i) {
return "rs-" + i;
}
public String randomInt(int i, int j) {
return "ri-" + i + ":" + "j";
}
}
Whatever object you pass to StandardEvaluationContext should have those methods. I put them in the same class that also runs the expression.
You could use something such as the following:
Warning, I haven't tested it. Just something to get started with
public String parseInput(String input){
String[] inputArray = input.split(" ");
String output = "";
for(String in : inputArray){ //run through each word of the user input
if(in.contains("randomString(")){ //if the user is calling randomString
String params = in.replace("randomString(", ""); //strip away function to get to params
params = in.replace("(", ""); //strip away function to get to params
String[] paramsArray = params.split(","); //these are string integers, and could be converted
//send off these split apart parameters to your randomString method
String out = randomString(paramsArray); //method parses string integers, outputs string
output += out + " ";
}else if(in.contains("randomInt(")){ //if the user is calling randomInt
String params = in.replace("randomInt(", ""); //strip away function to get to params
params = in.replace("(", ""); //strip away function to get to params
String[] paramsArray = params.split(","); //these are string integers, and could be converted
//send off these split apart parameters to your randomInt method
String out = randomInt(paramsArray); //method parses string integers, outputs string
output += out + " ";
}else{ //if the user is just entering text
output += in + " "; //concat the output with what the user wrote plus a space
}
}
return output;
}

Variable arguments in java

I have the following classes:
public class WhereSqlClause{
public static String build(String pattern, Object... args){
//For instance, pattern = "Some string :placeholder"
//and we call this method as WhereSqlClause.build(pattern, placeholder)
//where placeholder was a local variable initialized before
}
}
My question is how to replace all placeholders like :placeholder with the placeholder.toString() values.
For instance:
String pattern = "Some str another str:place :holder";
Object place = "Place";
Object holder = "Holder";
String WhereSqlClause.build(pattern, place, holder);
//Have to produce "Some str Place another str Holder"
and
String pattern = "Some str :holder another str :place ";
Object place = "Place";
Object holder = "Holder";
String WhereSqlClause.build(pattern, place, holder);
//Have to produce "Some str Holder another str Place"
You can use MessageFormat, which can replace placeholders according to their positions.
You'll need a pattern of the form :
"some template with param {0} and another param {1}"
Then you can write :
public static String build (String pattern, Object... args)
{
MessageFormat formatter = new MessageFormat (pattern);
StringBuffer output = new StringBuffer (256);
return formatter.format (args, output, null).toString();
}
String[] myArgs = {"arg1", "arg2"};
String sentence = "here is a text with :arg and :arg";
int counter = 0;
while (sentence.indexOf(":arg") != -1) {
sentence = sentence.substring(0, sentence.indexOf(":arg")) + myArgs [counter] + sentence.substring(sentence.indexOf(":arg") + 4, sentence.length());
counter++;
}
Example with a single string, after you just have to iterate over args...
//For instance, pattern = "Some string :placeholder"
//and we call this method as WhereSqlClause.build(pattern, placeholder)
//where placeholder was a local variable initialized before
StringBuilder sbPattern = new StringBuilder();
sbPattern.append(pattern);
// you get ":placeholder "
String placeholder = ":"+(String)args[0].toLowerCase() + " ";
// if is in the pattern, replace it
if (pattern.contains(placeholder)) {
int i = pattern.indexOf(placeholder);
sbPattern.replace(i, i + placeholder.length() , (String)args[0] + " ");
}

Check a particular string and delete part of string in android

I have a string format stored in shared preference. I would like to check if a particular word exists. If yes, then delete those few words which start from x and ends at y.
Something like this: For example:
String items = "Veggies=Beans-Carrot-Potato-Onions--DailyUse=Milk-Yogurt-Soap--Fruits=Apple-Banana-Grapes-Pears";
I would like to check if the above string items has "DailyUse=" if so delete all the words that are after "DailyUse=" until "--". So that my string looks like:
String Newitems = "Veggies=Beans-Carrot-Potato-Onions--Fruits=Apple-Banana-Grapes-Pears";
Is this possible? If so, how do I go about doing this?
Thanks!
Try this.
public static void main(String[] args) {
String items = "Veggies=Beans-Carrot-Potato-Onions--DailyUse=Milk-Yogurt-Soap--Fruits=Apple-Banana-Grapes-Pears";
String[] newItems = items.split("--");
System.out.println(newItems[0] + "--"+ newItems[2]);
}
I tried to put the DailyUse element in first, second and third position and this code seems to work. I think it can be improved, but here's an idea.
String items = "DailyUse=Milk-Yogurt-Soap--Veggies=Beans-Carrot-Potato-Onions--Fruits=Apple-Banana-Grapes-Pears";
String result = "";
if (items.contains("--DailyUse=")){ // not in first position
String[] a = items.split("--DailyUse=");
result = a[0];
if (a[1].contains("--")){ // Daily use is not the last element
String[] b = a[1].split("--");
result = result + "--" + b[1] ;
}
}
else if (items.contains("DailyUse=")){ // first position
String[] b = items.split("--");
result = items.replace(b[0]+"--", ""); // Delete the dailyuse part
}
Using regex
String [] tests = {
"DailyUse=Milk-Yogurt-Soap--Veggies=Beans-Carrot-Potato-Onions--DailyUse=Milk-Yogurt-Soap--Fruits=Apple-Banana-Grapes-Pears--DailyUse=Milk-Yogurt-Soap"
,"DailyUse=Milk-Yogurt-Soap"
,"DailyUse=Milk-Yogurt-Soap--DailyUse=Milk-Yogurt-Soap"
};
String key = "DailyUse";
for (String test : tests) {
String newItems = test;
// Replace only one at beginning
String regexp = "(^" + key + "=.+?(--|$))";
while(newItems.matches(regexp)) {
newItems = newItems.replaceAll(regexp, "");
}
// Regex to replace all other
regexp = "(--" + key + "=.+?)(--|$)";
newItems = newItems.replaceAll(regexp,"$2");
System.out.println("Before " + test);
System.out.println("After " + newItems);
}
UPDATE based on comments

Java equivalent of PHP's implode(',' , array_filter( array () ))

I often use this piece of code in PHP
$ordine['address'] = implode(', ', array_filter(array($cliente['cap'], $cliente['citta'], $cliente['provincia'])));
It clears empty strings and join them with a ",". If only one remains it doesn't add an extra unneeded comma. It doesn't add a comma at the end. If none remains it returns empty string.
Thus I can get one of the following results
""
"Street abc 14"
"Street abc 14, 00168"
"Street abc 14, 00168, Rome"
What is the best Java implementation (less code) in Java without having to add external libraries (designing for Android)?
Updated version using Java 8 (original at the end of post)
If you don't need to filter any elements you can use
String.join(CharSequence delimiter, CharSequence... elements)
String.join(" > ", new String[]{"foo", "bar"});
String.join(" > ", "foo", "bar");
or String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
String.join(" > ", Arrays.asList("foo", "bar"));
Since Java 8 we can use StringJoiner (instead of originally used StringBulder) and simplify our code.
Also to avoid recompiling " *" regex in each call of matches(" *") we can create separate Pattern which will hold its compiled version in some field and use it when needed.
private static final Pattern SPACES_OR_EMPTY = Pattern.compile(" *");
public static String implode(String separator, String... data) {
StringJoiner sb = new StringJoiner(separator);
for (String token : data) {
if (!SPACES_OR_EMPTY.matcher(token).matches()) {
sb.add(token);
}
}
return sb.toString();
}
With streams our code can look like.
private static final Predicate<String> IS_NOT_SPACES_ONLY =
Pattern.compile("^\\s*$").asPredicate().negate();
public static String implode(String delimiter, String... data) {
return Arrays.stream(data)
.filter(IS_NOT_SPACES_ONLY)
.collect(Collectors.joining(delimiter));
}
If we use streams we can filter elements which Predicate. In this case we want predicate to accept strings which are not only spaces - in other words string must contain non-whitespace character.
We can create such Predicate from Pattern. Predicate created this way will accept any strings which will contain substring which could be matched by regex (so if regex will look for "\\S" predicate will accept strings like "foo ", " foo bar ", "whatever", but will not accept " " nor " ").
So we can use
Pattern.compile("\\S").asPredicate();
or possibly little more descriptive, negation of strings which are only spaces, or empty
Pattern.compile("^\\s*$").asPredicate().negate();
Next when filter will remove all empty, or containing only spaces Strings we can collect rest of elements. Thanks to Collectors.joining we can decide which delimiter to use.
Original answer (before Java 8)
public static String implode(String separator, String... data) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length - 1; i++) {
//data.length - 1 => to not add separator at the end
if (!data[i].matches(" *")) {//empty string are ""; " "; " "; and so on
sb.append(data[i]);
sb.append(separator);
}
}
sb.append(data[data.length - 1].trim());
return sb.toString();
}
You can use it like
System.out.println(implode(", ", "ab", " ", "abs"));
or
System.out.println(implode(", ", new String[] { "ab", " ", "abs" }));
Output ab, abs
Why so serious?
Try StringUtils.join(new String[] {"Hello", "World", "!"}, ", ") !
Here is an Android-specific answer that may be helpful to some:
String combined = TextUtils.join(",", new String[]{"Red", "Green", "Blue"});
// Result => Red,Green,Blue
Be sure to import the TextUtils class:
import android.text.TextUtils;
You'd have to add your strings to an ArrayList, remove empty ones, and format it accordingly:
public static String createAddressString( String street, String zip_code, String country) {
List<String> list = new ArrayList<String>();
list.add( street);
list.add( zip_code);
list.add( country);
// Remove all empty values
list.removeAll(Arrays.asList("", null));
// If this list is empty, it only contained blank values
if( list.isEmpty()) {
return "";
}
// Format the ArrayList as a string, similar to implode
StringBuilder builder = new StringBuilder();
builder.append( list.remove(0));
for( String s : list) {
builder.append( ", ");
builder.append( s);
}
return builder.toString();
}
Additionally, if you had String[], an array of strings, you can easily add them to an ArrayList:
String[] s;
List<String> list = new ArrayList<String>( Arrays.asList( s));
Using Streams (for Java 8 and later) would be an alternate possible solution for this.
You are required to import
java.util.stream.Collectors;
to use the join process
You may use:
Arrays.asList("foo","bar").stream().collect(Collectors.joining(","));
to achieve the desired result.
A simple Implode
public static String implode(String glue, String[] strArray)
{
String ret = "";
for(int i=0;i<strArray.length;i++)
{
ret += (i == strArray.length - 1) ? strArray[i] : strArray[i] + glue;
}
return ret;
}
You can create overloads for it..
The above it equivalent of php implode.
Here is what you want:
import java.lang.*
public static String customImplode(String glue, String[] strArray)
{
String ret = "";
for(int i=0;i<strArray.length;i++)
{
if (strArray[i].trim() != "")
ret += (i == strArray.length - 1) ? strArray[i] : strArray[i] + glue;
}
return ret;
}
Here's my implode implementation:
/**
* Implodes the specified items, gluing them using the specified glue replacing nulls with the specified
* null placeholder.
* #param glue The text to use between the specified items.
* #param nullPlaceholder The placeholder to use for items that are <code>null</code> value.
* #param items The items to implode.
* #return A <code>String</code> containing the items in their order, separated by the specified glue.
*/
public static final String implode(String glue, String nullPlaceholder, String ... items) {
StringBuilder sb = new StringBuilder();
for (String item : items) {
if (item != null) {
sb.append(item);
} else {
sb.append(nullPlaceholder);
}
sb.append(glue);
}
return sb.delete(sb.length() - glue.length(), sb.length()).toString();
}
public static String implode(List<String> items, String separator) {
if (items == null || items.isEmpty()) {
return null;
}
String delimiter = "";
StringBuilder builder = new StringBuilder();
for (String item : items) {
builder.append(delimiter).append(item);
delimiter = separator;
}
return builder.toString();
}
Use this simple function:
private String my_implode(String spacer, String[] in_array){
String res = "";
for (int i = 0 ; i < in_array.length ; i++) {
if (!res.equals("")) {
res += spacer;
}
res += in_array[i];
}
return res;
}
Use:
data_arr = {"d1", "d2", "d3"};
your_imploded_text = my_implode(",", data_arr);
// Output: your_imploded_text = "d1,d2,d3"

how to process string in java

I want to make strings like "a b c" to "prefix_a prefix_b prefix_c"
how to do that in java?
You can use the String method: replaceAll(String regex,String replacement)
String s = "a xyz c";
s = s.replaceAll("(\\w+)", "prefix_$1");
System.out.println(s);
You may need to tweek the regexp to meet your exact requirements.
Assuming a split character of a space (" "), the String can be split using the split method, then each new String can have the prefix_ appended, then concatenated back to a String:
String[] tokens = "a b c".split(" ");
String result = "";
for (String token : tokens) {
result += ("prefix_" + token + " ");
}
System.out.println(result);
Output:
prefix_a prefix_b prefix_c
Using a StringBuilder would improve performance if necessary:
String[] tokens = "a b c".split(" ");
StringBuilder result = new StringBuilder();
for (String token : tokens) {
result.append("prefix_");
result.append(token);
result.append(" ");
}
result.deleteCharAt(result.length() - 1);
System.out.println(result.toString());
The only catch with the first sample is that there will be an extraneous space at the end of the last token.
hope I'm not mis-reading the question. Are you just looking for straight up concatenation?
String someString = "a";
String yourPrefix = "prefix_"; // or whatever
String result = yourPrefix + someString;
System.out.println(result);
would show you
prefix_a
You can use StringTokenizer to enumerate over your string, with a "space" delimiter, and in your loop you can add your prefix onto the current element in your enumeration. Bottom line: See StringTokenizer in the javadocs.
You could also do it with regex and a word boundary ("\b"), but this seems brittle.
Another possibility is using String.split to convert your string into an array of strings, and then loop over your array of "a", "b", and "c" and prefix your array elements with the prefix of your choice.
You can split a string using regular expressions and put it back together with a loop over the resulting array:
public class Test {
public static void main (String args[]) {
String s = "a b c";
String[] s2 = s.split("\\s+");
String s3 = "";
if (s2.length > 0)
s3 = "pattern_" + s2[0];
for (int i = 1; i < s2.length; i++) {
s3 = s3 + " pattern_" + s2[i];
}
System.out.println (s3);
}
}
This is C# but should easily translate to Java (but it's not a very smart solution).
String input = "a b c";
String output (" " + input).Replace(" ", "prefix_")
UPDATE
The first solution has no spaces in the output. This solution requires a place holder symbol (#) not occuring in the input.
String output = ("#" + input.Replace(" ", " #")).Replace("#", "prefix_");
It's probably more efficient to use a StringBuilder.
String input = "a b c";
String[] items = input.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
StringBuilder sb = new StringBuilder();
foreach (String item in items)
{
sb.Append("prefix_");
sb.Append(item);
sb.Append(" ");
}
sb.Length--;
String output = sb.ToString();

Categories

Resources