Pass an object as a parameter and modify it within the method - java

Let's say I got a Map<String, String> and I wanna remove all the entries that value contains foo. What is the best way to do it, in terms of optimization/memory/etc.? The four syso below are printing the same result, that is to say {n2=bar}.
public static void main(String[] args) {
Map<String, String> in = new HashMap<String, String>();
in.put("n1", "foo");
in.put("n2", "bar");
in.put("n3", "foobar");
// 1- create a new object with the returned Map
Map<String, String> in1 = new HashMap<String, String>(in);
Map<String, String> out1 = methodThatReturns(in1);
System.out.println(out1);
// 2- overwrite the initial Map with the returned one
Map<String, String> in2 = new HashMap<String, String>(in);
in2 = methodThatReturns(in2);
System.out.println(in2);
// 3- use the clear/putAll methods
Map<String, String> in3 = new HashMap<String, String>(in);
methodThatClearsAndReadds(in3);
System.out.println(in3);
// 4- use an iterator to remove elements
Map<String, String> in4 = new HashMap<String, String>(in);
methodThatRemoves(in4);
System.out.println(in4);
}
public static Map<String, String> methodThatReturns(Map<String, String> in) {
Map<String, String> out = new HashMap<String, String>();
for(Entry<String, String> entry : in.entrySet()) {
if(!entry.getValue().contains("foo")) {
out.put(entry.getKey(), entry.getValue());
}
}
return out;
}
public static void methodThatClearsAndReadds(Map<String, String> in) {
Map<String, String> out = new HashMap<String, String>();
for(Entry<String, String> entry : in.entrySet()) {
if(!entry.getValue().contains("foo")) {
out.put(entry.getKey(), entry.getValue());
}
}
in.clear();
in.putAll(out);
}
public static void methodThatRemoves(Map<String, String> in) {
for(Iterator<Entry<String, String>> it = in.entrySet().iterator(); it.hasNext();) {
if(it.next().getValue().contains("foo")) {
it.remove();
}
}
}

The best way is methodThatRemoves because:
In terms of memory consumption: it doesn't create a new map so doesn't add memory overhead.
In terms of CPU use: iterator has O(1) complexity for calling next or removing the current element.

