I'm looking at the structure of LinkedCaseInsensitiveMap (spring framework 5.0.5.RELEASE). I'm curious why LinkedCaseInsensitiveMap uses both LinkedHashMap and HashMap, and why not just use LinkedHashMap like this?
private final LinkedHashMap<String, V> targetMap;
public V get(Object key) {
if (key instanceof String) {
return this.targetMap.get(convertKey((String) key));
}
return null;
}
private final LinkedHashMap<String, V> targetMap;
private final HashMap<String, String> caseInsensitiveKeys;
In this case targetMap contains real-case string to your object, and caseInsensitiveKeys contains mapping your key in lower case to your real-case key.
It allows to show you real-case keys when you are doing for-each iteration, but at the same time it allows you to have case insensitivity.
So let say, following code:
LinkedCaseInsensitiveMap<Object> map = new LinkedCaseInsensitiveMap<>();
map.put("MyCustomObject", new Object());
will put "MyCustomObject" -> new Object() in targetMap, and "mycustomobject" -> "MyCustomObject" in caseInsensitiveKeys. And now if you try to print all objects from your map it will print it as you added and not changed keys. You can't archive it without second map.
Related
Say I have a HashMap and I want to insert the same value to a list of keys. How can I do this with Java 8 without iterating through all the keys and inserting the value? This is more of a Java Streams question.
Here is the straight forward way of doing it. This is a sample code that I wrote to demonstrate what I wanted to achieve.
public void foo(List<String> keys, Integer value) {
Map<String, Integer> myMap = new HashMap<>();
for (String key : keys) {
myMap.put(key, value);
}
}
Is there a simpler way of doing the above using Java 8 streams? How can I avoid the for loop using Java 8 streams. Thanks!
[Edit-1] A better code snippet below.
public void foo() {
Map<String, Integer> myMap = new HashMap<>();
List<String> keys = getKeysFromAnotherFunction();
Integer value = getValueToBeInserted(); // Difficult to show my actual use case. Imagine that some value is getting computed which has to be inserted for the keys.
for (String key : keys) {
myMap.put(key, value);
}
List<String> keys2 = getNextSetOfKeys();
Integer newValue = getValueToBeInserted();
for (String key : keys2) {
myMap.put(key, newValue);
}
}
Using collector, something like:
Map<String, Integer> myMap = keys.stream()
.collect(Collectors.toMap(key -> key,
val -> value, (a, b) -> b));
I think that your question is about factoring out some piece of code more than converting traditional for loops into stream constructs.
Suppose you have the following generic utility method:
public static <K, V, M extends Map<K, V>> M fillMap(
Supplier<List<K>> keysFactory,
Supplier<V> singleValueFactory,
Supplier<M> mapFactory) {
M map = mapFactory.get();
List<K> keys = keysFactory.get();
V singleValue = singleValueFactory.get();
keys.forEach(k -> map.put(k, singleValue));
return map;
}
Then, you could use the above method as follows:
Map<String, Integer> myMap = fillMap(() -> getKeysFromAnotherFunction(),
() -> getValueToBeInserted(),
HashMap::new); // create HashMap
myMap = fillMap(() -> getNextSetOfKeys(),
() -> getValueToBeInserted(),
() -> myMap); // use previously created map
There are variants for the code above, i.e., the method could receive a Map<K, V> instance instead of a Supplier<Map<K, V>>, or it might even be overloaded to support both variants.
I want to transform keys in a HashMap. The map has lower_underscore keys but an expected map should have camelCase keys. The map may also have null values.
The straightfoward code to do this is here:
Map<String, Object> a = new HashMap<String, Object>() {{
put("foo_bar", 100);
put("fuga_foga", null); // A value may be null. Collectors.toMap can't handle this value.
}};
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
I want to know the method to do this like Guava's Maps.transformValues() or Maps.transformEntries(), but these methods just transforms values.
Collectors.toMap() is also close, but this method throws NullPointerException when a null value exists.
Map<String, Object> collect = a.entrySet().stream().collect(
Collectors.toMap(x -> toCamel(x.getKey()), Map.Entry::getValue));
If you absolutely want to solve this using streams, you could do it like this:
Map<String, Object> b = a.entrySet()
.stream()
.collect(HashMap::new,
(m, e) -> m.put(toCamel(e.getKey()), e.getValue()),
HashMap::putAll);
But I find the "conventional" way shown in your question easier to read:
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
This is intended as a comment, but got too long for that.
Wanting something like Guava's Maps.transformValues() or Maps.transformEntries() doesn't make too much sense I think.
Those methods return a view of the original map and when you get some
value using a key then the value is transformed by some function that you specified.
(I could be wrong here because I'm not familiar with Guava but I'm making these assumptions based on documentation)
If you wanted to do "transform" the keys then you could do it by writing a wapper for the map like so:
public class KeyTransformingMap<K, V> implements Map {
private Map<K, V> original;
private Function<K, K> reverseTransformer;
public V get(Object transformedKey) {
K originalKey = reverseTransformer.apply((K) transformedKey);
return original.get(originalKey);
}
// delegate all other Map methods directly to original map (or throw UnsupportedOperationException)
}
In your case where you have a map with snake case keys but want camel case keys,
the reverseTransformer function would take in a camel case string and return a snake case string.
I.e reverseTransformer.apply("snakeCase") returns "snake_case" which you can then use as a key for the original map.
Having said all that I think that the straightforward code you suggested is the best option.
I want to determine if a given String startsWith any key in a Map.
The simple solution is to iterate through entire the keySet.
private static Map<String, String> someMap;
private static void method(String line) {
for (String key : someMap.keySet()) {
if (line.startsWith(key)) {
// do something with someMap.get(key);
}
}
}
My question is: Is there is a better data structure to handle this problem?
This can't be done directly with an HashMap: the problem is that HashMap uses an hash calculated on the key to manage its position inside the collection. So there is no way to search for a String key which starts with a specific substring since there is no correlation between two similar String values and their hashes.
But nothing is lost, if you switch to a TreeMap<String,String> the problem can be solved easily. A TreeMap is still an associative container but it stores entries by using a red-black tree in a sorted order.
This means that elements inside a TreeMap are always sorted. In addition to this it gives you some functionalities like:
Map.Entry<K,V> ceilingEntry(K key): Returns a key-value mapping associated with the least key greater than or equal to the given key, or null if there is no such key.
Map.Entry<K,V> floorEntry(K key): Returns a key-value mapping associated with the greatest key less than or equal to the given key, or null if there is no such key.
Now, not only you can search for a specific key by using a substring of its value, but you also do it in an efficient way. Mind that this works thanks to the implementation of compareTo of String class.
So your problem becomes trivial:
TreeMap<String, Object> map = new TreeMap<String, Object>();
map.put("baz", new Object());
map.put("foo", new Object());
map.put("fooz", new Object());
map.put("fo", new Object());
Map.Entry<String, Object> test = map.ceilingEntry("fo");
bool containsSubStringKey = test != null && test.getKey().startsWith("fo");
TreeMap<String, Object> map = new TreeMap<String, Object>();
map.put("baz", new Object());
map.put("foo", new Object());
map.put("fooz", new Object());
map.put("foor", new Object());
NavigableMap tempMap = list.subMap("foo", true, "fop", false);
//This will return a map of keys that start with "foo". true means inclusive and //false exclusive. "fop" is the first key that does not start with "foo"
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());
}
Need a dynamic data structure, which may be similar to a MAP(Java.util.Map), wchich is capable of storing, String and Object. And that Object may again need to store another map, which can store, String and Object.
I suspect that the requester is looking for something like the below:
class MultilevelMap<K,V> extends HashMap<List<K>,V> {
#SafeVarargs
public final put(V value, K keys...) {
put(makeKey(keys), value);
}
#SafeVarargs
public final V get(K keys...) {
return get(makeKey(keys));
}
// The remainder of this class is left as a tedious exercise for the reader
private List<K> makeKey(K[] keys) {
List<K> key = new ArrayList<K>(keys.size);
for(K k: keys) {
key.add(k);
}
return key;
}
}
A Trie, as far as I understand it is similar, but opposite. It presents an interface of Map<S,V>, but internally is implemented as a variable-depth Map<K,Map<K, ... V>> where K are consecutive affixes of S, such that if you concatenate all of the Ks between the top of the tree and V, you get the S you used as the key. The above presents an interface of (very approximately) Map<K,K, ... , V>, but internally is Map<List<K>, V>.
You can nest maps (and other containers) to arbitrary depth. This puts a Map in another Map in another ... to a total depth of 10:
private Map<String, Object> nest(int levelsLeft, Map<String, Object> parent) {
if (levelsLeft > 0) {
parent.put("key" + levelsLeft,
nest(levelsLeft - 1, new HashMap<String, Object>()));
}
return parent;
}
// from somewhere else
Map<String, Object> nested = nest(10, new Map<String, Object>());
((Map<String, Object>)nested.get("key10")).get("key9"); // goes all the way down to "key1"
Note that the price for declaring a Map<String, Object> is that, whenever you access something via get(), you need to cast it to whatever it actually is to be able to use it as something more specific than an Object.
Sounds like you either need a Multimap or a Trie.