This question already has answers here:
How to have a key with multiple values in a map?
(6 answers)
Closed 2 years ago.
Is there a Java Map class that satisfies these functionalities:
It's a generic class with the form UnknownMap<Key, Object>, I plan to use it in a node based application where each node in stored in this map and corresponds to a float value object like UnknownMap<Float, Node>.
It can store objects that can map to the same float value, so for example:
UnknownMap<Float, String> map = new UnknownMap<Float, String>();
map.put(1, "a");
map.put(2, "b");
map.put(3, "c");
map.put(1, "d");
System.out.println(map);
Would print out: {1="a", 1="d", 2="b", 3="c"} as opposed to: {1="d", 2="b", 3="c"}.
It has a method called subMap that returns a map with all the nodes within a key range, similar to TreeMap.subMap.
It must perform a binary search on the float key values to retrieve the correct object, as opposed to iterating through the whole collection. It needs to be fast since there will likely be thousands if not millions of nodes.
Does such a class exist, or will I have to write one from scratch?
Thanks.
Use Map.computeIfAbsent
Try it like this:
The first time the key is encountered, it creates a list and adds the string to it. The next time, it just adds the string to the list. This is one technique for mapping multiple values to the same key.
Map<Float,List<String>> map = new HashMap<>();
map.computeIfAbsent(10f, k->new ArrayList<>()).add("a");
System.out.println(map);
map.computeIfAbsent(10f, k->new ArrayList<>()).add("b");
System.out.println(map);
map.computeIfAbsent(30f, k->new ArrayList<>()).add("c");
System.out.println(map);
map.computeIfAbsent(30f, k->new ArrayList<>()).add("d");
System.out.println(map);
map.computeIfAbsent(10f, k->new ArrayList<>()).add("e");
System.out.println(map);
Prints the following after each addition to see the map changing.
{10.0=[a]}
{10.0=[a, b]}
{30.0=[c], 10.0=[a, b]}
{30.0=[c, d], 10.0=[a, b]}
{30.0=[c, d], 10.0=[a, b, e]}
You could also concatenate new strings to the existing string using a similar technique with Map.compute(). Because the variable in a lambda must be effectively final, this is most easily done by isolating the map in a method.
Map<Float, String> map2 = new HashMap<>();
add(10f, "a", map2);
add(10f, "b", map2);
add(30f, "c", map2);
add(30f, "d", map2);
add(10f, "e", map2);
map2.entrySet().forEach(System.out::println);
Prints
30.0=c, d
10.0=a, b, e
The method
public static void add(float key, String value, Map<Float, String> map) {
map.compute(key,
(k, v) -> v == null ? value : v + ", " + value);
}
TreeMap will do the job. Make the 'value' part a List (or possibly Set) to deal with the 'one key can map to multiple values' clause:
public void put(float k, String v) {
map.computeIfAbsent(k, k_ -> new ArrayList<String>()).add(v);
}
for example.
Related
I have two HashMap in Java.
First one contains a key and its value. Where second contains an evaluation index (order) of that keys. I want to sort the first map by referring to the second map.
First HashMap <key, value>
<"C","ccc">
<"D","ddd">
<"A","aaa">
<"B","bbb">
Second HashMap <key, value>
<"0","A">
<"1","B">
<"2","C">
<"3","D">
Result should be
<"A","aaa">
<"B","bbb">
<"C","ccc">
<"D","ddd">
Looping this two map and checking comparing keys is simple but not efficient. Any efficient idea?
You can use Java Stream API. First, sort the second map entrySet by key then map second map's value as key and get first map's value by value of the second map and then collect as LinkedHashMap using Collectors.toMap
secondMap.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.collect(Collectors.toMap(Map.Entry::getValue, k -> firstMap.get(k.getValue()),
(x, y) -> y, LinkedHashMap::new));
If you use LinkedHashMap/TreeMap ordered by key for the second map then you don't need to sort the keys. See demo here
First of all HashMap is not ordered collections. The key-value pairs in HashMap are ordered based on the value of hashCode() result of keys. So I would say you can't keep sorted values in HashMap.
Instead, you can use LinkedHashMap - it will be ordered with order of insertion.
And for your solution, i would do:
HashMap<String, String> firstMap = ...
HashMap<String, String> secondMap = ...
LinkedHashMap<String, String> orderedMap = new LinkedHashMap<>();
for (int i = 0; i < secondMap.size(); ++i) {
String key = secondMap.get(String.valueOf(i));
orderedMap.put(key, firstMap.get(key));
}
Did not run this code, but it should work.
Alternatively, you can use TreeMap that is ordered based on Comparable interface of the keys.
And to answer what is better to use - TreeMap or LinkedHashMap - depends on how actually you are using this map later. In most cases LinkedHashMap is enough, although if you need to, for example, get the closest greater element to some key, then TreeMap is a choice.
There are some comparison between HashMap and TreeMap
What is the difference between a HashMap and a TreeMap?
Traverse the values of secondMap and collect the related key, value set in the map:
Map<String, String> result = secondMap.values().stream()
.collect(Collectors.toMap(Function.identity(), firstMap::get, (x,y)-> x, LinkedHashMap::new));
Try this:
Map<String, String> firstMap = new HashMap<>();
firstMap.put("C", "ccc");
firstMap.put("D", "ddd");
firstMap.put("A", "aaa");
firstMap.put("B", "bbb");
Map<String, String> secondMap = new HashMap<>();
secondMap.put("0", "A");
secondMap.put("1", "B");
secondMap.put("2", "C");
secondMap.put("3", "D");
Map<String, String> result = secondMap.values().stream()
.collect(Collectors.toMap(Function.identity(), firstMap::get, (x,y)-> x, LinkedHashMap::new));
System.out.println(result);
I'm trying to remove a particular key from Map and reposition all keys index wise. For example: My map is like Map<Integer,String>
0,"A"
1,"B"
2,"C"
3,"D"
if i remove key 1 then output should be
0,"A"
1,"C"
2,"D"
How do i keep keys in index wise (0 to size-1) and assign next value to previous key after remove?
This is required because before insertion in map i need to check whether key exists or not. This scenario required for my recycler view adapter to hold unique positions to avoid reloading of items if exists in map
In your situation, if u want the index to be in that behavior. Map is not the answer. use List - can use ArrayList[insertion is often] or LinkedList[fast when reading].
Sample:
List<String> items = new ArrayList<>();
items.add("A");
items.add("B");
items.add("C");
items.add("D");
So if you remove index 1 or B;
items.remove(1);
The index of C will automatically be 1. Just like you wanted above.
Maps inherently have no order. There is no way to change the keys without taking all the values out of the Map and reinserting them with new keys.
It sounds like you may be better off using a List instead of a Map.
ArrayList<String> strings = new ArrayList(Arrays.asList("A", "B", "C"));
// 0 => "A", 1 => "B", 2 => "C"
strings.remove(1);
// 0 => "A", 1 => "C"
You can't order the keys on deletion by updating key values. In MAP only way to update keys is removing the key and and re-insert it with new value. That is tricky and expensive. You can go with ArrayList for the desired result as suggested.
The TreeMap<K,V> in the jdk java.util package can meet your needs. Whatever key you delete , the sort will never change.
If you want to define yourself sort method, just use its custom comparator to over-write construction method TreeMap(Comparator<? super K> comparator) to achieve key sorting.
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<Integer, String>();
map.put(0, "A");
map.put(1, "B");
map.put(2, "C");
map.put(3, "D");
// whatever key you delete , the sort will not change...
for (Map.Entry<Integer, String> entry : resultMap.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
you choose a very bad way to implement this in adapter
but you can solve it with below code:
final Map<Integer, String> myStupidMap = new HashMap<>();
myStupidMap.put(1, "A");
myStupidMap.put(2, "B");
myStupidMap.put(3, "C");
myStupidMap.put(4, "D");
final int[] count = {0};
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
myStupidMap.forEach(new BiConsumer<Integer, String>() {
#Override
public void accept(Integer integer, String s) {
if (count[0] == 1){
myStupidMap.remove(integer);
}
count[0]++;
}
});
}
i hope useful for you But my suggestion is not to use the map.
i am having map1 as <k1,v1> and i have to create map2 with map1 as a value like map2=<k3,map1>.
But keys k1 ans k3 are having duplicate and we have to retain duplictes.
Example:
map1={(1,a),(1,b),(2,c)}
map2={5={1,a},5={1,b},6={2,c}}
How to achieve this using hashmaps or maps(without using guava's multimap concept)
As HashMap doesn't allow to store duplicate keys, you might want to consider changing your code a bit and create HashMap<Key,ArrayList<HashMap>>. Maps with the same key would be stored under the same Key in the ArrayList of your HashMaps. "ArrayList of HashMaps" would be the value of parent HashMap. There is a simple example how I see you could achieve something similar to containing more values to duplicate keys (I used hardcoded key values to make it a bit simpler to read, I also added some explanations in the comments to the code):
import java.util.*;
public class A {
public static void main(String[] args) {
Map<Integer, ArrayList<Character>> map1 = new HashMap<>(); // Map containing few Characters under one key
map1.put(1, new ArrayList<Character>());
map1.get(1).add('a');
map1.get(1).add('b');
map1.put(2, new ArrayList<Character>());
map1.get(2).add('c');
System.out.println("map1: " + map1); // prints: map1: {1=[a, b], 2=[c]}
Map<Integer, ArrayList<HashMap<Integer, Character>>> map2 = new HashMap<>(); // Map storing as keys 5 and 6, values are maps from map1
map2.put(5, new ArrayList<HashMap<Integer, Character>>());
map2.put(6, new ArrayList<HashMap<Integer, Character>>());
for(Map.Entry<Integer, ArrayList<Character>> entry : map1.entrySet()) { // For each Integer-ArrayList pair from map1...
if(entry.getKey().equals(1)) { // Check if key Integer is equal to 1, if it is...
for(Character character : entry.getValue()) { // Create Maps containg pairs of Integer as key and each Character as value...
HashMap<Integer, Character> innerMap = new HashMap<>();
innerMap.put(entry.getKey(), character);
map2.get(5).add((new HashMap<Integer,Character>(innerMap)));
}
}
if(entry.getKey().equals(2)) { // Check if key Integer is equal to 1, if it is...
for(Character character : entry.getValue()) { // Create Maps containg pairs of Integer as key and each Character as value...
HashMap<Integer, Character> innerMap = new HashMap<>();
innerMap.put(entry.getKey(), character);
map2.get(6).add((new HashMap<Integer,Character>(innerMap)));
}
}
}
System.out.println("map2: " + map2); // prints: map2: {5=[{1=a}, {1=b}], 6=[{2=c}]}
}
}
Output you get:
map1: {1=[a, b], 2=[c]}
map2: {5=[{1=a}, {1=b}], 6=[{2=c}]}
You can use result Map and combination of getKey() and getValue() methods to create Maps like 5={1,a} and 5=[{1=b} on-the-go (but be aware that you cannot store them in the same Map).
Maps don't allow more than one mapping for any given key.
What you can do, though, is to have a Map<K1, List<V1>> for map1, which maps keys to lists of values.
To add an entry, instead of using Map.put, use Map.computeIfAbsent:
map1.computeIfAbsent(1, k -> new ArrayList<>()).add("a");
map1.computeIfAbsent(1, k -> new ArrayList<>()).add("b");
map1.computeIfAbsent(2, k -> new ArrayList<>()).add("c");
This has the following structure:
map1: {1=[a, b], 2=[c]}
Now, for map2, it's not clear if you'd need to take the same approach or if you'd be done using Map.put.
I have a map of map on which I want to stream,
Map<LocalDate, Map<Integer, List<String>>> mainMap;
and remove the values which match another map,
Map<LocalDate, List<String>> childMap;
Ex. mainMap contains
{2016-02-23={120=[1,2,3], 121=[1,2,3], 122=[1,2,3], 123=[1,2,3]}}
and childMap contains
{2016-02-23=[120,123]}
I want to remove these values from mainMap.
Expected Output =>
{2016-02-23={121=[1,2,3], 122=[1,2,3]}}
How to do it in Java 8?
You could do it in-place with the following:
mainMap.forEach((k, v) ->
v.keySet().removeIf(s ->
Optional.ofNullable(childMap.get(k)).map(o -> o.contains(s.toString())).orElse(false)
)
);
This iterates through the mainMap. Then, concerning the value of that map, which is a Map<Integer, List<String>>, it removes all the integer keys (converted to a String) where the childMap for the current date points to a list containing that key. Note that the integer key is not removed if the child map does not contain a list for the current date.
A full sample code is the following, which prints your desired output:
public static void main(String[] args) {
Map<LocalDate, Map<Integer, List<String>>> mainMap = new HashMap<>();
Map<Integer, List<String>> map = new HashMap<>();
map.put(120, Arrays.asList("1", "2", "3"));
map.put(121, Arrays.asList("1", "2", "3"));
map.put(122, Arrays.asList("1", "2", "3"));
map.put(123, Arrays.asList("1", "2", "3"));
mainMap.put(LocalDate.of(2016, 2, 23), map);
Map<LocalDate, List<String>> childMap = new HashMap<>();
childMap.put(LocalDate.of(2016, 2, 23), Arrays.asList("120", "123"));
mainMap.forEach((k, v) ->
v.keySet().removeIf(s ->
Optional.ofNullable(childMap.get(k)).map(o -> o.contains(s.toString())).orElse(false)
)
);
System.out.println(mainMap);
}
An alternative to Tunaki’s answer is
mainMap.forEach((k, v) -> childMap.getOrDefault(k, emptyList())
.stream().map(Integer::valueOf).forEach(v::remove));
the difference is that Tunaki’s solution will iterate over the contents of the sub-maps contained in mainMap and perform a lookup on childMap for each, whereas the solution above will iterate over the lists found in childMap and perform a lookup the submaps of mainMap. This is preferable when the sizes of both, the submaps and the lists, grow.
One thing that hasn’t been addressed, is whether mappings of the mainMap should get removed if their submaps get emptied due to the operation. Supporting removal of outer mappings is not possible in one pass when iterating over the map that is going to be modified (unless you resort to an old loop using an Iterator).
A solution is to iterate over the childMap then:
childMap.forEach((k,v) -> mainMap.computeIfPresent(k, (d,m) -> {
v.stream().map(Integer::valueOf).forEach(m::remove);
return m.isEmpty()? null: m;
}));
Note that most complication arises from the type mismatch between your two maps, so a conversion between the childMap’s Strings to the mainMap’s Integers has to be performed. If they had the same type, e.g. if childMap was a Map<LocalDate, List<Integer>> or mainMap was Map<LocalDate, Map<String, List<String>>>, the solution not removing empty maps was as simple as
mainMap.forEach((k, v) -> v.keySet().removeAll(childMap.getOrDefault(k, emptyList())));
and the solution which will also remove empty mappings from the outer map:
childMap.forEach((k,v) -> mainMap.computeIfPresent(k, (d,m) -> {
m.keySet().removeAll(v);
return m.isEmpty()? null: m;
}));
The code above may remove mappings to an initially empty map. If you want to remove mappings only if the map has been emptied during this operation, i.e. not touch those which were empty to begin with, the code becomes even simpler:
childMap.forEach((k,v) ->
mainMap.computeIfPresent(k, (d,m) -> m.keySet().removeAll(v) && m.isEmpty()? null: m));
I want to sort the tree map based on the key where key is a variable,so sorting should be based on variable value, How can we achieve this? I want use in built sort method rathar implementing it through code, any reply with example is of great help.
TreeMap (which implements SortedMap) stores automatically the keys in the correct order:
Map<Integer, String> map = new TreeMap<Integer, String>();
map.put(1, "one");
map.put(3, "three");
map.put(2, "two");
// prints one two three
for(Integer key : map.keySet()) {
System.out.println(map.get(key));
}
As Key-Type (in that case Integer) you can use any class which implements Comparable (or you can provide a Comparator when creating the TreeMap)
Edit: Okay, here is a suggestion how to re-map your map.
Map<Integer, String> oldMap; // get oldMap from somewhere
// Prepare remapping
Map<Integer, String> newMap = new TreeMap<Integer, String>();
Map<Integer, Integer> keyMap = new HashMap<Integer, Integer>();
// Store a new key for each old key
keyMap.put(oldKey, newKey);
// fill the newMap
for(Integer oldKey : keyMap.keySet()) {
newMap.put(keyMap.get(oldKey), oldMap.get(oldKey));
}
oldMap = newMap; // if needed
A treemap is a Red-black tree, which is a balanced binary search tree. In other words, the tree is already sorted (or rather, arranged as per the binary search tree rules) with its height balanced so that tree operations have a O(lg n) complexity. However, I think what you want is to print all the keys in sorted order. This is as simple as implementing an inorder traversal on the treemap, or you could use the keySet() method to get a Set and iterate over the values.
e.g. of inorder traversal
void inorderTraversal( Node root ){
if( root == null ) return;
inorderTraversal( root.getLeft() );
root.printValue();
inorderTraversal( root.getRight() );
}
EDIT:
Okay, I'm pretty sure this is what you want. You want to sort by values:
Map<String, Integer> map = new TreeMap<String, Integer>();
map.put("one", 8);
map.put("two", 10);
map.put("three", 9);
map.put("hundred", 1);
System.out.println(map.values());
Output:
[1, 8, 9, 10]
So this works even for sorting string values:
Map<Integer, String> map = new TreeMap<Integer, String>();
map.put(8, "one");
map.put(10, "two");
map.put(9, "three");
map.put(1, "hundred");
System.out.println(map.values());
Output:
[hundred, one, three, two]
Also, sachin take note that having "variable keys" and variable values are completely different things.
TreeMap implements the SortedMap interface and is sorted by its key without you having to do anything:
The map is sorted according to the natural ordering of its keys, or by
a Comparator provided at map creation time, depending on which
constructor is used.