Java 8 HashMap return String [duplicate] - java

I love Guava, and I'll continue to use Guava a lot. But, where it makes sense, I try to use the "new stuff" in Java 8 instead.
"Problem"
Lets say I want to join url attributes in a String. In Guava I would do it like this:
Map<String, String> attributes = new HashMap<>();
attributes.put("a", "1");
attributes.put("b", "2");
attributes.put("c", "3");
// Guava way
String result = Joiner.on("&").withKeyValueSeparator("=").join(attributes);
Where the result is a=1&b=2&c=3.
Question
What is the most elegant way to do this in Java 8 (without any 3rd party libraries)?

You can grab the stream of the map's entry set, then map each entry to the string representation you want, joining them in a single string using Collectors.joining(CharSequence delimiter).
import static java.util.stream.Collectors.joining;
String s = attributes.entrySet()
.stream()
.map(e -> e.getKey()+"="+e.getValue())
.collect(joining("&"));
But since the entry's toString() already output its content in the format key=value, you can call its toString method directly:
String s = attributes.entrySet()
.stream()
.map(Object::toString)
.collect(joining("&"));

public static void main(String[] args) {
HashMap<String,Integer> newPhoneBook = new HashMap(){{
putIfAbsent("Arpan",80186787);
putIfAbsent("Sanjay",80186788);
putIfAbsent("Kiran",80186789);
putIfAbsent("Pranjay",80186790);
putIfAbsent("Jaiparkash",80186791);
putIfAbsent("Maya",80186792);
putIfAbsent("Rythem",80186793);
putIfAbsent("Preeti",80186794);
}};
/**Compining Key and Value pairs and then separate each pair by some delimiter and the add prefix and Suffix*/
String keyValueCombinedString = newPhoneBook.entrySet().stream().
map(entrySet -> entrySet.getKey() + ":"+ entrySet.getValue()).
collect(Collectors.joining("," , "[","]"));
System.out.println(keyValueCombinedString);
/**
* OUTPUT : [Kiran:80186789,Arpan:80186787,Pranjay:80186790,Jaiparkash:80186791,Maya:80186792,Sanjay:80186788,Preeti:80186794,Rythem:80186793]
*
* */
String keyValueCombinedString1 = newPhoneBook.entrySet().stream().
map(Objects::toString).
collect(Collectors.joining("," , "[","]"));
System.out.println(keyValueCombinedString1);
/**
* Objects::toString method concate key and value pairs by =
* OUTPUT : [Kiran=80186789,Arpan=80186787,Pranjay=80186790,Jaiparkash=80186791,Maya=80186792,Sanjay=80186788,Preeti=80186794,Rythem=80186793]
* */
}
> Blockquote

Related

Java-Stream - Split, group and map the data from a String using a single Stream

