Replace a map of values in string - java

Let's say I have a String text = "abc" and I want to replace a map of values, eg:
a->b
b->c
c->a
How would you go for it?
Because obviously:
map.entrySet().forEach(el -> text = text.replaceAll(el.getKey(), el.getValue()))
won't work, since the second replacement will overwrite also the first replacement (and at the end you won't get bca)
So how would you avoid this "replacement of the previous replacement"?
I saw this answer but I hope in a more concise and naive solution (and hopefully without the use of Apache external packages)
By the way the string can be also more than one character

I came up with this solution with java streams.
String text = "abc";
Map<String, String> replaceMap = new HashMap<>();
replaceMap.put("a", "b");
replaceMap.put("b", "c");
replaceMap.put("c", "a");
System.out.println("Text = " + text);
text = Arrays.stream(text.split("")).map(x -> {
String replacement = replaceMap.get(x);
if (replacement != null) {
return x.replace(x, replacement);
} else {
return x;
}
}).collect(Collectors.joining(""));
System.out.println("Processed Text = " + text);
Output
Text = abc
Processed Text = bca

This is a problem I'd normal handle with regex replacement. The code for that in Java is a bit verbose, but this should work:
String text = "abc";
Map<String, String> map = new HashMap<>();
map.put("a", "b");
map.put("b", "c");
map.put("c", "a");
String regex = map.keySet()
.stream()
.map(s -> Pattern.quote(s))
.collect(Collectors.joining("|"));
String output = Pattern.compile(regex)
.matcher(text)
.replaceAll((m) -> {
String s = m.group();
String r = map.get(s);
return r != null ? r : s;
});
System.out.println(output);
// bca
It's relatively straightforward, if a little verbose because Java. First, create a regex expression that will accept any of the keys in the map (using Pattern.quote() to sanitize them), and then use lambda replacement to pluck the appropriate replacement from the map whenever an instance is found.
The performance-intensive part is just compiling the regex in the first place; the replacement itself should make only one pass through the string.
Should be compatible with Java 1.9+

Java 8 onwards, there is a method called chars that returns an IntStream from which you can get a character corresponding to integer represented by the character and map it using your map.
If your map is String to String map then you could use:
text = text.chars().mapToObj(el -> map.get(String.valueOf((char)el))).
collect(Collectors.joining(""));
if your map is Character to Character then just remove String.valueOf()
text = text.chars().mapToObj(el -> map.get((char)el)).collect(Collectors.joining(""));

Related

Java Regex match to avoid splitting

