We're currently using Guava for its immutable collections but I was surprised to find that their maps don't have methods to easily create new maps with minor modifications. And on top of that, their builder doesn't allow assigning new values to keys, or removing keys.
So if I wanted to modify just one value, here's what I would like to be able to do:
ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
ImmutableMap<Guid, ImmutableMap<String, Integer>> modifiedMap =
originalMap.cloneAndPut(key, value);
Here's what it looks like Guava are expecting me to do:
ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
Map<Guid, ImmutableMap<String, Integer>> mutableCopy = new LinkedHashMap<>(originalMap);
mutableCopy.put(key, value);
originalMap = ImmutableMap.copyOf(mutableCopy);
/* put the map back */
By doing this I get a new copy of the map with the modification I want. The original copy is untouched and I will be using an atomic reference to put the thing back so the whole setup is thread-safe.
It's just slow.
There is a lot of wasted copying going on under the covers here. Suppose there's 1,024 buckets in the map. That's 1,023 buckets which you're unnecessarily creating all over again (twice each, too), when you could have used those immutable buckets as-is and cloned only one of them.
So I guess:
Is there a Guava utility method buried somewhere for this sort of thing? (It isn't in Maps or on the ImmutableMap.Builder.)
Is there any other Java library which gets this sort of thing right? I am under the impression that Clojure has this sort of thing under the hood but we're not ready to switch languages just yet...
A bit unexpected the map of Functional Java is mutable like Guava's. The list is immutable though as I would expect.
Googling for "persistent collection java" brought up: pcollections. There are's a Map implementation.
Before actually using any other implementation I would benchmark the memory and performance characteristics against Guava. I would not be surprised if it's still better.
One may go with the following, to just duplicate the map once instead of twice:
ImmutableMap<String, Object> originalMap = /* get the map */;
Map<String, Object> modifiedMap = ImmutableMap.<String, Object>builder().putAll( originalMap )
.put( "new key", new Object() )
.build();
However, removing a value looks quite less beautiful with an iteration over the existing map, e.g. like this:
ImmutableMap<String, Object> originalMap = /* get the map */;
if( !originalMap.containsKey( "key to remove" ) ) {
return;
}
ImmutableMap.Builder<String, Object> mapBuilder = ImmutableMap.builder();
for( Map.Entry<String, Object> originalEntry : originalMap.entrySet() ) {
if( originalEntry.getKey().equals( "key to remove" ) ) {
continue;
}
mapBuilder.put( originalEntry );
}
Map<String, Object> modifiedMap = mapBuilder.build();
Related
Is there any fast and reliable way/approach to remove a set of entries based on an attribute on the entry's value. Below approach loops every entry which in undesirable.
e.g: ConcurrentHashMap - Entries will be in millions
Iterator<Map.Entry<String, POJO>> iterator = map.entrySet().iterator();
for (Iterator<Map.Entry<String, POJO>> it = iterator; it.hasNext(); ) {
Map.Entry<String, POJO> entry = it.next();
POJO value = entry.getValue();
if (value != null && *attribute*.equalsIgnoreCase(value.getAttribute())) {
it.remove();
}
}
There is no better way of mutating map in place without utilizing additional data structures. What you are basically asking for is secondary index like employed in databases, where you have pointers to entries based on some non-primary key properties. If you don't want to store extra index, there is no easier way then to iterate through all entries.
What I would suggest you to look into is composing map view over your original map. For example something like (using guava)
Map<String,POJO> smallerMap = Maps.filterValues(map,
v -> !attribute.equalsIgnoreCase(v.getAttribute())
);
You will need to be careful with such view (don't call size() on it for example), but for access etc it should be fine (depending on your exact needs, memory constraints etc).
And side note - please note I have removed null check for value. You cannot store null values in ConcurrentHashMap - and it is also not that great idea in normal map as well, better to remove entire key.
There are two solutions that I can think of
First solution:
Create another map for storing the object hashcode as key and corresponding key as value. The structure could be like as below
Map<Integer, String> map2 = new HashMap<>();
Here is working solution. Only drawback of this is getting a unique hashcode might be tough if there are large number of objects.
import java.util.HashMap;
import java.util.Map;
public class DualHashMap {
public static void main(String a[]){
Map<String, Pojo> map = new HashMap<>();
Map<Integer, String> map2 = new HashMap<>();
Pojo object1 = new Pojo();
Pojo object2 = new Pojo();
map.put( "key1", object1);
map.put( "key2", object2);
map2.put(object1.hashCode(), "key1");
map2.put(object2.hashCode(), "key2");
// Now let say you have to delete object1 you can do as follow
map.remove(map2.get(object1.hashCode()));
}
}
class Pojo{
#Override
public int hashCode(){
return super.hashCode(); //You must work on this yourself, and make sure hashcode is unique for each object
}
}
2nd solution:-
Use the solution provided for dual hashmap by Guava or Apache
The Apache Commons class you need is BidiMap.
Here is another for you by Guava
I'm about to get a grasp on the new Java 8 stream and lambda options, but there are still a few subtleties that I haven't yet wrapped my mind around.
Let's say that I have a map where the keys are the names of people. The value for each name is a map of ages and Person instances. Further assume that there does not exist more than one person with the same name and age.
Map<String, NavigableMap<Long, Person>> names2PeopleAges = new HashMap<String, NavigableMap<Long, Person>>();
After populating that map (elsewhere), I want to produce another map of the oldest person for each name. I want to wind up with a Map<String, Person> in which the keys are identical to those in the first map, but the value for each entry is the value of the value map for which the key of the value map has the highest number.
Taking advantage of the fact that a NavigableMap sorts its keys, I can do this:
Map<String, Person> oldestPeopleByName = new HashMap<String, Person>();
names2PeopleAges.forEach((name, peopleAges) -> {
oldestPeopleByName.put(name, peopleAges.lastEntry().getValue());
});
Question: Can I replace the last bit of code above with a single Java 8 stream/collect/map/flatten/etc. operation to produce the same result? In pseudo-code, my first inclination would be:
Map<String, Person> oldestPeopleByName = names2PeopleAges.forEachEntry().mapValue(value->value.lastEntry().getValue());
This question is meant to be straightforward without any tricks or oddities---just a simple question of how I can fully leverage Java 8!
Bonus: Let's say that the NavigableMap<Long, Person> above is instead merely a Map<Long, Person>. Could you extend the first answer so that it collects the person with the highest age value, now that NavigableMap.lastEntry() is not available?
You can create a Stream of the entries and collect it to a Map :
Map<String, Person> oldestPeopleByName =
names2PeopleAges.entrySet()
.stream()
.collect (Collectors.toMap(e->e.getKey(),
e->e.getValue().lastEntry().getValue())
);
Now, without lastEntry :
Map<String, Person> oldestPeopleByName =
names2PeopleAges.entrySet()
.stream()
.collect (Collectors.toMap(e->e.getKey(),
e->e.getValue().get(e.getValue().keySet().stream().max(Long::compareTo)))
);
Here, instead of relying on lastEntry, we search for the max key in each of the internal Maps, and get the corresponding Person of the max key.
I might have some silly typos, since I haven't actually tested it, by in principle it should work.
A similar question is asked before, and I provided there my answers https://stackoverflow.com/a/75004577/6777695 The answer there is slighty different than the one accepted here. I think the remapping of a key or/and value should belong in the map part of a stream instead of the collect as the map function for a stream is designed to do the actual transforming of data. In your use case I would suggest the following code snippet:
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
Map<String, Person> oldestPeopleByName = namesToPeopleAges.entrySet().stream()
.map(entry -> Map.entry((entry.getKey(), entry.getValue().lastEntry().getValue()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}
I have created a HashMap as per my code...
HashMap map=new HashMap();//HashMap key random order.
map.put("Amit","Java");
map.put("Saral","J2EE");
map.put("Saral","Andriod");//same key but different value
map.put("Nitin","PHP");
map.put("hj","Spring1");
System.out.println("There are "+map.size()+" elements in the map.");
System.out.println("Content of Map are...");
Set s=map.entrySet();
Iterator itr=s.iterator();
while(itr.hasNext()){
Map.Entry m=(Map.Entry)itr.next();
System.out.println(m.getKey()+"\t"+m.getValue()+"\t"+ m.hashCode());
}
When I execute this code, the value for key=Saral is Android. Is there any way that I can get the previous value for this key, which was J2EE?
No, you can't have that with a standard HashMap. The easiest solution would be to store a List as value in the map though, and then you can add multiple items to the list (Btw you should use generic collections too). To simplify, you could use a helper method like this:
void addToMap(Map<String, List<String>> map, String key, String value) {
List<String> list = map.get(key);
if (list == null) {
list = new ArrayList<String>();
map.put(key, list);
}
list.add(value);
}
Map<String, List<String>> map = new HashMap<String, List<String>>();
addToMap(map, "Amit", "Java");
addToMap(map, "Saral", "J2EE");
addToMap(map, "Saral", "Andriod");//same key but different value
addToMap(map, "Nitin", "PHP");
addToMap(map, "hj", "Spring1");
...
The helper method here is just an illustration - a full, robust implementation may need to include e.g. checks for duplicate values, depending on whether you allow them. If not, you may prefer using a Set instead of List.
Update
To print out the contents of this map, you need to use an embedded loop to iterate through the list of values for each map entry (btw you can use a foreach loop instead of an iterator):
for (Map.Entry<String, List<String>> m : map.entrySet())
{
for (String v : m.getValue())
{
System.out.println(m.getKey()+"\t"+v+"\t"+ m.hashCode());
}
}
A Map can contain at most one entry per key, so when you call map.put("Saral","Andriod"), the old "J2EE" value is removed. To support multiple values per key, you would need to maintain a Map<String, List<String>> or else a multi-map implementation such as Guava's Multimap.
As a side note I would recommend you start using generics, for example Map<String, String>, Iterator<String>, etc. for type safety at compile time.
The old value is overwritten (replaced). There will be only one mapping (entry) for one unique key. There fore it does not exist anymore so you can not retrieve it.
You cannot do this with standard implementations of Map that Java provides. However there are implementations of MultiMap (that's basically what you're after).
One example is this one from Google:
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/Multimap.html
Note that you won't be able to just get this one interface, you'll need a few classes along with it.
As other have said, this won't work with a standard Map. However, Google's Guava provides a MultiMap interface, which you can use to store multiple values with a single key.
Example of use:
Multimap<String,String> multiMap = ArrayListMultimap.create();
multiMap.put("color", "red");
multiMap.put("color", "blue");
System.out.println(multiMap.get("color")); //returns a ["red', "blue"] list
I already know how to do it the hard way and got it working - iterating over entries and swapping "manually". But i wonder if, like so many tasks, this one can be solved in a more elegant way.
I have read this post, unfortunately it does not feature elegant solutions. I also have no possibility to use any fancy Guava BiMaps or anything outside the jdk (project stack is already defined).
I can assume that my map is bijective, btw :)
Map<String, Integer> map = new HashMap<>();
Map<Integer, String> swapped = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
If you don't have a choice to use a third party library, I don't consider the following code so ugly (though some scripting languages do have elegant ways of doing it):
//map must be a bijection in order for this to work properly
public static <K,V> HashMap<V,K> reverse(Map<K,V> map) {
HashMap<V,K> rev = new HashMap<V, K>();
for(Map.Entry<K,V> entry : map.entrySet())
rev.put(entry.getValue(), entry.getKey());
return rev;
}
The standard API / Java runtime doesn't offer a bi-directional map, so the only solution is to iterate over all entries and swap them manually.
What you can do is create a wrapper class which contains two maps and which does a dual put() internally so you have fast two views on the data.
[EDIT] Also, thanks to open source, you don't have to include a third party library, you can simply copy the classes you need into your own project.
Maps are not like lists, which can be reversed by swapping head with tail.
Objects in maps have a computed position, and using the value as key and the key as value would requiere to re-compute the storage place, essentialy building another map. There is no elegant way.
There are, however, bidirectional maps. Those may suit your needs. I'd reconsider using third-party libraries.
There are some jobs that can be simplified to a certain point and no more. This may be one of them!
If you want to do the job using Java collections apis only then brute force is the way to go - it will be quick (unless the collection is huge) and it will be an obvious piece of code.
As a hint to answer
https://stackoverflow.com/a/42091477/8594421
This only works, if the map is not a HashMap and does not contain duplicate values.
Map<String,String> newMap = oldMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
throws an exception
java.lang.IllegalStateException: Duplicate key
if there are values more than once.
The solution:
HashMap<String,String> newMap = new HashMap<>();
for(Map.Entry<String,String> entry : oldMap.entrySet())
newMap.put(entry.getValue(), entry.getKey());
// Add inverse to old one
oldMap.putAll(newMap);
If you had access to apache commons-collections, you could have used MapUtils.invertMap.
Note: The behaviour in case of duplicated values is undefined.
(Replying to this as this is the first google result for "java invert map").
Java stream API provides nice set of APIs that would help you with this.
If the values are unique then the below would work. When I mean values, I mean the V in the Map<K, V>.
Map<String, Integer> map = new HashMap<>();
Map<Integer, String> swapped = map.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
If the values are not unique, then use below:
Map<Integer, List<String>> swapped = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
Thanks Nikita and FreyaZ. Posting as new answer as there were so many edit queues for Nikita's Answer
This will work for duplicate values in the map also, but not for HashMap as values.
package Sample;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Sample {
public static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
Map<String, Set<String> > newmap = new HashMap<String, Set<String> >();
map.put("1", "a");
map.put("2", "a");
map.put("3", "b");
map.put("4", "b");
System.out.println("before Reversing \n"+map.toString());
for (Map.Entry<String, String> entry : map.entrySet())
{
String oldVal = entry.getValue();
String oldKey = entry.getKey();
Set<String> newVal = null;
if (newmap.containsKey(oldVal))
{
newVal = newmap.get(oldVal);
newVal.add(oldKey);
}
else
{
newVal= new HashSet<>();
newVal.add(oldKey);
}
newmap.put(oldVal, newVal);
}
System.out.println("After Reversing \n "+newmap.toString());
}
}
I'm used to working with PHP but lately I've been working with Java and I'm having a headache trying to figure this out. I want to save this representation in Java:
Array (
["col_name_1"] => Array (
1 => ["col_value_1"],
2 => ["col_value_2"],
... ,
n => ["col_value_n"]
),
["col_name_n"] => Array (
1 => ["col_value_1"],
2 => ["col_value_2"],
... ,
n => ["col_value_n"]
)
)
Is there a clean way (i.e. no dirty code) to save this thing in Java? Note; I would like to use Strings as array indexes (in the first dimension) and I don't know the definite size of the arrays..
Try using a Map<String, List<String>>. This will allow you to use Strings as keys / indices into the outer map and get a result being a list of Strings as values. You'll probably want to use a HashMap for the outer map and ArrayList's for the inner lists.
If you want some clean code that is similar to the PHP you gave to initialize it, you can do something like this:
Map<String, List<String>> columns = new HashMap<String, List<String>>() {{
put("col_name_1", Arrays.asList("col_val_1", "col_val_2", "col_val_n"));
put("col_name_2", Arrays.asList("col_val_1", "col_val_2", "col_val_n"));
put("col_name_n", Arrays.asList("col_val_1", "col_val_2", "col_val_n"));
}};
You can use a Map and a List (these both are interfaces implemented in more than one way for you to choose the most adequate in your case).
For more information check the tutorials for Map and List and maybe you should start with the Collections tutorial.
An example:
import java.util.*;
public class Foo {
public static void main(String[] args) {
Map<String, List<String>> m = new HashMap<String, List<String>>();
List<String> l = new LinkedList<String>();
l.add("col_value_1");
l.add("col_value_2");
//and so on
m.put("col_name_1",l); //repeat for the rest of the colnames
//then, to get it you do
List<String> rl = m.get("col_name_1");
}
}
You want a Map, which are keyed by just about anything. HashMaps work in most cases.
Something like this.
List<String> col1Vals = new java.util.ArrayList<String>();
col1Vals.add("col_value_1");
col1Vals.add("col_value_2");
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("col_name_1", col1Vals);
If you want something simpler, the commons-lang library has a MultiMap.
Be forewarned that the Vector is legacy code for the Collections framework. It synchronizes access to its elements which slows down performance. Most use cases for using List don't need this kind of thread safety, and even if you did, I would be more inclined to use the CopyOnWriteArrayList.