I have a string as below:
String data = "010$$fengtai,010$$chaoyang,010$$haidain,027$$wuchang,027$$hongshan,027$$caidan,021$$changnin,021$$xuhui,020$$tianhe";
And I want to convert it into a map of type Map<String,List<String>> (like shown below) by performing the following steps:
first split the string by , and then split by $$;
the substring before $$ would serve as a Key while grouping the data, and the substring after $$ needs to placed inside into a list, which would be a Value of the Map.
Example of the resulting Map:
{
027=[wuchang, hongshan, caidan],
020=[tianhe],
010=[fengtai, chaoyang, haidain],
021=[changnin, xuhui]
}
I've used a traditional way of achieving this:
private Map<String, List<String>> parseParametersByIterate(String sensors) {
List<String[]> dataList = Arrays.stream(sensors.split(","))
.map(s -> s.split("\\$\\$"))
.collect(Collectors.toList());
Map<String, List<String>> resultMap = new HashMap<>();
for (String[] d : dataList) {
List<String> list = resultMap.get(d[0]);
if (list == null) {
list = new ArrayList<>();
list.add(d[1]);
resultMap.put(d[0], list);
} else {
list.add(d[1]);
}
}
return resultMap;
}
But it seems more complicated and verbose. Thus, I want to implement this logic one-liner (i.e. a single stream statement).
What I have tried so far is below
Map<String, List<String>> result = Arrays.stream(data.split(","))
.collect(Collectors.groupingBy(s -> s.split("\\$\\$")[0]));
But the output doesn't match the one I want to have. How can I generate a Map structured as described above?
You simply need to map the values of the mapping. You can do that by specifying a second argument to Collectors.groupingBy:
Collectors.groupingBy(s -> s.split("\\$\\$")[0],
Collectors.mapping(s -> s.split("\\$\\$")[1],
Collectors.toList()
))
Instead of then splitting twice, you can split first and group afterwards:
Arrays.stream(data.split(","))
.map(s -> s.split("\\$\\$"))
.collect(Collectors.groupingBy(s -> s[0],
Collectors.mapping(s -> s[1],Collectors.toList())
));
Which now outputs:
{027=[wuchang, hongshan, caidan], 020=[tianhe], 021=[changnin, xuhui], 010=[fengtai, chaoyang, haidain]}
You can extract the required information from the string without allocating intermediate arrays and by iterating over the string only once and also employing the regex engine only once instead of doing multiple String.split() calls and splitting first by coma , then by $$. We can get all the needed data in one go.
Since you're already using regular expressions (because interpreting "\\s\\s" requires utilizing the regex engine), it would be wise to leverage them to the full power.
Matcher.results()
We can define the following Pattern that captures the pieces of you're interested in:
public static final Pattern DATA = // use the proper name to describe a piece of information (like "027$$hongshan") that the pattern captures
Pattern.compile("(\\d+)\\$\\$(\\w+)");
Using this pattern, we can produce an instance of Matcher and apply Java 9 method Matcher.result(), which produces a stream of MatchResults.
MatchResult is an object encapsulating information about the captured sequence of characters. We can access the groups using method MatchResult.group().
private static Map<String, List<String>> parseParametersByIterate(String sensors) {
return DATA.matcher(sensors).results() // Stream<MatchResult>
.collect(Collectors.groupingBy(
matchResult -> matchResult.group(1), // extracting "027" from "027$$hongshan"
Collectors.mapping(
matchResult -> matchResult.group(2), // extracting "hongshan" from "027$$hongshan"
Collectors.toList())
));
}
main()
public static void main(String[] args) {
String data = "010$$fengtai,010$$chaoyang,010$$haidain,027$$wuchang,027$$hongshan,027$$caidan,021$$changnin,021$$xuhui,020$$tianhe";
parseParametersByIterate(data)
.forEach((k, v) -> System.out.println(k + " -> " + v));
}
Output:
027 -> [wuchang, hongshan, caidan]
020 -> [tianhe]
021 -> [changnin, xuhui]
010 -> [fengtai, chaoyang, haidain]

Java1.8 Split string into key-value pairs

