In Java, I have to insert strings value based on the key in main string.
For example -
Main String -
sellers(seller: $sellerId, shipment: $shipmentId)
Map of key and value -
{
sellerId: abc
shipmentId: 123
}
So after inserting it will become
sellers(seller: abc, shipment: 123)
I know i can do string replace. But that doesn't seem to be good approach here. So just wondering is there a standard approach or better way of doing things here?
Two approaches you can consider:
1 - loop over map entries, and do a simple string replace (note that this assumes a single occurrence of each var in the strings; if that is not the case, you need to use replaceAll):
String text = "sellers(seller: $sellerId, shipment: $shipmentId)";
Map<String, Object> binding = ...;
String result = text;
for (Entry<String, Object> entry : binding.entrySet()) {
result = result.replace("$" + entry.getKey(), String.valueOf(entry.getValue()));
}
2 - for advanced use cases, you want to use a proper template engine. And here's an example using groovy's simple template engine (use in java by adding the groovy jar):
groovy.text.SimpleTemplateEngine engine = new groovy.text.SimpleTemplateEngine();
Writable template = engine.createTemplate(text).make(binding);
String result = template.toString();
Just note that groovy replaces variable names prefixed with $, and that's why this works without changes (making this a good choice for your current syntax).
Both produce your expected result, but you have to choose based on what this can turn into.
Depending on values map can hold you may face some problems. For instance if value may contain other key identifier like
{
foo: $bar
bar: 123
}
then using series of replace(mapEntryKey, mapEntryValue) could change string like
abc $foo efg $bar
first into $foo->$bar
abc $bar efg $bar
and then $bar->123
abc 123 efg 123
which is NOT what we wanted.
To avoid such problem we should iterate over template only once, search for each $key and replace it with value stored for it in map. If map doesn't contain such key we can leave it as it (replace it with itself).
We can do it with Matcher#replaceAll(Function<MatchResult,String> replacer). BTW if map value can contain $ and \ which are also metacharacters in replacement, we need to escape them. To do it we can use Mather#quoteReplacement method.
Demo:
Map<String, String> map = Map.of("sellerId", "abc",
"shipmentId", "123");
String yourTemplate = "sellers(seller: $sellerId, shipment: $shipmentId)";
Pattern p = Pattern.compile("\\$(\\w+)");
Matcher m = p.matcher(yourTemplate);
String replaced = m.replaceAll(match -> {
if(map.containsKey(match.group(1))){
return Matcher.quoteReplacement(map.get(match.group(1)));
}else{
return Matcher.quoteReplacement(match.group());
}
});
System.out.println(replaced);
Output: sellers(seller: abc, shipment: 123).
String format is an option here
Map<String, Integer> yourMap = new HashMap<>();
yourMap.put("abc", 123);
for (Map.Entry<String, Integer> entry : yourMap.entrySet()) {
String output = String.format("sellers(seller: %s, shipment: %d)", entry.getKey(), entry.getValue());
System.out.println("output = " + output);
}
Related
I have this String
String tst = " {"id":$.id, "parent_id":200}";
I am trying to extract $.id from this string and replace it by an other word.
For now I tried:
tst = tst.replaceAll("(\\$.).", "other_word");
But this code is replacing all the rest (like "parent_id"...) by this other word
here is the output:
{"id":other_wordd_mag, "parent_id":200}
it's replacing only the "i" from "id_mag" any solution ?
This code seems to be replacing as expected:
String tst = " {\"id\":$.id, \"parent_id\":200}";
System.out.println(tst.replaceAll("\\$\\.id", "other_word"));
Output:
{"id":other_word, "parent_id":200}
Update
If you need to substitute some variables inside JSON, you can use the following regexp:
String tst = "{\"id\":$.id, \"name\":$.name, \"parent_id\":200}";
System.out.println(tst.replaceAll("(\\$\\.\\w+)", "\"other_word\"")); // using shorthand for word characters
output:
{"id":"other_word", "name":"other_word", "parent_id":200}
Or, if you have a map of variables in the form of key-value pairs, you can use this method:
static String replaceVars(String src, Map<String, String> vars) {
for (Map.Entry<String, String> e : vars.entrySet()) {
src = src.replaceAll("(\\$\\." + e.getKey()+ ")", "\"" + e.getValue()+"\"");
}
return src;
}
// -----------
String tstDiff = "{\"id\":$.id, \"name\":$.name, \"parent_id\":200}";
System.out.println(replaceVars(tstDiff, Map.of("id", "my_id", "name", "my_name")));
output:
{"id":"my_id", "name":"my_name", "parent_id":200}
Working with these types of strings can be a little bit easier if you know what JSON is.
And Java has also a really good library for handling Json strings called GSON.
You can use this library and for this specific case use the fromJson method.
But if you want to work with regex and get familiar with Strings:
tst.replaceAll("\\$\\.id", "other_word")
This should work properly.
So I'm trying retrieve specific substrings in values in a Hashmap constructed like this..
HashMap<ID, "Home > Recipe > Main Dish > Chicken > Chicken Breasts">
Which is passed from a different method that returns a HashMap
In above example, I need to retrieve Chicken.
Thus far, I have..
public static ArrayList<String> generalize() {
HashMap<String, String> items = new HashMap<>();
ArrayList<String> cats = new ArrayList<>();
items = RecSys.readInItemProfile("PATH", 0, 1);
for(String w : items.values()) {
cats.add(w);
}
for(String w : cats) {
int e = w.indexOf('>', 1 + w.indexOf('>', 1 + w.indexOf('>')));
String k = w.substring(e+1);
System.out.print(k);
e = 0;
}
System.out.println("k" + cats);
return cats;
}
Where I try to nullify String e for each iteration (I know it's redundant but it was just to test).
In my dataset, the first k-v pair is
3880=Home > Recipes > Main Dish > Pasta,
My output is
Pasta
Which is ok. If there are more than 3x ">", it'll return all following categories. Optimally it wouldn't do that, but it's ok if it does. However, further down the line, it (seemingly) randomly returns
Home > Recipe
Along with the rest of the data...
This happens at the 6th loop, I believe.
Any help is greatly appreciated..
Edit:
To clarify, I have a .csv file containing 3 columns, whereas 2 are used in this function (ID and Category). These are passed to this function by a read method in another class.
What I need to do is extract a generalized description of each category, which in all cases is the third instance of category specification (that is, always between the third and fourth ">" in every k-v pair).
My idea was to simply put all values in an arraylist, and for every value extract a string from between the third and fourth ">".
I recommend using the following map:
Map<Integer, List> map = new HashMap<>();
String[] vals = new String[] { "HomeRecipe", "Main Dish", "Chicken",
"Chicken Breasts" };
map.put(1, Arrays.asList(vals));
Then, if you need to find a given value in your original string using an ID, you can simply call ArrayList#get() at a certain position. If you don't care at all about order, then a map of integers to sets might make more sense here.
If you can. change your data structure to a HashMap<Integer, List<String>> or HashMap<Integer, String[]>. It's better to store the categories (by cats you mean categories right?) in a collection instead of a string.
Then you can easily get the third item.
If this is not possible. You need to do some debugging. Start by printing every input and output pair and find out which input caused the unexpected output. Your indexOf method seems to work at first glance.
Alternatively, try this regex method:
String k = cats.replaceAll("(?:[^>]+\\s*>\\s*){3}([^>]+).*", "$1");
System.out.println(k);
The regex basically looks for a xxx > yyy > zzz > aaa ... pattern and replaces that pattern with aaa (whatever that is in the original string).
EDIT:
I have string like this:
String value1 = "xyzz###$%helloworldtestdata"
or
String value1 = "xyzztestcase" or String value1 = "notincludedxyzztestcase"
and
String value2 = "xxxyz! xyyz xyzz xyyz"
I am trying to filter out each string with their corresponding word. So far, I have this code and it was fine but not with the value1
Map<String, String> map = new HashMap<String, String>();
map.put("xxxyz!", "test1");
map.put("xxxyz?", "test2");
map.put("xyyz", "test3");
map.put("xyzz", "test4");
for (String s : map.keySet()) {
if (value2.contains(s)) {
value2 = value2.replaceAll(s, map.get(s));
}
}
If I use the value2 here is the output I am getting:
test1 test3 test4 test3
But if I use the value1 I am getting this one:
test4###$%helloworldtestdata
How can I filter out the part that is not included on my map, key but not messing the spaces of value1?
The replaceAll method is simply taking whatever your value for s (the keys in your map) is and replacing it with your value for s in your map. From what you described, something similar to what you want to do is do a
value2 = value2.replaceAll("###$%helloworldtestdata", "");
This will replace the string ###$%helloworldtestdata with an empty one.
To do this reassignment in your method, you would want to add the following to your map:
map.put("###$%helloworldtestdata", "test5");
(test5 is just an example)
Adding this will not mess up your spaces in value1 because the string it looking to replace (the regex) has not been changed for any of the other strings you are looking for.
i dont know about i use, but here i use in my code.
var = "how to set love"
i just use one set to get value i want.
print var[:2]
is wil get "how"
and yes if you fil replace just use
x = var.replace("i will be", "how to")
it will get "i will be set love"
correct me if i flase 😁
I have a String like "key1:value1|prop:id|key3:value3|id:ABC.CDE|key4:value4", how can I split using Splitter.MapSplitter from Guava until id key?
Splitter.MapSplitter.on('|').withKeyValuePairs(':").split() returns an unmodifiable Map, so I need to walk through Map until id key and put entries into a new Map in this case. It does not looks an effective solution.
String.substring(0, String.indexOf('|', String.indexOf("id"))) is not an option because "id" String can be substring of any value before id key.
Or I can use two splitter, entrySplitter to split string into entries and keyValueSplitter to split entries into key-value pairs and then put them into a map while specific key hasn't been reached.
What is the best solution?
Other than copying the output of MapSplitter to another Map and manipulate that (assuming the keys order is preserved), I see no other solution than partially parsing yourself.
Your remark about "id" possibly appearing elsewhere is correct, so you need to search something more specific, like |id:...| or id:...| if id is the first key.
private static final Pattern ID_REGEX = Pattern.compile("(^|\\|)id:.+?\\|");
...
Matcher matcher = ID_REGEX.matcher(line);
if (matcher.find()) {
lineForMapSplitter = line.substring(0, matcher.end()-1);
}
First of all, don't use Splitter.MapSplitter directly, but rather Splitter#withKeyValueSeparator (here: Splitter.on('|').withKeyValueSeparator(':'). Secondly, in your case the most efficient way would be manually split pairs and then split pairs if your predicate (about key == id) is not met, and not create map until the very end.
TIMTOWDI, but I'm using jOOL, which has useful methods for your use case. Seq.seq(Iterable) is a simple helper for creating stream and, what's more important, Seq#limitUntilClosed(Predicate) will pick up all values until it finds id key:
private static final Splitter PAIRS_SPLITTER = Splitter.on('|');
private static final Splitter KEY_VALUE_SPLITTER = Splitter.on(':');
private static final String INVALID_ENTRY_MESSAGE = "Chunk [%s] is not a valid entry";
private Map<String, String> homebrewMapSplitter(final String string)
{
return Seq.seq(PAIRS_SPLITTER.split(string))
.map(this::createEntry)
.limitUntilClosed(e -> e.getKey().equals("id"))
.collect(ImmutableMap.toImmutableMap(
Map.Entry::getKey,
Map.Entry::getValue)
);
}
// ~copied from MapSplitter#split(CharSequence)
private Map.Entry<String, String> createEntry(final String entry)
{
Iterator<String> entryFields = KEY_VALUE_SPLITTER.split(entry).iterator();
checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry);
String key = entryFields.next();
checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry);
String value = entryFields.next();
checkArgument(!entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry);
return Maps.immutableEntry(key, value);
}
I have a phone directory with details like this:
ABc -> 123
bcd -> 345
cda -> 523
abc -> 678
So if I want to see ABc person's phone numbers, I should get the both numbers. How can we implement this in java? means which data structure will be the best?
If you are just looking for some data structure to handle this kind of data, I will offer using a HashMap of ArrayLists like this:
HashMap<String, ArrayList<Integer>> phoneNumber
Then you will be able to assign a dynamic array of numbers (like [123, 678] to each key (like "abc")
For this example, the code would be something like:
HashMap<String, ArrayList<Integer>> phoneNumber = new HashMap<String, ArrayList<Integer>>();
String key = "abc";
int value = 123;
if (phoneNumber.containsKey(key)) {
phoneNumber.get(key).add(value);
} else {
ArrayList<Integer> arrayList = new ArrayList<Integer>(1);
arrayList.add(value);
phoneNumber.put(key, arrayList);
}
Also if the letter case is important in this example, consider using String.toLowerCase() function, which makes the second line of code above, like this:
String key = "abc".toLowerCase();