In one of my projects, I tried to set values in a nested map passed in to the parameter and return an updated map. The question is: assuming that I didn't know the map structure, how can I set values in a given path in a nested map?
I tried to do just that. I attempted to recursively call the set method but to no avail, instead of returning {age=1, human={lives=3, deaths=2}}, the method either returned {deaths=2} or null. However, please note that this is one of my many innumerable tries.
Here's one of my methods (other methods were deleted):
#SuppressWarnings("unchecked")
private static Map<Object, Object> setNested(YamlParser parser, List<String> paths, String key, Object value, Map<Object, Object> previousMap, int loops) {
Object found = parser.getObject(paths);
if (!(found instanceof Map))
return previousMap; // path is not nested
Map<Object, Object> theMap = (Map<Object, Object>) found;
theMap.put(key, value);
// .... ?
System.out.println(theMap);
return setNested(parser, paths, key, theMap, theMap, loops + 1);
}
A version with adding all the missing intermediate maps is even simpler:
private static Map<String, Object> setNested(Map<String, Object> map, List<String> keys, Object value) {
String key = keys.get(0);
List<String> nextKeys = keys.subList(1, keys.size());
Object newValue;
if (nextKeys.size() == 0) {
newValue = value;
} else if (!map.containsKey(key)) {
newValue = setNested(new LinkedHashMap<>(), nextKeys, value);
} else {
newValue = setNested((Map<String, Object>) map.get(key), nextKeys, value);
}
Map<String, Object> copyMap = new LinkedHashMap<>(map);
copyMap.put(key, newValue);
return copyMap;
}
I do not see what the YamlParser is good for in this example and I do not know what exactly you want to do. I think, it is about making a new map where the intermediate maps and the final (leaf) map have been copied and the new leaf map has a new value.
If this is not exactly what you need, you are free to modify it. Maybe it gives you a hint how to implement your own method:
public class Test {
private static Map<String, Object> setNested(Map<String, Object> map, List<String> keys, Object value) {
String key = keys.get(0);
List<String> nextKeys = keys.subList(1, keys.size());
if (nextKeys.size() == 0) {
Map<String, Object> copyMap = new LinkedHashMap<>((Map) map);
copyMap.put(key, value);
return copyMap;
} else if (!map.containsKey(key)) {
return map;
} else {
Map<String, Object> copyMap = new LinkedHashMap<>((Map) map);
Map<String, Object> nextMap = (Map<String, Object>) map.get(key);
copyMap.put(key, setNested(nextMap, nextKeys, value));
return copyMap;
}
}
public static void main(String[] args) {
Map<String, Object> map1 = new LinkedHashMap<>();
Map<String, Object> map2 = new LinkedHashMap<>();
map2.put("lives", 3);
map2.put("deaths", 2);
map1.put("age", 1);
map1.put("human", map2);
System.out.println(map1);
map1 = setNested(map1, Arrays.asList("human", "deaths"), 7);
System.out.println(map1);
}
}
Note: This method can insert new keys at the lowest level maps, but not at the intermediate maps.
Related
I have this map on my YamlFile class, which stores all the keys of the file on this format: String key = "firskey.secondkey.thirdkey", Object value = "Example value"
private static Map<String, Object> deepkeymap;
Now, I want to convert my deepkeymap to a nested map that works like this: {firstkey={secondkey={thirdkey="Example value"}}}, my deepkeymap actually stores 4 keys with 4 values (the amount of keys and values will change). I have kind of accomplished this, but not totally as it only converts the last key and value of my deepkeymap, in fact, the example I've put is the output of my nested map, here is the code:
public void save() {
try {
Map<String, Object> datamap = new HashMap<String, Object>();
for(String key : deepkeymap.keySet()) {
Object value = deepkeymap.get(key);
int end = key.length();
for(int start; (start = key.lastIndexOf('.', end - 1)) != -1; end = start) {
value = new HashMap<>(Collections.singletonMap(key.substring(start + 1, end), value));
}
datamap.putAll(new HashMap<>(Collections.singletonMap(key.substring(0, end), value)));
}
System.out.println("Datamap: "+datamap);
} catch (IOException e) {
e.printStackTrace();
}
}
As mentioned above, output is:
Datamap: {firstkey={secondkey={thirdkey="Example value"}}}
But it should have another 3 keys as deepkeymap contains 4 keys with their respective 4 values, I have already checked they are stored on it and no one has a null value, doing a debug on the keySet loop printing keys and values.
You can play with the code below. Factory method fails for incorrect input, also, there is only toString for the DeepKeyMap. Below is a JUnit test, that just runs the code and tests nothing. You can extract the DeepKeyMap into a seperate class if you will use it in the future.
public class MapTest
{
static class DeepKeyMap
{
Map<String, Object> map = new HashMap<>();
public void put(
String path,
Object value
)
{
String[] split = path.split("\\.");
this.put(split, value);
}
public void put(
String[] path,
Object value
)
{
Map<String, Object> deepestMap = createMapsToTheDeepestKey(path);
String deepestKey = path[path.length - 1];
deepestMap.put(deepestKey, value);
}
private Map<String, Object> createMapsToTheDeepestKey(String[] path)
{
Map<String, Object> deepestMap = map;
for (int i = 0; i < path.length - 1; i++)
{
String key = path[i];
deepestMap = getDeeperMap(deepestMap, key);
}
return deepestMap;
}
private Map<String, Object> getDeeperMap(
Map<String, Object> deepestMap,
String key
)
{
if (!deepestMap.containsKey(key))
{
deepestMap.put(key, new HashMap<>());
}
return (Map<String, Object>) deepestMap.get(key);
}
#Override
public String toString()
{
return map.toString();
}
public static DeepKeyMap from(Map<String, Object> original)
{
DeepKeyMap result = new DeepKeyMap();
// the for loop can be minimized to
// original.forEach(result::put);
for (var entry : original.entrySet())
{
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}
#Test
void test()
{
Map<String, Object> original = Map.of("flat", "First Level",
"nested.value.one", "Second Level",
"nested.value.two", "Third Level",
"nested.different.value.one", "Fourth Level"
);
DeepKeyMap deepMap = DeepKeyMap.from(original);
System.out.println(deepMap);
}
}
Edit: I refactored the code above a bit. Hopefully it is a bit more clear what it does.
I would not use it like that, you can have there identical keys on different levels, which is not the intention of a Map.
Also, you may produce a construct that has as type of key either a map or a string. This brings various uncertainties.
You should consider using other data structures or a database.
What is your intention, maybe we can assist you.
How do I access a list inside a map of type Map <String, Object>?
public Map<String, Object> getListInsideMapObject(Long id, Date from) {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> mapList = new ArrayList<>();
List<MappedList> conTime = new ArrayList<>();
conTime = xxxRepository.findByxxx(id, from);
Map<String, Object> map = xxxService.xxx(id);
List<String> times = (List<String>) map.get("xxx");
for (MappedList t : conTime) {
int num = 0;
Map<String, Object> res = new HashMap<>();
res.put("x", "");
res.put("status", null);
for (Contraction c : con) {
num++;
res.put("status", "stat");
res.put("x", new Date());
}
}
res.put("y", num);
mapList.add(res);
result.put("mapList", mapList);
result.put("mapListA", mapListA);
result.put("mapListB", mapListB);
//etc
return result;
}
I am trying to call this service (getListInsideMapObject) and access each list from this map and loop through each list. for example in class xxx i want to call getListInsideMapObject(Long id, Date from) as a service and access each list from the map
I imagine you want something like this:
public NewClass1() {
// have an instance from the class that gives you the map
ClassThatBuildsTheMap mapClass = new ClassThatBuildsTheMap();
// get the map. must provide id and date
Map <String, Object> myMap = mapClass.getListInsideMapObject(id, date);
// access the lists inside the map
useListInsideAMap(myMap);
}
private void useListInsideAMap(Map<String, Object> map){
// Prior to Java8:
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey(); // if you want to use the key
Object value = entry.getValue();
if(value instanceof List){
// I supose it is a list of sytrings
List l = (List) value;
for (Object item : l) {
// Do something with the item from the list
}
}
}
// Java8:
// loop through the map
map.forEach((String key, Object value)->{
// if you want to use key, just call key
// checks if the value (Object) from the map is a list
if(value instanceof List){
List l = (List)value;
// loop through the list
l.forEach((Object item)->{
// Do something with the item from the list
});
}
});
}
You can cast your Object to List
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> mapList= new ArrayList<>();
result.put("1", mapList);
-------------------------------------------------------
List<Map<String, Object>> = (List<Map<String, Object>>)result.get("1")
Then, you can loop normally using for/foreach.
Also you can use Optional object to get your map value by key and check it before cast to needed type (to avoid ClassCastException issues) :
Map<String, Object> someMap = new HashMap<>();
List<Map<String, Object>> result = Optional.ofNullable(someMap.get("id"))
.filter(obj -> obj instanceof List)
.map(obj -> (List<Map<String, Object>>)obj)
.orElse(Collections.emptyList());
In that case you will have empty List (if element missed or type is not a List) or expected List value.
I have a list of maps. Which I cannot predict the key of each map. key can be an any two digits number which will change acordingly to the request.But values contains unique values from request to request.Now I need to sort this list of maps according to the value of each key. I've already tried something like bellow,
List<Map<String,String>> listOfMaps = new ArrayList<>();
Map<String,String> map1 = new HashMap<>();
Map<String,String> map2 = new HashMap<>();
map1.put("key1","value1");
map2.put("key2","value1");
listOfMaps.add(map1);
listOfMaps.add(map2);
sort(listOfMaps);
Collections.sort(listOfMaps, new Comparator<Map<String, String>>() {
public int compare(final Map<String, String> o1, final Map<String, String> o2) {
return o1.get("key").compareTo(o2.get("key"));
}
});
Since "key" can be differ from map to map, seems I couldnt use the above code.
I've also tried these examples.
How to Sort a List of Maps by two Categories?
Sorting list of maps based on a value
Since I'm cannot predict the key, I couldnt find an answer from these post.Can any one help with this?.
The below example should work, under the assumption that each map element contains only one entry:
List<Map<String, String>> list = new ArrayList<>();
//Add entries
Collections.sort(list, new Comparator<Map<String, String>>() {
public int compare(Map<String, String> o1, Map<String, String> o2) {
Collection<String> values1 = o1.values();
Collection<String> values2 = o2.values();
if(!values1.isEmpty() && !values2.isEmpty()){
return values1.iterator().next().compareTo(values2.iterator().next());
}else{
return 0;
}
}
});
While it is occasionally necessary to create a Map with only one key and one value (Collections.singletonMap is used for this), it doesn't look like Map is appropriate for your situation. It makes much more sense to use a custom class with two fields, something like this:
// This is immutable. You may prefer to make fields non-final and add setters.
// You may also want to override equals() and hashCode().
final class StringPair {
private final String key;
private final String value;
StringPair(String key, String value) {
this.key = key;
this.value = value;
}
#Override
public String toString() {
return "[" + key + ", " + value + "]";
}
}
Then you can do:
List<StringPair> list = new ArrayList<>();
list.add(new StringPair("K2", "V2"));
list.add(new StringPair("K1", "V1"));
Collections.sort(list, new Comparator<StringPair>()
#Override
public int compare(StringPair o1, StringPair o2) {
return o1.value.compareTo(o2.value);
}
});
System.out.println(list); // Prints [[K1, V1], [K2, V2]]
ı am trying to merge more than one hashmaps also sum the values of same key,
ı want to explain my problem with toy example as follows
HashMap<String, Integer> m = new HashMap<>();
HashMap<String, Integer> m2 = new HashMap<>();
m.put("apple", 2);
m.put("pear", 3);
m2.put("apple", 9);
m2.put("banana", 6);
ı tried putall
m.putAll(m2);
output is as follows
{banana=6, apple=9, pear=3}
but its result is not true for this problem.
ı want to output as
{banana=6, apple=11, pear=3}
how can ı get this result in java?
If you are using Java 8, you can use the new merge method of Map.
m2.forEach((k, v) -> m.merge(k, v, (v1, v2) -> v1 + v2));
This is a very nice use case for Java 8 streams. You can concatentate the streams of entries and then collect them in a new map:
Map<String, Integer> combinedMap = Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.summingInt(Map.Entry::getValue)));
There are lots of nice things about this solution, including being able to make it parallel, expanding to as many maps as you want and being able to trivial filter the maps if required. It also does not require the orginal maps to be mutable.
This method should do it (in Java 5+)
public static <K> Map<K, Integer> mergeAndAdd(Map<K, Integer>... maps) {
Map<K, Integer> result = new HashMap<>();
for (Map<K, Integer> map : maps) {
for (Map.Entry<K, Integer> entry : map.entrySet()) {
K key = entry.getKey();
Integer current = result.get(key);
result.put(key, current == null ? entry.getValue() : entry.getValue() + current);
}
}
return result;
}
Here's my quick and dirty implementation:
import java.util.HashMap;
import java.util.Map;
public class MapMerger {
public static void main(String[] args) {
HashMap<String, Integer> m = new HashMap<>();
HashMap<String, Integer> m2 = new HashMap<>();
m.put("apple", 2);
m.put("pear", 3);
m2.put("apple", 9);
m2.put("banana", 6);
final Map<String, Integer> result = (new MapMerger()).mergeSumOfMaps(m, m2);
System.out.println(result);
}
public Map<String, Integer> mergeSumOfMaps(Map<String, Integer>... maps) {
final Map<String, Integer> resultMap = new HashMap<>();
for (final Map<String, Integer> map : maps) {
for (final String key : map.keySet()) {
final int value;
if (resultMap.containsKey(key)) {
final int existingValue = resultMap.get(key);
value = map.get(key) + existingValue;
}
else {
value = map.get(key);
}
resultMap.put(key, value);
}
}
return resultMap;
}
}
Output:
{banana=6, apple=11, pear=3}
There are some things you should do (like null checking), and I'm not sure if it's the fastest. Also, this is specific to integers. I attempted to make one using generics of the Number class, but you'd need this method for each type (byte, int, short, longer, etc)
ı improve Lucas Ross's code. in stead of enter map by one by in function ı give all maps one times to function with arraylist of hashmap like that
public HashMap<String, Integer> mergeAndAdd(ArrayList<HashMap<String, Integer>> maplist) {
HashMap<String, Integer> result = new HashMap<>();
for (HashMap<String, Integer> map : maplist) {
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer current = result.get(key);
result.put(key, current == null ? entry.getValue() : entry.getValue() + current);
}
}
return result;
}
}
it works too. thanks to everbody
Assume that you have many HashMaps: Map<String,Integer> map1, map2, map3;
Then you can use Java 8 streams:
Map<String,Integer> combinedMap = Stream.of(map1, map2, map3)
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.summingInt(Map.Entry::getValue)));
If the key exists, add to it's value. If not insert.
Here is a simple example which merges one map into another:
Foo oldVal = map.get(key);
if oldVal == null
{
map2.put(key, newVal);
}
else
{
map2.put(key, newVal + oldVal);
}
Obviously you have to loop over the first map so you can process all of it's entries but that's trivial.
Something like this should work:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String map1_key = entry.getKey();
int map1_value = entry.getValue();
//check:
if(map2.get(map1_key)!=null){
int map2_value = map2.get(map1_key);
//merge:
map3.put(map1_key,map1_value+map2_value);
}else{
map3.put(map1_key,map1_value);
}
}
for (Map.Entry<String, Integer> entry2 : map2.entrySet()) {
String map2_key = entry2.getKey();
int map2_value = entry2.getValue();
//check:
if(map1.get(map2_key)!=null){
int map1_value = map1.get(map2_key);
//merge:
map3.put(map2_key,map1_value+map2_value);
}else{
map3.put(map2_key,map2_value);
}
}
I have the following structure:
import java.util.LinkedHashMap;
...
LinkedHashMap <String, Object>level0 = new LinkedHashMap<String, Object>();
LinkedHashMap <String, Object>level1 = new LinkedHashMap<String, Object>();
LinkedHashMap <String, Object>level2 = new LinkedHashMap<String, Object>();
LinkedHashMap <String, Object>level3 = new LinkedHashMap<String, Object>();
level1.put("level2", level2);
level2.put("level2", level3);
level0.put("level1", level1);
System.out.println(level0);
Output this:
{
level1={
level2={}
}
}
I need to set a value through a "path" (or something), would be something like this:
MapThisObject example = new MapThisObject(level0);
example.putValue("level1.level2", "string", "test");
example.putValue("level1.level2", "int", 1);
example.putValue("level1.level2", "object", new LinkedHashMap());
System.out.println(example.result());
/*output:
{
level1={
level2={
string="test",
int=1,
Object={}
}
}
}
*/
In other words, there is the possibility to put or set values for "multidimensional objects" through a "path" (like Xpath)?
A simple example
public static void set(Map<String, Object> map, String path, Object value) {
String[] parts = path.split("\\.");
for(int i = 0; i < parts.length-1 ; i++) {
String key = parts[i];
Map<String, Object> map2 = (Map<String, Object>) map.get(key);
if (map2 == null) {
map.put(key, map2 = new LinkedHashMap<String, Object>());
}
map = map2;
}
map.put(parts[parts.length - 1], value);
}
set(example, "level1.level2.string", "test");
set(example, "level1.level2.int", 1);
From what you've described, it sounds like all you need is a map containing maps, nested to however many axes you're trying to select from.
The alternative would be to build your own tree structure, of course. Or to express it as an XML DOM tree, which would let you use standard XPath.