There are various ways to achieve this. Here are three.
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
System.out.println("using entrySet and toString");
for (Entry<String, String> entry : map.entrySet()) {
System.out.println(entry);
}
System.out.println();
System.out.println("using entrySet and manual string creation");
for (Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println();
System.out.println("using keySet");
for (String key : map.keySet()) {
System.out.println(key + "=" + map.get(key));
}
System.out.println();
Output
using entrySet and toString
key1=value1
key2=value2
key3=value3
using entrySet and manual string creation
key1=value1
key2=value2
key3=value3
using keySet
key1=value1
key2=value2
key3=value3
Answer from Stefan Dollase on Stack Overflowdictionary - Map to String in Java - Stack Overflow
How to define schema for Map<String, String>
java - How to achieve this Map<String, List<>> structure - Stack Overflow
hash map - How to make Map<String, Object> expendible(?) safely in java - Code Review Stack Exchange
There are various ways to achieve this. Here are three.
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
System.out.println("using entrySet and toString");
for (Entry<String, String> entry : map.entrySet()) {
System.out.println(entry);
}
System.out.println();
System.out.println("using entrySet and manual string creation");
for (Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println();
System.out.println("using keySet");
for (String key : map.keySet()) {
System.out.println(key + "=" + map.get(key));
}
System.out.println();
Output
using entrySet and toString
key1=value1
key2=value2
key3=value3
using entrySet and manual string creation
key1=value1
key2=value2
key3=value3
using keySet
key1=value1
key2=value2
key3=value3
Inside of your loop, you have the key, which you can use to retrieve the value from the Map:
for (String key: mss1.keySet()) {
System.out.println(key + ": " + mss1.get(key));
}
Use Object#toString().
String string = map.toString();
That's after all also what System.out.println(object) does under the hoods. The format for maps is described in AbstractMap#toString().
Returns a string representation of this map. The string representation consists of a list of key-value mappings in the order returned by the map's
entrySetview's iterator, enclosed in braces ("{}"). Adjacent mappings are separated by the characters ", " (comma and space). Each key-value mapping is rendered as the key followed by an equals sign ("=") followed by the associated value. Keys and values are converted to strings as byString.valueOf(Object).
You can also use google-collections (guava) Joiner class if you want to customize the print format
Joiner.on(",").withKeyValueSeparator("=").join(map);
Map<String, List<String>> myMaps = new HashMap<String, List<String>>();
for (DataObject item : myList) {
if (!myMaps.containsKey(item.getKey())) {
myMaps.put(item.getKey(), new ArrayList<String>());
}
myMaps.get(item.getKey()).add(item.getValue());
}
I would use the guavas Multimap implementation. But it is easy doable with the standard JDK aswell.
Example standard JDK:
public static void main(String[] args) {
Scanner s = new Scanner(
"car toyota\n" +
"car bmw\n" +
"car honda\n" +
"fruit apple\n" +
"fruit banana\n" +
"computer acer\n" +
"computer asus\n" +
"computer ibm");
Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
while (s.hasNext()) {
String key = s.next();
if (!map.containsKey(key))
map.put(key, new LinkedList<String>());
map.get(key).add(s.next());
}
System.out.println(map);
}
Example guava:
public static void main(String[] args) {
Scanner s = new Scanner(
"car toyota\n" +
"car bmw\n" +
"car honda\n" +
"fruit apple\n" +
"fruit banana\n" +
"computer acer\n" +
"computer asus\n" +
"computer ibm");
Multimap<String, String> map = LinkedListMultimap.create();
while (s.hasNext())
map.put(s.next(), s.next());
System.out.println(map);
}
Output (both implementations):
{car=[toyota, bmw, honda], fruit=[apple, banana], computer=[acer, asus, ibm]}
if(curr > keys.length -1) return;
// ...
Object currVal = target.get(currKey);
Don't shorten variable names just because you can. It makes the code harder to read and harder to maintain.
if(curr > keys.length -1) return;
Ideally you're always use a "full block if" for statements, as it makes it easier to see the return condition.
if (curr > keys.length - 1) {
return;
}
Also, your formatting seems to be off in a few cases. I'd suggest to always use an automatic formatter in your workflow so that you never need to worry about the formatting of your code ever again.
target.put(currKey, 1);
int and Integer are requiring conversion between each other, that's called "boxing" and might or might not be a problem when doing it implicit.
You ain't got any sort of error handling, if I see this right.
So, let's get to the design. If I understand your example right, what you want to do is to have a list of keys, which represent the path through the tree structure, and the last key keeps a counter how often it was added.
There are several ways to achieve that, and the question is what exactly you're trying to achieve to know which one applies.
For every further implementation, we'll assume the following interface:
public class CounterTreeMap {
public int getValue(String... keys);
public void putValue(String... keys);
}
You notice that we've already simplified how to use this class, the rest are implementation details. Additionally, I'll skip tests on the input parameters for brevity.
The cheap one
The cheapest solution is to "fake" depth by hashing the input array and having a single Map. However, this implementation would be able to store a value for the path "A, B" as well as for "A, B, C".
protected Map<Integer, Integer> values = new HashMap<>();
public int getValue(String... keys) {
int keysHash = Arrays.hashCode(keys);
// TODO We are missing logic here to handle possible hash collisions.
Integer value = values.get(Integer.valueOf(keysHash));
if (value != null) {
return value.intValue();
} else {
return -1; // Or throw exception here.
}
}
The nearly as cheap one
Given the problems from the previous implementation, we can fix at least the possible has collisions by using the full path as key instead.
protected Map<String, Integer> values = new HashMap<>();
public int getValue(String... keys) {
String key = String.join("/", keys);
Integer value = values.get(Integer.valueOf(key));
if (value != null) {
return value.intValue();
} else {
return -1; // Or throw exception here.
}
}
Now that solves the possible hash collisions, but still allows us to store values at each node, basically. That might even be wanted, I don't know, you never specified.
The one with the many maps
As you've done, we can build a hierarchy of Maps, however, I'd base decisions on the type of the encountered leaf.
Recursive implementation:
protected Map<String, Object> tree = new HashMap<>();
public int getValue(String... keys) {
return getValue(tree, keys, 0);
}
protected int getValue(Map<String, Object> branch, String[] keys, int currentKeyIndex) {
String key = keys[currentKeyIndex];
Object value = branch.get(key);
if (value == null) {
return -1; // Or throw an exception here.
} else if (value instanceof Integer) {
return ((Integer)value).intValue();
} else if (value instanceof Map<?, ?>) {
return getValue((Map<String, Object)value, keys, currentKeyIndex + 1)
}
}
Implementation using a loop instead:
protected Map<String, Object> tree = new HashMap<>();
public int getValue(String... keys) {
Map<String, Object> branch = tree;
int currentKeyIndex = 0;
while (branch != null) {
String key = keys[currentKeyIndex];
Object value = branch.get(key);
if (value == null) {
return -1; // Or throw an exception here.
} else if (value instanceof Integer) {
return ((Integer)value).intValue();
} else if (value instanceof Map<?, ?>) {
branch = (Map<String, Object)value;
currentKeyIndex++;
}
}
}
Both implementations lack proper checks, though, but you should get the idea.
Given the use case as example, I'd go with flattening/joining the path into a single string (using "/" as separator) and, when adding a new path, increasing the existing value or setting a new one if it does not exist.
It took a long time just to understand what your program is doing and how it might be useful.
You were concerned about the unchecked casting, and that's a valid concern, but maybe it's not a problem if you make that behaviour clear, for instance, by including a failure scenario in the test case or JavaDoc, in which case you are just stating it's the responsibility of the other code that makes use of this to only pass valid arguments.
Here's a version of the same thing that's hopefully a bit better at describing what it does. With things a bit more plain to read, you can decide if you want your program to behave differently in the failure case -- maybe it should throw a checked exception instead of ClassCastException or maybe you want to actually support these cases?
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
/**
* I am building some statistic counter in Java. Each metric could have flexible
* depth so the implementation should able to explore the depth when value
* insert or update.
*
*/
public class StatisticsCounter {
private Map<String, Object> rootMap = new HashMap<>();
public void increment(String... paths) {
recursiveIncrement(rootMap, paths, 0);
}
private void recursiveIncrement(Map<String, Object> targetMap, String[] pathToTarget,
int pathIndex) {
final int targetDepth = pathToTarget.length - 1;
final String counterName = pathToTarget[pathIndex];
if (pathIndex == targetDepth && targetMap.containsKey(counterName)) {
// assume objects at the target depth are int
targetMap.put(counterName, (int) targetMap.get(counterName) + 1);
} else if (pathIndex == targetDepth) {
// at the target depth, put ints
targetMap.put(counterName, 1);
} else if (targetMap.containsKey(counterName)) {
// assume object is a map, recur into it for the next counter name
recursiveIncrement((Map<String, Object>) targetMap.get(counterName),
pathToTarget, pathIndex + 1);
} else {
// create a new map and recur into it for the next counter name
Map<String, Object> emptyMap = new HashMap<>();
targetMap.put(counterName, emptyMap);
recursiveIncrement(emptyMap, pathToTarget, pathIndex + 1);
}
}
public String toString() {
return rootMap.toString();
}
@Test
public void test() {
StatisticsCounter counters = new StatisticsCounter();
counters.increment("USA", "California", "Los Angeles");
counters.increment("USA", "California", "Los Angeles");
counters.increment("USA", "California", "San Diego");
counters.increment("USA", "California", "Bay Area", "San Francisco",
"Financial District");
counters.increment("USA", "Tennessee", "Memphis");
counters.increment("USA", "Tennessee", "Memphis");
counters.increment("Italy", "Venice");
counters.increment("Italy", "Rome");
counters.increment("Italy", "Venice");
counters.increment("Vatican City");
counters.increment("Vatican City");
try {
counters.increment("USA", "California");
fail("Cannot increment a key that already has subcounters");
} catch (ClassCastException expectedException) {
assertEquals("java.util.HashMap cannot be cast to java.lang.Integer",
expectedException.getMessage());
}
try {
counters.increment("USA", "Tennessee", "Memphis", "Graceland");
fail("Cannot add subcounters to a key that already has a count");
} catch (ClassCastException expectedException) {
assertEquals("java.lang.Integer cannot be cast to java.util.Map",
expectedException.getMessage());
}
System.out.println(counters);
// {USA={Tennessee={Memphis=2}, California={San Diego=1, Bay Area={San
// Francisco={Financial District=1}}, Los Angeles=2}}, Italy={Rome=1, Venice=2},
// Vatican City=2}
}
}
Map<String, Object> map = new HashMap<String, Object>();
...
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof String) {
// Do something with entry.getKey() and entry.getValue()
} else if (entry.getValue() instanceof Class) {
// Do something else with entry.getKey() and entry.getValue()
} else {
throw new IllegalStateException("Expecting either String or Class as entry value");
}
}
Every objects (excluding interfaces) in java extends Object, so your approach is correct.
To know whether an object is a string or other object type, use the instanceof keyword.
Example:
Map<String, Object> objectMap = ....;
for (String key : objectMap.keySet()) {
Object value = objectMap.get(key);
if (value instanceof String) {
System.out.println((String) value);
} else if (value instanceof Class) {
System.out.println("Class: " + ((Class)value).getName());
}
}