I have an input list like:
var list = Arrays.asList("etc->hosts", "etc->dockers->ssl->certs", "root");
And I am trying to convert it into nested map:
{etc={dockers={ssl={certs={}}}, hosts={}}, root={}}
I tried to split the key from input list with dot i.e. '->' and tried to iterate over to construct a map: Map<String, Map>.
Tried.groupingBy() .computeIfAbsent() .stream().map().filter().collect(), both of them failed.
You don't need streams, just a simple for loop that walks down each chain:
Map<String, Object> map = new HashMap<>();
for (String chain : list) {
Map<String, Object> current = map;
for (String node : chain.split("->")) {
current = (Map<String, Object>)current.computeIfAbsent(node, n -> new HashMap<>());
}
}
If you want to avoid the unchecked cast warnings, you can define a self-referential map like this (assuming you don't plan to mix in additional value types):
class NestingMap extends HashMap<String, NestingMap> {}
Ideone Demo
Related
I want to get values from this List:
List<Map<CategoryModel,Long>> list = categoryRespository.findSalesByCategory();
I print values from list like this:
for (Map<CategoryModel, Long> categoryModelLongMap : list) {
System.out.println(categoryModelLongMap.values());
}
Output:
[computers, 0]
[mouses, 0]
[keyboards, 0]
[laptops, 0]
But now i want to add these values to another map, as they appear above. I wrote something like that but it doesnt work
Map<String, String> newMap = new HashMap<>();
for (Map<CategoryModel, Long> featureService : list) {
for (Map.Entry<CategoryModel,Long> entry : featureService.entrySet()) {
newMap.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
}
}
Output:
{number=0, category_name=laptops}
Map<String, String> newMap, should be Map<String, Long>, but i dont know how to dispose of those "number" and "category_name". Objecy CategoryModel have also method "getCategoryName" which return String. Can someone help me how can i iterate through values in list?
One way to do it would be using something like this:
Instead of
Map<String, String> newMap = new HashMap<>();
for (Map<CategoryModel, Long> featureService : list) {
for (Map.Entry<CategoryModel,Long> entry : featureService.entrySet()) {
newMap.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
}
}
Use
Map<String, String> newMap = new HashMap<>();
for (Map<CategoryModel, Long> featureService : list) {
for (Map.Entry<CategoryModel,Long> entry : featureService.entrySet()) {
String rawKeyReturn = String.valueOf(entry.getKey());
String rawValueReturn = String.valueOf(entry.getValue());
newMap.put(
rawKeyReturn.substring(rawKeyReturn.indexOf("=")+1,rawKeyReturn.length()),
rawValueReturn.substring(rawValueReturn.indexOf("=")+1,rawValueReturn.length())
);
}
}
Apologies for the formatting, I am not sure how to fix the spacing in the code blocks.
This solution works by finding just creating a substring of the results of your calls to the valueOf() method and cutting off the label (everything up to and including the first = sign).
There may be more elegant ways to do this, but I'm not sure how.
You need to flatten the entries of each map in the List.
And then group the data associated with each distinct CategoryModel and add up the values mapped to the same key.
Basically, this can be done using two nested loops (your attempt was close).
That how it can be implemented using Java 8 method Map.merge():
List<Map<CategoryModel,Long>> list = categoryRespository.findSalesByCategory();
Map<String, Long> result = new HashMap<>();
for (Map<CategoryModel, Long> categoryModelLongMap : list) {
for (Map.Entry<CategoryModel, Long> entry : categoryModelLongMap.entrySet()) {
result.merge(entry.getKey().getCategoryName(),
entry.getValue(),
Long::sum);
}
}
Alternatively, it can be done using Stream API.
groupingBy + summingLong
You can generate a stream over the list. Flatten the entries of each Map using flatMap() operation, and then apply collect().
Collector groupingBy() would group the entries having the same category, and the downstream collector summingLong() would aggregate the values associated with the same key.
List<Map<CategoryModel,Long>> list = categoryRespository.findSalesByCategory();
Map<String, Long> result = list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.groupingBy(
entry -> entry.getKey().getCategoryName(),
Collectors.summingLong(Map.Entry::getValue)
));
toMap
Similarly, you can use three-args version of Collector toMap():
List<Map<CategoryModel,Long>> list = categoryRespository.findSalesByCategory();
Map<String, Long> result = list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
entry -> entry.getKey().getCategoryName(),
Map.Entry::getValue,
Long::sum
));
Every Java Map iteration example I've seen recommends this paradigm:
for (Map.Entry<String, String> item : hashMap.entrySet()) {
String key = item.getKey();
String value = item.getValue();
}
However, when I attempt to do this I get a warning from my compiler:
Incompatible types: java.lang.Object cannot be converted to java.util.Map.Entry<java.lang.String, java.lang.Object>
Here's my code - the only wrinkle I see is that I'm iterating over an array of Map objects, and then iterating over the elements of the individual Map:
result = getArrayOfMaps();
// Force to List LinkedHashMap
List<LinkedHashMap> result2 = new ArrayList<LinkedHashMap>();
for (Map m : result) {
LinkedHashMap<String, Object> n = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : m.entrySet()) {
n.put(entry.getKey(),entry.getValue());
}
result2.add(n);
}
Am I missing something blatantly obvious?
This is happening because you are using raw types: a List<LinkedHashMap> instead of a List<LinkedHashMap<Something, SomethingElse>>. As a result, the entrySet is just a Set instead of a Set<Map.Entry<Something, SomethingElse>>. Don't do that.
I want to convert from Collection<Map<String,String>> to Map<String,String>.
When I tried to do this way,
Map<String,String> m = (Map<String,String>)map.values();
where,
map is of type Map<String,Map<String,String>>
I'm getting
java.lang.ClassCastException: java.util.TreeMap$Values cannot be cast to java.util.Map
What is it trying to say? I'm not able to get it and how do I correctly convert from Collection<Map<String,String>> to Map<String,String>?
You can use this small snippet to put all the values into a single map:
Map<String, String> result = new TreeMap<>();
for(Map<String, String> value : map.values()) {
result.putAll(value);
}
Though this will just overwrite duplicate keys with a new value if there are any.
As long as it's collection then you should think as it's collection of objects.
Then proceed the iteration, for each object, you shall put it in the map
public Map<String, String> getMapsFromArrayOfMaps( Collection<Map<String,String>> maps ) {
Map<String, String> result = new HashMap<>();
maps.forEach(map->result.putAll(map));
return result ;
}
I have written this:
HashMap<String, String> map1 = new HashMap<String, String>();
Map<String, ArrayList<String>> map2 = new HashMap<String, ArrayList<String>>();
i am trying to allow more then 1 value for each key in a hashmap. so if the first key is '1', i want to allow '1' to be paired with values '2' and '3'.
so it be like:
1 --> 2
|--> 3
but when I do:
map2.put(key, value);
it gives error that says "incompatible types" and it can not be converted to ArrayList and it says the error is at the value part of the line.
If you are using Java 8, you can do this quite easily:
String key = "someKey";
String value1 = "someValue1";
String value2 = "someValue2";
Map<String, List<String>> map2 = new HashMap<>();
map2.computeIfAbsent(key, k -> new ArrayList<>()).add(value1);
map2.computeIfAbsent(key, k -> new ArrayList<>()).add(value2);
System.out.println(map2);
The documentation for Map.computeIfAbsent(...) has pretty much this example.
In map2 you need to add ArrayList (you declared it as Map<String, ArrayList<String>> - the second one is the value type) only, that's why it gives you incompatible types.
You would need to do initialize the key with an ArrayList and add objects to it later:
if (!map2.containsKey(key)) {
map2.put(key, new ArrayList<String>());
}
map2.get(key).add(value);
Or you could use Multimap from guava, then you can just map2.put and it won't overwrite your values there but add to a list.
You are little bit away from what you are trying to do.
Map<String, ArrayList<String>> map2 = new HashMap<String, ArrayList<String>>();
this will allow only String as key and an ArrayList as value. So you have to try something like:
ArrayList<String> value=new ArrayList<String>();
value.add("2");
value.add("3");
map2.put("1", value);
When retrieving you also have to follow ans opposite procedure.
ArrayList<String> valueTemp=map2.get("1");
then you can iterate over this ArrayList to get those values ("2" and "3");
Try like this. //use list or set.. but set avoids duplicates
Map<String, Set<String>> map = new HashMap<>();
Set<String> list = new HashSet<>();
// add value to the map
Boolean b = map.containsKey(key);
if (b) {
map.get(key).addAll(list);
} else
map.put(key, list);
}
You can not add different values in same key in Map. Map is override the value in that key. You can do like this way.
Map<String, ArrayList<String>> map = new HashMap<String, ArrayList<String>>();
ArrayList<String> list=new ArrayList<String>();
list.add("2");
list.add("3");
map.put("1", list);
first add value in array list then put into map.
It is all because standard Map implementations in java stores only single pairs (oneKey, oneValue). The only way to store multiple values for a particular key in a java standard Map is to store "collection" as value, then you need to access this collection (from Map) by key, and then use this collection "value" as regular collection, in your example as ArrayList. So you do not put something directly by map.put (except from creating the empty collection), instead you take the whole collection by key and use this collection.
You need something like Multimap, for example:
public class Multimap<T,S> {
Map<T, ArrayList<S>> map2 = new HashMap<T, ArrayList<S>>();
public void add(T key, S value) {
ArrayList<T> currentValuesForGivenKey = get(key);
if (currentValuesForGivenKey == null) {
currentValuesForGivenKey = new ArrayList<T>();
map2.get(key, currentValuesForGivenKey);
}
currentValuesForGivenKey.add(value);
}
public ArrayList<S> get(T key) {
ArrayList<String> currentValuesForGivenKey = map2.get(key);
if (currentValuesForGivenKey == null) {
currentValuesForGivenKey = new ArrayList<S>();
map2.get(key, currentValuesForGivenKey);
}
return currentValuesForGivenKey;
}
}
then you can use it like this:
Multimap<String,String> map2 = new Multimap<String,String>();
map2.add("1","2");
map2.add("1","3");
map2.add("1","4");
for (String value: map2.get("1")) {
System.out.println(value);
}
will print:
2
3
4
it gives error that says "incompatible types" and it can not be converted to ArrayList and it says the error is at the value part of the line.
because, it won't automatically convert to ArrayList.
You should add both the values to list and then put that list in map.
I need to validate if map (String to String) entry doesn't contain same key and value pair (case-insensitive). For example -
("hello", "helLo") // is not a valid entry
I was wondering if Google collection's Iterable combined with Predicates some how could solve this problem easily.
Yes I could have simple iterator for entries to do it myself, but thinking of any thing already up.
Looking for something in-lined with Iterables.tryFind(fromToMaster, Predicates.isEqualEntry(IGNORE_CASE)).isPresent()
If you want to use guava, you can use the Maps utils, specifically the filterEntries function.
An example to filter only entries where the key does not equal the value (ignoring the case) could look like this
Map<String, String> map = new HashMap<>();
map.put("hello", "helLo");
map.put("Foo", "bar");
Map<String, String> filtered = Maps.filterEntries(map, new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return !input.getKey().equalsIgnoreCase(input.getValue());
}
});
System.out.println(filtered); // will print {Foo=bar}
However there is no default Predicate in guava's Predicates I know of that does what you want.
Addition:
If you want a validation mechanism without creating a new map, you can use Iterables and the any method to iterate over the entry set of the map. To make the condition more readable I would assign the predicate to a variable or a member field of the class you are working in.
Predicate<Map.Entry<String, String>> keyEqualsValueIgnoreCase = new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return input.getKey().equalsIgnoreCase(input.getValue());
}
};
if (Iterables.any(map.entrySet(), keyEqualsValueIgnoreCase)) {
throw new IllegalStateException();
}
or if you need the entry, you can use the Iterables#tryFind method and use the returned Optional
Optional<Map.Entry<String, String>> invalid = Iterables.tryFind(map.entrySet(), keyEqualsValueIgnoreCase);
if(invalid.isPresent()) {
throw new IllegalStateException("Invalid entry " + invalid.get());
}