I'm reading a text file that has multiple lines like below.
key1:Combine(val -> [{"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}])
key2:Combine(val -> [{"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"}])
key3:Combine(val -> [{"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"}])
For each line I need to create a map that has key and the Json string as the val.
For example for the above example I would need my map to be like this
map1 = key1,{"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}
map2 = key2,{"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"}
map3 = key3,{"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"}
Im using the split approach like below and strip the last 2 chars of the second val.
String[] temp = str.split(":Combine(val -> [");
Im trying to create regex pattern match to exract these key and val which I need help on
You will need to use a pattern and matcher with the regex that you require like so:
Pattern pattern = Pattern.compile("val -> (.*)\\)");
Matcher matcher = pattern.matcher(inputString);
matcher.results().forEach((match) -> {
System.out.println(match.group(1));
});
Will output:
[{"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}]
[{"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"}]
[{"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"}]
To convert the Json I can only recommend using Gson and parse it through that using by creating a class for it like:
Class GenericItem {
Public Integer id;
Public String pid;
}
Class ListOfGenericItems {
List<GenericItem> items;
}
Then using gson to turn the results into something more usable:
var key1 = new Gson.fromJson(match.group(1), ListOfGenericItems.class);
This way if you wish to use the data you can use
key1.items(0).id; //get id of result of first group on key 1
key1.items(1).pid; //get pid of result of second group on key 1
#WJS did have a better answer for the regex pattern however, mine will only return the json values theirs will bring the first key as well if you wanted to use that to identify key of groups.
Alternative using split:
Used regex:
"^(\\w+):Combine\\(val -> \\[(.*)\\]"
Split in context:
/**
* Content of inputFile.txt:
* key1:Combine(val -> [{"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}])
* key2:Combine(val -> [{"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"}])
* key3:Combine(val -> [{"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"}])
*/
public static void main(String[] args) throws IOException {
String fileName = "C:\\Users\\myUserName\\Desktop\\inputFile.txt"; // windows file system
List<String> linesFromInputFile = Files.readAllLines(Paths.get(fileName));
Pattern replacePattern = Pattern.compile("^(\\w+):Combine\\(val -> \\[(.*)\\]");
Pattern splitPattern = Pattern.compile("#");
Map<String, String> mapResult = linesFromInputFile.stream()
.map(line -> replacePattern.matcher(line).replaceFirst("$1#$2"))
.map(replacedLine -> splitPattern.split(replacedLine))
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
// Print output
mapResult.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.forEach(entry -> System.out.printf("Key: '%s' => Value: '%s'%n"
, entry.getKey()
, entry.getValue()));
}
Output:
Key: 'key1' => Value: '{"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"})'
Key: 'key2' => Value: '{"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"})'
Key: 'key3' => Value: '{"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"})'
Here is one of many possible solutions.
String[] strs = {
"key1:Combine(val -> [{\"id\":\"123\",\"pid\":\"Xd34d\"},{\"id\":\"124\",\"pid\":\"sdDfsd\"}])",
"key2:Combine(val -> [{\"id\":\"211\",\"pid\":\"Xd34d\"},{\"id\":\"223\",\"pid\":\"sdDfsd\"}])",
"key3:Combine(val -> [{\"id\":\"423\",\"pid\":\"Xd34d\"},{\"id\":\"454\",\"pid\":\"sdDfsd\"}])" };
Here is one way to parse it. And you can apply it in a stream to make it easy to build the map. The key and value would end up in group1 and group2 below.
(.*?): reluctant capture of characters up to first :
.*?\\[ reluctant skipping of characters up to and including first [
(.*}) capture of remaining characters up to and including last }
String regex = "(.*?):Combine.*?\\[(.*})";
Pattern p = Pattern.compile(regex);
Map<String, String> results = Arrays.stream(strs)
.flatMap(st -> p.matcher(st).results())
.collect(Collectors
.toMap(m -> m.group(1), m -> m.group(2)));
results.entrySet().forEach(System.out::println);
Prints
key1={"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}
key2={"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"}
key3={"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"}
You indicated you wanted to avoid splitting but in your post you show an example of using split so I thought I would address that with the following. You can split on multiple string using the alternation (|) operator. In the following case the split would produce an array of size 3. Only the first two values would be used as key and value.
String regex = ":Combine.*?\\[|(?:\\])";
String s =
"key1:Combine(val -> [{\"id\":\"123\",\"pid\":\"Xd34d\"},{\"id\":\"124\",\"pid\":\"sdDfsd\"}])";
String [] parts = s.split(regex);
for (int i = 0; i < parts.length; i++) {
System.out.printf("parts[%d] -> %s%n", i, parts[i]);
}
prints
parts[0] -> key1
parts[1] -> {"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}
parts[2] -> )
And using in a stream to create a map
key1={"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}
key2={"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"}
key3={"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"}
Map<String, String> results = Arrays.stream(strs)
.map(st -> st.split(regex))
.collect(Collectors.toMap(a -> a[0], a -> a[1]));
results.entrySet().forEach(System.out::println);
prints
key1={"id":"123","pid":"Xd34d"},{"id":"124","pid":"sdDfsd"}
key2={"id":"211","pid":"Xd34d"},{"id":"223","pid":"sdDfsd"}
key3={"id":"423","pid":"Xd34d"},{"id":"454","pid":"sdDfsd"}

Split string into parts in java to put it in a map

I have been trying to get all the values from a string and put them in a map in the following manner:
So I have a string which is like this:
String cookies = "i=lol;haha=noice;df3=ddtb;"
So far I have been trying this out:
final Map<String, String> map = new HashMap<>();
map.put(cookies.split(";")[0].split("=")[0], cookies.split(";")[0].split("=")[1]);
But this way I can only put one value in and it is quite long and ugly. Is there any was to due this with regex or a loop?
You could use a loop to iterate over the key value pairs and put them into the map:
String[] cookieArr = cookies.split(";");
for(String cookieString : cookieArr){
String[] pair = cookieString.split("=");
if(pair.length < 2){
continue;
}
map.put(pair[0], pair[1]);
}
The if is only there to prevent ArrayIndexOutOfBounds expcetions if cookie string is malformed
an alternativ would be using a stream:
Arrays.stream(cookies.split(";")).forEach(cookieStr -> map.put(cookieStr.split("=")[0], cookieStr.split("=")[1]));
As mentioned by #WJS in the comment, you could use map.putIfAbsent(key, vlaue) instead of map.put(key, value) to prevent overriding of values. But in case of cookies it could be a desired behavior to overwrite the old value with the new.
You could do it like this. It presumes your format is consistent.
first splits each k/v pair on ";"
the splits on "=" into key and value.
and adds to map.
if duplicate keys show up, the first one encountered takes precedence (if you want the latest value for a duplicate key then use (a, b)-> b as the merge lambda.)
String cookies = "i=lol;haha=noice;df3=ddtb";
Map<String, String> map = Arrays.stream(cookies.split(";"))
.map(str -> str.split("=")).collect(Collectors
.toMap(a -> a[0], a->a[1], (a, b) -> a));
map.entrySet().forEach(System.out::println);
Prints
df3=ddtb
haha=noice
i=lol

Replacing a string in java

I am trying to replace the short form words to normal from a string in java but don't know how to do it in a good way because I can have multiple ('ve 're 'nt) and such. Is it good to use array list and if so how do I achieve that?
What I have tried so far:
public class main {
public static void main(String[] args) {
String s = "We've been doing this for ages. I'm having a difficulty doing this. Thats getting confusing.";
s = s.replaceAll("we've", "we have");
s = s.replaceAll("I'm", "I am");
s = s.replaceAll("that's", "that is");
}
}
Thanks!
You can do it more efficiently using regexes.
First, build a map containing your searches and replacements.
Map<String, String> replacements =
Map.of("we've", "we have", "I'm", "I am" /* etc */);
(or some pre-Java 9 equivalent)
Now, build a regex to match the things you want to replace:
Pattern p = Pattern.compile(
replacements.keySet()
.stream()
.map(Pattern::quote)
.collect(Collectors.joining("|")));
Now, create a Matcher and a StringBuilder in which to accumulate your new string:
Matcher m = p.matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String replacement = replacements.get(m.group(0));
m.appendReplacement(sb, replacement);
}
m.appendTail(sb);
String newS = sb.toString();
Ideone demo
You could use a Map<String, String>, for example a HashMap where the keys would be the short form to replace and the value the replacement string. Then you could just iterate Map.entrySet() and call the replace method on the string.
Code could be (please note that I have omitted the initial letter - except for I which has to be upper case - to avoid the capitalized problem):
String str = s;
HashMap<String, String> replacements = new HashMap<>();
replacements.put("e've", "e have");
replacements.put("I'm", "I am");
replacements.put("hat's", "hat is");
for (Map.Entry<String, String> entry: replacements.entrySet()) {
str = str.replaceAll(entry.getKey(), entry.getValue());
}
It does not really make sense if it is intended to be used only once, but it could be the base of a method that could be re-used on many strings.
You can use a StringBuilder if you don't want to keep creating new strings all the time:
StringBuilder builder = new StringBuilder("We've been doing this for ages. I'm having a difficulty doing this. That's getting confusing.");
HashMap<String, String> replacements = new HashMap<>();
replacements.put("'ve", " have");
replacements.put("'m", " am");
replacements.put("'s", " is");
// others...
for (Map.Entry<String, String> entry: replacements.entrySet()) {
int index;
while ((index = builder.indexOf(entry.getKey())) != -1) {
builder.replace(index, index + entry.getKey().length(), entry.getValue());
}
}
System.out.println(builder);
Do note that if you are trying to replace all contractions like this, you are unlikely going to succeed 100% as some phrases contract to the same contraction, for example:
That has -> That's
That is -> That's
Also note that some 's don't indicate a contraction:
Mary's <-- how are you handling this?
You can kind of tackle the second problem by looking for more specific sequences like That's instead of just 's, but for the first problem, you will need to somehow understand the context.

Java: Replace in TreeMap

I have a Treemap:
TreeMap<String, Integer> map = new TreeMap<String, Integer>();
It counts words that are put in, for example if I insert:
"Hi hello hi"
It prints:
{Hi=2, Hello=1}
I want to replace that "," with a "\n", but I did not understand the methods in Java library. Is this possible to do? And is it possible to convert the whole TreeMap to a String?
When printing the map to the System.out is uses the map's toString function to print the map to the console.
You could either string replace the comma with a newline like this:
String stringRepresentation = map.toString().replace(", ", "\n");
This might however poses problems when your key in the map contains commas.
Or you could create a function to produce the desired string format:
public String mapToMyString(Map<String, Integer> map) {
StringBuilder builder = new StringBuilder("{");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
builder.append(entry.getKey()).append('=').append(entry.getValue()).append('\n');
}
builder.append('}');
return builder.toString();
}
String stringRepresentation = mapToMyString(map);
Guava has a lot of useful methods. Look at Joiner.MapJoiner
Joiner.MapJoiner joiner = Joiner.on('\n').withKeyValueSeparator("=");
System.out.println(joiner.join(map));

Java String array parsing and getting data

String input data is
{phone=333-333-3333, pr_specialist_email=null, sic_code=2391, status=ACTIVE, address1=E.BALL Drive, fax=333-888-3315, naics_code=325220, client_id=862222, bus_name=ENTERPRISES, address2=null, contact=BYRON BUEGE}
Key and values will increase in the array.
I want to get the value for each key ie myString.get("phone") should return 333-333-3333
I am using Java 1.7, is there any tools I can use this to parse the data and get the values.
Some of my input is having values like,
{phone=000000002,Desc="Light PROPERTITES, LCC", Address1="C/O ABC RICHARD",Address2="6508 THOUSAND OAKS BLVD.,",Adress3="SUITE 1120",city=MEMPHIS,state=TN,name=,dob=,DNE=,}
Comma separator doesn't work here
Here is a simple function that will do exacly what you want. It takes your string as an input and returns a Hashmap containing all the keys and values.
private HashMap<String, String> getKeyValueMap(String str) {
// Trim the curly ({}) brackets
str = str.trim().substring(1, str.length() - 1);
// Split all the key-values tuples
String[] split = str.split(",");
String[] keyValue;
HashMap<String, String> map = new HashMap<String, String>();
for (String tuple : split) {
// Seperate the key from the value and put them in the HashMap
keyValue = tuple.split("=");
map.put(keyValue[0].trim(), keyValue[1].trim());
}
// Return the HashMap with all the key-value combinations
return map;
}
Note: This will not work if there's ever a '=' or ',' character in any of the key names or values.
To get any value, all you have to do is:
HashMap<String, String> map = getKeyValueMap(...);
String value = map.get(key);
You can write a simple parser yourself. I'll exclude error checking in this code for brevity.
You should first remove the { and } characters, then split by ', ' and split each resulting string by =. At last add the results into a map.
String input = ...;
Map<String, String> map = new HashMap<>();
input = input.substring(1, input.length() - 1);
String elements[] = input.split(", ");
for(String elem : elements)
{
String values[] = elem.split("=");
map.put(values[0].trim(), values[1].trim());
}
Then, to retrieve a value, just do
String value = map.get("YOURKEY");
You can use "Google Core Libraries for Java API" MapSplitter to do your job.
First remove the curly braces using substring method and use the below code to do your job.
Map<String, String> splitKeyValues = Splitter.on(",")
.omitEmptyStrings()
.trimResults()
.withKeyValueSeparator("=")
.split(stringToSplit);

Categories