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.
Related
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.
I have a Java enum class with multiple enum constants defined with multiple fields per constant.
I want to be able to convert an enum constant into a key-value pair list containing the field name and values.
The difficulty comes from creating a setup that will allow for users to only need to add a new enum constant or even add new fields to each constant and not have the code consuming this data know about which fields will exist.
public enum Stuff
{
ORDINAL_ZERO( "Zero's identifier", "Zero's value A", "Zero's value B" ),
ORDINAL_ONE( "One's identifier", "One's value A", "One's value B" );
private String identifer;
private String valueA;
private String valueB;
Stuff( String identifier, String valueA, String valueB )
{
this.identifier = identifier;
this.valueA = valueA;
this.valueB = valueB;
}
public static Stuff getStuffForIdentifier( String identifier )
{
for ( Stuff stuff : values() )
{
if ( stuff.getIdentifier().equalsIgnoreCase( identifier ) )
return stuff;
}
}
public static Map<String, String> getStuffForIdentifierAsMap( String identifier )
{
TODO: return a key-value pair map of a single enum constant where I can iterate through the keys and get their values without knowing what all fields are defined ahead of time i.e. if we asked for a map of the 0 ordinal constant's fields:
Map<String, String> map = new HashMap<String, String>();
map.put("identifier", "Zero's identifer");
map.put("valueA", "Zero's value A");
map.put("valueB", "Zero's value B");
return map;
}
}
You can iterate on Stuff.values() in order to find the one with the right identifier
public static Map<String, Object> getStuffForIdentifierAsMap(String identifier)
throws IllegalAccessException {
for (Stuff stuff : Stuff.values()) {
if(stuff.getIdentifier().equals(identifier)) {
...
}
Note : calling Stuff.values() each time you call the method is evil because it will create a new copy of the array each time. On production code, you would avoid that with some cache.
Then, you can iterate on the fields of the class like this :
private static Map<String, Object> dumpValues(Stuff stuff) throws IllegalAccessException {
Map<String, Object> map = new HashMap<String, Object>();
for (Field field : stuff.getClass().getDeclaredFields()) {
map.put(field.getName(), field.get(stuff));
}
return map;
}
Test :
public static void main(String[] args) throws Exception {
Map<String, Object> props = getStuffForIdentifierAsMap("Zero's identifier");
for (Map.Entry<String, Object> entry : props.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
Output :
ORDINAL_ONE : ORDINAL_ONE
identifier : Zero's identifier
valueB : Zero's value B
valueA : Zero's value A
ORDINAL_ZERO : ORDINAL_ZERO
$VALUES : [Lcom.denodev.Stuff;#60e53b93
Note : you get some extra properties from the class itself.
I have a task given me on the Java programming course. According to this task I have to create a method that returns a number of the HashMap elements with identical keys. But the problem is that the iterator goes through elements with different keys only, so anyway, the method returns 1. What`s the way out?
package com.javarush.test.level08.lesson08.task03;
import java.util.*;
/* Одинаковые имя и фамилия
Создать словарь (Map<String, String>) занести в него десять записей по принципу «Фамилия» - «Имя».
Проверить сколько людей имеют совпадающие с заданным имя или фамилию.
*/
public class Solution
{
public static void main(String[] args)
{
HashMap<String, String> friends = createMap();
getCountTheSameLastName(friends, "гладких");
getCountTheSameFirstName(friends, "Виталий");
}
public static HashMap<String, String> createMap()
{
HashMap<String, String> name = new HashMap<String, String>();
name.put("гладких", "Иван");
name.put("пересыпкин", "Артем");
name.put("пересыпкин", "Владислав");
name.put("халитов", "Виталий");
name.put("чернышев", "Виталий");
name.put("ивинских", "Виталий");
name.put("ивинских", "Альфред");
name.put("осипова", "Мария");
name.put("ивинских", "Павел");
name.put("гейтс", "Билл");
return name;
}
public static int getCountTheSameFirstName(HashMap<String, String> map, String name)
{
int MatchesFirstnameCount = 0;
for (HashMap.Entry<String, String> pair : map.entrySet()) {
String s = pair.getValue();
if (s.equals(name) ) {
MatchesFirstnameCount++;
}
}
return MatchesFirstnameCount;
}
public static int getCountTheSameLastName(HashMap<String, String> map, String lastName)
{
int MatchesSecondnameCount = 0;
for (HashMap.Entry<String, String> pair : map.entrySet()) {
if (pair.getKey().equals(lastName))
MatchesSecondnameCount++;
}
return MatchesSecondnameCount;
}
}
This sounds like a trick question. Each key in a HashMap can only have one value associated with it. Each key in a HashMap must be unique.
When adding a duplicate key the old value is replaced (see HashMap.put)
This sounds like a tricky question. The key in HashMap must be unique. If you want to store multiple elements on the same key, you can save a collection like:
Map<Integer, List<Object>> map = new HashMap<>();
Then, the following method would return a List.
map.get(0)
So i have constructor that looks so:
public Group(Entry<String, List<String>> rawGroup) {
permission=rawGroup.getKey();
List<String> params = rawGroup.getValue();
limits = Integer.parseInt(params.get(0));
int a = Integer.parseInt(params.get(1));
int b = Integer.parseInt(params.get(2));
s1 = Math.min(a, b);
s2 = Math.max(a, b);
}
And "List params = rawGroup.getValue();" makes that:
java.lang.ClassException: java.lang.String cannot be cast to java.util.List
I can't understand why this is happening, getValue() can't return String because it's not String
UPDATE: Entry is a part of EntrySet that returns Map
UPDATE2:
so here's code that uses that constructor -
Map<String, List<String>> rawGroups = (Map) holder.config.getConfigurationSection(HEADING).getValues(true);
for (Entry<String, List<String>> rawGroup : rawGroups.entrySet()) {
groups.add(new Group(rawGroup));
}
The key line is here:
Map<String, List<String>> rawGroups = (Map) holder.config.getConfigurationSection(HEADING).getValues(true);
You've assumed that what holder.config.getConfigurationSection(HEADING).getValues(true) returns is a Map<String, List<String>>, and told the compiler to make that assumption as well. Clearly that's not the case, because when you try to use it that way, it fails.
You need to find out what holder.config.getConfigurationSection(HEADING).getValues(true) is really returning, and use that.
Here's a simple demonstration of the same basic concept (live copy):
public static void main (String[] args)
{
Map<String, List<String>> m = (Map)getMap();
try {
System.out.println(m.get("entry").get(0)); // Fails here
}
catch (Exception e) {
System.out.println("Failed: " + e.getMessage());
e.printStackTrace(System.out);
}
}
static Object getMap() {
Map m = new HashMap();
List l = new LinkedList();
l.add(42);
m.put("entry", l);
return m;
}
This is a very generic question. I have a hashmap and I want to print it in a tabular format dynamically without knowing the size of the contents beforehand. The table should be well spaced just like we see in a Db query result. Are there any libraries/utilities which directly helps in this type of conversion? Or does JAVA have some intrinsic functions which I could make use of?
The code which I have written is a very naive one, and does not cater to dynamic length of the strings. I need the rows to be aligned also.
StringWriter returnString = new StringWriter();
Map<String,HashMap<String, String>> hashMap = new HashMap<String, HashMap<String, String>>();
for (Entry e : hashMap.entrySet()) {
HashMap<String, Number> hm = (HashMap<String, Number>) e.getValue();
String key = (String) e.getKey();
returnString.append("|\t").append(key).append("\t|");
for (Entry en : hm.entrySet()){
returnString.append("|\t").append((String) en.getValue()).append("\t|");
}
returnString.append("\r\n");
}
return returnString.toString();
The output should be like this irrespective of the strings length
s1 | s3 | s4
askdkc | asdkask | jksjndan
It looks like you already have the iteration figured out and are just working on the formatting. You could put it into a TableModel, and let the JTable handle the tabular formatting.
You could select fixed column widths or iterate once over the entries to find the maximum length of each column, then again to print them with appropriate padding.
Another option would be to extend HashMap so that it records the longest key and value as entries are added:
package com.example;
public class MyHashMap<K, V> extends java.util.HashMap<K, V> {
private int maxKeyLength = 0;
private int maxValueLength = 0;
#Override
public V put(K key, V value) {
maxKeyLength = Math.max(maxKeyLength, key.toString().length());
maxValueLength = Math.max(maxValueLength, value.toString().length());
return value;
};
public int getMaxKeyLength() {
return maxKeyLength;
}
public int getMaxValueLength() {
return maxValueLength;
}
}
Note this ignores the obvious case where you also remove items--depending on your usage pattern, you'll have to do a little or a lot more work if you also want to shrink the columns when removing entries with the longest keys/values.
I have written a small code that will print the HashMap similar (not exactly) to how query results are printed (like sqlplus). Just sharing here so that it might help some one.
List<Map<String, Object>> resultSet = jdbcTemplate.queryForList(selectQuery);
Map<String, Integer> maxColSizeMap = new HashMap<String, Integer>();
boolean initMaxColSizeMap = true;
for (Map<String, Object> map : resultSet) {
for (String key : map.keySet()) {
if (initMaxColSizeMap) {
String ColValue = (map.get(key) == null ) ? "" : map.get(key).toString();
Integer whoIsBig = Math.max(ColValue.length(), key.length());
maxColSizeMap.put(key, whoIsBig);
} else {
String ColValue = (map.get(key) == null ) ? "" : map.get(key).toString();
Integer whoIsBig = Math.max(ColValue.length(), key.length());
whoIsBig = Math.max(maxColSizeMap.get(key), whoIsBig);
maxColSizeMap.put(key, whoIsBig);
}
}
initMaxColSizeMap = false;
}
// Column HEADER
for (Map<String, Object> map : resultSet) {
System.out.println("");
StringBuilder colName = new StringBuilder();
StringBuilder underLine = new StringBuilder();
for (String key : map.keySet()) {
colName.append(StringUtils.rightPad(key, maxColSizeMap.get(key)));
colName.append(" ");
underLine.append(StringUtils.repeat('-', maxColSizeMap.get(key)));
underLine.append(" ");
}
// Do one time only
System.out.println(colName.toString());
System.out.println(underLine.toString());
break;
}
// Print the rows
for (Map<String, Object> map : resultSet) {
StringBuilder row = new StringBuilder();
for (String key : map.keySet()) {
String str = map.get(key) == null ? "" : map.get(key).toString();
row.append(StringUtils.rightPad(str, maxColSizeMap.get(key) + 1));
}
System.out.println(row);
}
System.out.println("");
Note: org.apache.commons.lang3.StringUtils is used for padding.
This one is not an exact answer to the question, so tweak the code as required.
You can use a for-each loop and just print it out, correct? No need to have the size..
Whatever is meant by "table" though?
How print out the contents of a HashMap<String, String> in ascending order based on its values?
You may want to get all the keys of the map, iterate the keys and print the details e.g.
Map<String, String> valueMap = new HashMap<String, String>();
.....
Set<String> keys = valueMap.keySet();
Iterator<String> iter = keys.iterator();
while(iter.haxNext()){
String key = iter.next();
System.out.println("\t"+key+"|\t"+valueMap.get(key));
}
EDIT: If you want specific width then consider using Apache StringUtils as below:
int MAXWIDTH = 20; //<- set this with the max width of the column
Map<String, String> valueMap = new HashMap<String, String>();
.....
Set<String> keys = valueMap.keySet();
Iterator<String> iter = keys.iterator();
while(iter.haxNext()){
String key = iter.next();
System.out.println(StringUtils.rightPad(key, MAXWIDTH)+ "|"+
StringUtils.rightPad(valueMap.get(key), MAXWIDTH));
}