I have a string like this but very big string
String data = "created:2022-03-16T07:10:26.135Z,timestamp:2022-03-16T07:10:26.087Z,city:Bangalore,Country:Ind";
Now : indicates key-value pairs while , separates the pairs. I want to add the key-value pairs to a HashMap. I am expecting output:-
{created=2022-03-16T07:10:26.135Z,timestamp=2022-03-16T07:10:26.087Z,city=Bangalore,Country=Ind}
I tried in multiple way but I am getting like that
{timestamp=2022-03-16T07, created=2022-03-16T07}
Based on the information provided, here one way to do it. It required both splitting in sections and limiting the size and location of the split.
String data = "created:2022-03-16T07:10:26.135Z,timestamp:2022-03-16T07:10:26.087Z,city:Bangalore,Country:Ind";
Map<String, String> map =
Arrays.stream(data.split(","))
.map(str -> str.split(":", 2))
.collect(Collectors.toMap(a -> a[0], a -> a[1]));
map.entrySet().forEach(System.out::println);
See this code run live at IdeOne.com.
city=Bangalore
created=2022-03-16T07:10:26.135Z
Country=Ind
timestamp=2022-03-16T07:10:26.087Z
As I said in the comments, you can't use a single map because of the duplicate keys. You may want to consider a class as follows to hold the information
class CityData {
private String created; // or a ZonedDateTime instance
private String timeStamp;// or a ZonedDateTime instance
private String city;
private String country;
#Getters and #setters
}
You could then group all the cities for of a given country for which you had data in a map as follows:
Map<String, List<CityData>> where the Key is the country.
var data="created:2022-03-16T07:10:26.135Z,timestamp:2022-03-16T07:10:26.087Z";
var split = data.split(","); // splitting created and timestamp
var created = split[0].substring(8); // 8 is size of 'created:'
var timestamp = split[1].substring(10); // 10 is size of 'timestamp:'
Map<String, String> result = new HashMap<>();
result.put("created", created);
result.put("timestamp", timestamp);
output:
{created=2022-03-16T07:10:26.135Z, timestamp=2022-03-16T07:10:26.087Z}
You need to split the data and iterate on this, split it one more time on colon by specifying index=2 and store the result in a Map. If you want to preserve the order use LinkedHashMap.
Map<String, String> map = new LinkedHashMap<>();
String data = "created:2022-03-16T07:10:26.135Z,timestamp:2022-03-16T07:10:26.087Z,city:Bangalore,Country:Ind";
String[] split = data.split(",");
for (String str: split) {
String[] pair = str.split(":", 2);
map.put(pair[0],pair[1]);
}
System.out.println(map);
Output: {created=2022-03-16T07:10:26.135Z, timestamp=2022-03-16T07:10:26.087Z, city=Bangalore, Country=Ind}

Replace a map of values in string

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

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 associative-array