The most efficient way methodThatRemoves, because it
Uses almost no memory
Creates no objects except the (lightweight) iterator
Is extremely fast (doesn't use any map lookups)
I would not make a copy first though, unless you have an unmodifiable map or you need to preserve the original.

For me the best is the one with the Iterator - methodThatRemoves, because you don't create an intermediate Map and don't use put method.
By the way the first one : methodThatReturns can be faster because put complexity is O(1) whereas remove is O(n) in worst case but it will use more memory because you have 2 different instances of Map.

I would personally go with methodThatRemoves because you are only performing a loop operation and checking for "foo" equality. The others do that as well as Object map creation and map clear/put operations. So you clearly have 1 method doing less.
Also if you want to reduce memory usage you are better off not creating an extra HashMap just to remove 1 or more entries. This is assuming you don't mind the extra computation to iterate the map.
If you really want to go more in depth, you should evaluate this using a profiler or some sort.

Related

Avoiding ConcurrentModificationException while modifying multiple Maps

I have class containing multiple HashMaps. The values in these HashMaps are the same but the keys are different. I have to remove the same element from all Maps that contain it. The method that removes these elements takes a Collection as argument, iterates over it and removes elements from multiple HashMaps.
Here is the code:
private Map<Position, Place> map1 = new HashMap<Position, Place>();
private Map<String, List<Place>> map2 = new HashMap<String, List<Place>>();
private Map<Category, List<Place>> map3 = new HashMap<Category, List<Place>>();
public void removePlaces2(Collection<Place> places) {
Iterator<Place> iter = places.iterator();
while (iter.hasNext()) {
Place p = iter.next();
Position pos = p.getPosition();
String name = p.getName();
Category cat = p.getCategory();
map1.remove(pos);
List<Place> list1 = map2.get(name);
list1.remove(p);
if (list1.isEmpty()) {
map2.remove(name);
}
if (cat != null) {
List<Place> list2 = map3.get(cat);
list2.remove(p);
}
this.remove(p);
modified = true;
}
revalidate();
}
The method throws an ConcurrentModificationException at the line Place p = iter.next();. (But not every time.) I'm at a loss as to how to avoid it. If I use iter.remove(p) that will only remove the element from the argument to the method: Collection<Place> places. And this is not what I want.
The question is how can I avoid this exception while removing the element from multiple maps?
Notice: I am not iterating over the maps from which the element is to be removed.
If you are passing map1, map2, or map3 as an argument to removePlaces2(), create a copy instead:
removePlaces2(new LinkedList<Place>(map1.values()));
If the exception persists try using thread safe versions of the maps:
private Map<Position, Place> map1 = Collections.synchronizedMap(new HashMap<Position, Place>());
private Map<String, List<Place>> map2 = Collections.synchronizedMap(new HashMap<String, List<Place>>());
private Map<Category, List<Place>> map3 = Collections.synchronizedMap(new HashMap<Category, List<Place>>());

Convert ArrayList to HashMap<String, String>

I have this ArrayList
public ArrayList<HashMap<String, String>> xmlFileNames = new ArrayList<>();
and I want to convert this to:
HashMap<String, String> comparemap2 = new HashMap<>();
What I want is: I want all the Items inside the ArrayList and want to put them into the HashMap
My HashMap looks like:
KEY VALUE
job_id 032014091029309130921.xml
job_id 201302149014021492929.xml
job_id 203921904901920952099.xml
EDIT:
Later I want to compare this map with an existing map:
Properties properties = new Properties();
try {
properties.load(openFileInput("comparexml.kx_todo"));
} catch (IOException e) {
e.printStackTrace();
}
for (String key : properties.stringPropertyNames()) {
compareMap.put(key, properties.get(key).toString());
}
HashMap<String, String> oldCompareMap = new HashMap<>();
for (HashMap key : xmlFileNames) {
oldCompareMap.putAll(key);
}
isEqualMaps(oldCompareMap, compareMap);
I only want to compare, if the filename exists in the compareMap. If not, than add it to the xmlFileName Map
I've looked up in StackOverFlow, how I can convert ArrayList to HashMap. But the other Threads treat data types like Item or Product.
I hope you can help me!
Kind Regards
Given...
public ArrayList<HashMap<String, String>> xmlFileNames = new ArrayList<>();
then something like this should do it.
HashMap<String, String> nhm = new HashMap<>();
for (HashMap xmlFileHm : xmlFileNames ) {
nhm.putAll(xmlFileHm);
}
but be aware if you have duplicate keys in your hashmaps they will get overwritten.
You should also think about coding to interfaces. Take a look at Map and List rather than typing your collections to implementations (ArrayList and HashMap). Take a look at this thread which is quite interesting What does it mean to "program to an interface"?
Depending on what you are trying to do as well you might consider a MultiMap as this might server your purposes better
Edit After update to the question...
A multimap would be better here with one key and multiple values. Although arguably if the key never changes then you could just store the values in a list. For multiamps you can use Google's guava library or do one yourself. For example (not checked for compilation errors as Im doing this from my head)
Map<String, List<String>> m = new HashMap<>();
if (m.containsKey("key")) {
m.get("key").add("new value");
}
else {
List<String> l = new ArrayList<>();
l.add("new value");
m.put("key", l);
}
You can create a new HashMap, then iterate through the list and put all elements from the map from the list to the main map.
List<Map<String, String>> list = new ArrayList<>();
Map<String, String> map = new HashMap<>();
for (Map<String, String> mapFromList : list) {
map.putAll(mapFromList);
}
You can try something like this..
ArrayList<HashMap<String, String>> xmlFileNames = new ArrayList<>();
HashMap<String, String> comparemap2 = new HashMap<>();
for(HashMap<String, String> i:xmlFileNames){
comparemap2.putAll(i);
}
You may need to consider the case of duplicate keys. else they will get override.
Create a new map and put All each element of arrayList to the map.
But in that case if you have same keys in two element of arrayList (hashmap) then it will override the previous one.

How to sort a Map in Java by its String keys which are numeric

I have created a map called result.
In the sortByKeys method as my keys are String with Numeric values, I have converted them to Integer key type Map then sorted them.
The sorting is working fine when I am looping and printing individually, but not when I am setting them in another Map.
public class TestDate {
public static void main (String args[]){
Map<String, String> result = new HashMap<String, String>();
result.put("error", "10");
result.put("1","hii");
result.put("Update","herii");
result.put("insert","insert");
result.put("10","hiiuu");
result.put("7","hii");
result.put("21","hii");
result.put("15","hii");
Map<String, String> sorted = sortByKeys(result);
//System.out.println(sorted);
}
private static Map<String, String> sortByKeys(Map<String, String> map) {
Map <Integer,String> unSorted = new HashMap<Integer, String>();
Map <String,String> sorted = new HashMap<String, String>();
for (Map.Entry<String, String> entry : map.entrySet())
{
try{
int foo = Integer.parseInt(entry.getKey());
unSorted.put(foo, entry.getValue());
}catch (Exception e){
}
}
Map<Integer, String> newMap = new TreeMap<Integer, String>(unSorted);
Set set = newMap.entrySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
Map.Entry me = (Map.Entry)iterator.next();
System.out.println(me.getKey());
System.out.println(me.getValue());
sorted.put(me.getKey().toString(), me.getValue().toString());
}
System.out.println(sorted);
return null;
}
}
Here is the o/p :
1
hii
7
hii
10
hiiuu
15
hii
21
hii
{21=hii, 10=hiiuu, 1=hii, 7=hii, 15=hii}
If you don't need the last inch of performance, you can solve this rather directly, without an extra step to sort the map, by using SortedMap:
Map<String,String> result = new TreeMap<>(Comparator.comparingInt(Integer::parseInt));
If you are among the unfortunate bunch who are still being denied access to Java 8, you'll have to implement the Comparator in long-hand:
new TreeMap<>(new Comparator<String,String> { public int compare(String a, String b) {
return Integer.compare(Integer.parseInt(a), Integer.parseInt(b));
}});
The above approach works only under the assumption that all keys are parseable integers. If that is not the case, then you won't be able to use the SortedMap directly, but transform your original map into it, filtering out the unparseable keys.
It's because the Map you're putting them into is a HashMap, which isn't sorted. There's no guarantee of the ordering of results you'll get out of the HashMap, even if you put them in in the right order.
(And calling it sorted doesn't change anything :) )
You print 2 different maps and not the same: you iterate over and print the entries of newMap map, and at the end you print sorted map.
You see the sorted entries printed because you iterate over your sorted newMap.
Then you print the sorted map which is unsorted (despite by its name). You print a different map instance.
Print this:
System.out.println(newMap); // This is the instance of the sorted "TreeMap"

Map of maps - putting value to inner map

I have map of maps
Map<String, Map<String,Integer>> outerMap = new HashMap<String, Map<String, Integer>>();
and I want to put some values to inner map. Is that correct way? Or it can be done better?
class SampleMap {
Map<String, Map<String, Integer>> outerMap = new HashMap<String, Map<String, Integer>>();
public void add(String outerKey, String innerKey, Integer value) {
Map<String, Integer> tempMap = new HashMap<String, Integer>();
if (outerMap.size() > 0)
tempMap = outerMap.get(outerKey);
tempMap.put(innerKey, value);
outerMap.put(key, tempMap);
}
}
You can improve the code by avoiding the creation of a new inner map eagerly, until the point when you know that you must create it.
In addition, if you know that the inner map instance came from the outer map, you don't have to spend time putting it back where it came from.
public void add(String outerKey, String innerKey, Integer value) {
Map<String, Integer> tempMap
if (outerMap.containsKey(outerKey)) {
tempMap = outerMap.get(outerKey);
} else {
tempMap = new HashMap<String, Integer>();
outerMap.put(outerKey, tempMap);
}
tempMap.put(innerKey, value);
}
Technically there is nothing wrong in your code (except a minor improvement suggested by dasblinkenlight), but is map of maps what you really need?
If you want to read/write values by two keys, probably it's better to create map from pair of two keys (MultiKey or Pair implementation can be used) or another data structure (see this comment for details https://stackoverflow.com/a/3093993/554281)

Java Map, filter with values properties

I have a
TreeMap resMap new TreeMap<String, Map<String, String>>();
I would like to filter and keep only entries that values contains a known pair, let's say ('mike' => 'jordan'), and avoid a loop like below
Is there in my included libraries apache.commons and google.common a filter method (that probably would do a loop too, but at least it's less verbose
for (Entry<String, TreeMap<String, String>> el : resMap.entrySet()){
if (el.getValue().get("mike").equals("jordan")){
//
}
}
You can use filters from Guava and the Predicate interface.
Predicate<T> yourFilter = new Predicate<T>() {
public boolean apply(T o) {
// your filter
}
};
So, simple example would be:
Predicate<Integer> evenFilter = new Predicate<Integer>() {
public boolean apply(Integer i) {
return (i % 2 == 0);
}
};
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Map<Integer, Integer> evenMap = Maps.filterValues(map, evenFilter);
Rather than force your client code to use a filter/loop, build what you need into the API of your class:
public class MyClass {
private TreeMap resMap new TreeMap<String, Map<String, String>>();
public void filter(String key, String value) {
// Some impl here. Either your loop or the guava approach
}
}
BTW, if you use your loop, consider changing to this:
for (Iterator<Map.Entry<String, TreeMap<String, String>>> i = resMap.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, TreeMap<String, String>> entry = i.next();
if (value.equals(entry.getValue().get(key))) {
i.remove();
}
}
The changes to the loop are:
Changed order of equals to avoid NPE
Using iterator to allow removal of entries directly
Even if you don't have a class, you could easily wrap it up in a static method on a utility class, where it could also easily be parameterized to work with any nested map:
public static <K1, K2, V> void filter(Map<K1, Map<K2, V>> map, K2 key, V value) {
// Some impl here
}
Here's a non-guava impl for the static method:
for (Iterator<Map.Entry<K1, Map<K2, V>>> i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry<K1, Map<K2, V>> entry = i.next();
if (value.equals(entry.getValue().get(key))) {
i.remove();
}
}
From #Ferrybig answer in this post.
You can use the Java 8 method Collection.removeIf for this purpose:
map.values().removeIf(Object o -> o.get("mike").equals("jordan"));
This removed all values that match the predicate.
Online demo
This works by the fact that calling .values() for a HashMap returns a collection that delegated modifications back to the HashMap itself, meaning that our call for removeIf() actually changes the HashMap (this doesn't work on all java Map's)
Take a look at Guava's Predicates and Functions.
Here are two examples. The both print the key based on match in the value's properties.
private static void printMatchingEntriesUsingALoop(Map<String, Map<String, String>> resMap, String key, String value) {
for (Map.Entry<String, Map<String, String>> entry : resMap.entrySet())
if (value.equals(entry.getValue().get(key)))
System.out.println(entry.getKey());
}
private static void printMatchingEntriesUsingGuava(Map<String, Map<String, String>> resMap, final String key, final String value) {
Predicate<Map<String, String>> keyValueMatch =
new Predicate<Map<String, String>>() {
#Override
public boolean apply(#Nullable Map<String, String> stringStringMap) {
return value.equals(stringStringMap.get(key));
}
};
Maps.EntryTransformer<String, Map<String, String>, Void> printKeys =
new Maps.EntryTransformer<String, Map<String, String>, Void>() {
#Override
public Void transformEntry(#Nullable String s,
#Nullable Map<String, String> stringStringMap) {
System.out.println(s);
return null;
}
};
Maps.transformEntries(Maps.filterValues(resMap, keyValueMatch), printKeys);
}
public static void main(String... args) {
Map<String, Map<String, String>> resMap = new TreeMap<String, Map<String, String>>();
printMatchingEntriesUsingALoop(resMap, "first", "mike");
printMatchingEntriesUsingGuava(resMap, "first", "mike");
}
One uses a loop and one use Guava.
While the first one performs better, you should really decide which will be the easiest to understand and maintain.
Some suggestions from #missingfaktor. You have to use your own judgement, but he highlighted some of the issues well.
a lot of code duplication.
special case handling.
More cyclomatic complexity.
More chances of error, as a result of first three bullets.
Hard to follow code.
Imagine you are a new developer who has to support this software. Which would you rather be facing?
You can filter the map using java 8 and streams. The first step in this process is converting to a stream using entrySet().stream(). This gives you a Stream<Map.Entry<String, TreeMap<String, String>>. You can then use filter(...) to filter the list. When you filter, you should return true when the incoming value should be included in the filter result. After you filtered the results, you can use foreach to loop over the final result.
The final result will look like the following:
resMap.entrySet().stream()
.filter(e -> el.getValue().get("mike").equals("jordan"))
.foreach(e -> {
// Do something with your entry here
});

Categories

Resources