How can I create and fetch associative arrays in Java like I can in PHP?
For example:
$arr[0]['name'] = 'demo';
$arr[0]['fname'] = 'fdemo';
$arr[1]['name'] = 'test';
$arr[1]['fname'] = 'fname';
Java doesn't support associative arrays, however this could easily be achieved using a Map. E.g.,
Map<String, String> map = new HashMap<String, String>();
map.put("name", "demo");
map.put("fname", "fdemo");
// etc
map.get("name"); // returns "demo"
Even more accurate to your example (since you can replace String with any object that meet your needs) would be to declare:
List<Map<String, String>> data = new ArrayList<>();
data.add(0, map);
data.get(0).get("name");
See the official documentation for more information
Java doesn't have associative arrays like PHP does.
There are various solutions for what you are doing, such as using a Map, but it depends on how you want to look up the information. You can easily write a class that holds all your information and store instances of them in an ArrayList.
public class Foo{
public String name, fname;
public Foo(String name, String fname){
this.name = name;
this.fname = fname;
}
}
And then...
List<Foo> foos = new ArrayList<Foo>();
foos.add(new Foo("demo","fdemo"));
foos.add(new Foo("test","fname"));
So you can access them like...
foos.get(0).name;
=> "demo"
You can accomplish this via Maps. Something like
Map<String, String>[] arr = new HashMap<String, String>[2]();
arr[0].put("name", "demo");
But as you start using Java I am sure you will find that if you create a class/model that represents your data will be your best options. I would do
class Person{
String name;
String fname;
}
List<Person> people = new ArrayList<Person>();
Person p = new Person();
p.name = "demo";
p.fname = "fdemo";
people.add(p);
Look at the Map interface, and at the concrete class HashMap.
To create a Map:
Map<String, String> assoc = new HashMap<String, String>();
To add a key-value pair:
assoc.put("name", "demo");
To retrieve the value associated with a key:
assoc.get("name")
And sure, you may create an array of Maps, as it seems to be what you want:
Map<String, String>[] assoc = ...
There is no such thing as associative array in Java. Its closest relative is a Map, which is strongly typed, however has less elegant syntax/API.
This is the closest you can get based on your example:
Map<Integer, Map<String, String>> arr =
org.apache.commons.collections.map.LazyMap.decorate(
new HashMap(), new InstantiateFactory(HashMap.class));
//$arr[0]['name'] = 'demo';
arr.get(0).put("name", "demo");
System.out.println(arr.get(0).get("name"));
System.out.println(arr.get(1).get("name")); //yields null
Well i also was in search of Associative array and found the List of maps as the best solution.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class testHashes {
public static void main(String args[]){
Map<String,String> myMap1 = new HashMap<String, String>();
List<Map<String , String>> myMap = new ArrayList<Map<String,String>>();
myMap1.put("URL", "Val0");
myMap1.put("CRC", "Vla1");
myMap1.put("SIZE", "Vla2");
myMap1.put("PROGRESS", "Vla2");
myMap.add(0,myMap1);
myMap.add(1,myMap1);
for (Map<String, String> map : myMap) {
System.out.println(map.get("URL"));
}
//System.out.println(myMap);
}
}
Java equivalent of Perl's hash
HashMap<Integer, HashMap<String, String>> hash;
Java doesn't have associative arrays, the closest thing you can get is the Map interface
Here's a sample from that page.
import java.util.*;
public class Freq {
public static void main(String[] args) {
Map<String, Integer> m = new HashMap<String, Integer>();
// Initialize frequency table from command line
for (String a : args) {
Integer freq = m.get(a);
m.put(a, (freq == null) ? 1 : freq + 1);
}
System.out.println(m.size() + " distinct words:");
System.out.println(m);
}
}
If run with:
java Freq if it is to be it is up to me to delegate
You'll get:
8 distinct words:
{to=3, delegate=1, be=1, it=2, up=1, if=1, me=1, is=2}
Use ArrayList < Map < String, String > >
Here a code sample :
ArrayList<Map<String, String>> products = new ArrayList<Map<String, String>>();
while (iterator.hasNext()) {
Map<String, String> product = new HashMap<String, String>();
Element currentProduct = iterator.next();
product.put("id",currentProduct.get("id"));
product.put("name" , currentProduct.get("name") );
products.add(product );
}
System.out.println("products : " + products);
Output :
products : [{id=0001, name=prod1}, {id=0002, name=prod2}]
Associative arrays in Java like in PHP :
SlotMap hmap = new SlotHashMap();
String key = "k01";
String value = "123456";
// Add key value
hmap.put( key, value );
// check if key exists key value
if ( hmap.containsKey(key)) {
//.....
}
// loop over hmap
Set mapkeys = hmap.keySet();
for ( Iterator iterator = mapkeys.iterator(); iterator.hasNext();) {
String key = (String) iterator.next();
String value = hmap.get(key);
}
More info, see Class SoftHashMap : https://shiro.apache.org/static/1.2.2/apidocs/org/apache/shiro/util/SoftHashMap.html
Object[][] data = {
{"mykey1", "myval1"},
{"mykey2", "myval2"},
{new Date(), new Integer(1)},
};
Yes, this require iteration for searchting value by key, but if you need all of them, this will be the best choice.
In JDK 1.5 (http://tinyurl.com/3m2lxju) there is even a note: "NOTE: This class is obsolete. New implementations should implement the Map interface, rather than extending this class."
Regards, N.
Actually Java does support associative arrays they are called dictionaries!
Thinking more about it, I would like to throw out tuples as a more general-purpose way of dealing with this problem. While tuples are not native to Java, I use Javatuples to provide me the same functionality which would exist in other languages. An example of how to deal with the question asked is
Map<Pair<Integer, String>, String> arr = new HashMap<Pair<Integer, String>, String>();
Pair p1 = new Pair(0, "name");
arr.put(p1, "demo");
I like this approach because it can be extended to triples and other higher ordered groupings with api provided classes and methods.
Regarding the PHP comment 'No, PHP wouldn't like it'. Actually, PHP would keep on chugging unless you set some very restrictive (for PHP) exception/error levels, (and maybe not even then).
What WILL happen by default is that an access to a non existing variable/out of bounds array element 'unsets' your value that you're assigning to. NO, that is NOT null. PHP has a Perl/C lineage, from what I understand. So there are: unset and non existing variables, values which ARE set but are NULL, Boolean False values, then everything else that standard langauges have. You have to test for those separately, OR choose the RIGHT evaluation built in function/syntax.

Categories