How can I compare two MultiMaps? - java

I have two Multimaps which have been created from two huge CSV files.
Multimap<String, SomeClassObject> mapOne = ArrayListMultimap.create();
Multimap<String, SomeClassObject> mapTwo = ArrayListMultimap.create();
I have assumed one CSV column to be as a Key and each of the Key has thousands of values associated with it. Data contained within these Multimaps should be same. Now I want to compare the data within these Multimaps and find if any values are different. Here are the two approaches I am thinking of:
Approach One:
Make one big list from the Multimap. This big list will contain a few individual lists. Each of the smaller lists contains a unique value which is the "key" read from Multimap along with its associated values, which will form the rest of that individual list.
ArrayList<Collection<SomeClassObject>> bigList = new ArrayList<Collection<SomeClassObject>>();
Within bigList will be individual small lists A, B, C etc.
I plan on picking individual lists from each bigList of the two files on the basis of checking that individual list from second Multimap contains that "key" element. If it does, then compare both of these lists and find anything that could not be matched.
Approach Two:
Compare both the Multimaps but I am not sure how will that be done.
Which approach should have smaller execution time? I need the operation to be completed in minimum amount of time.

Use Multimaps.filterEntries(Multimap, Predicate).
If you want to get the differences between two Multimaps, it's very easy to write a filter based on containsEntry, and then use the filtering behavior to efficiently find all the elements that don't match. Just build the Predicate based on one map, and then filter the other.
Here's what I mean. Here, I'm using Java 8 lambdas, but you can look at the revision history of this post to see the Java 7 version:
public static void main(String[] args) {
Multimap<String, String> first = ArrayListMultimap.create();
Multimap<String, String> second = ArrayListMultimap.create();
first.put("foo", "foo");
first.put("foo", "bar");
first.put("foo", "baz");
first.put("bar", "foo");
first.put("baz", "bar");
second.put("foo", "foo");
second.put("foo", "bar");
second.put("baz", "baz");
second.put("bar", "foo");
second.put("baz", "bar");
Multimap<String, String> firstSecondDifference =
Multimaps.filterEntries(first, e -> !second.containsEntry(e.getKey(), e.getValue()));
Multimap<String, String> secondFirstDifference =
Multimaps.filterEntries(second, e -> !first.containsEntry(e.getKey(), e.getValue()));
System.out.println(firstSecondDifference);
System.out.println(secondFirstDifference);
}
Output is the element that is not in the other list, in this contrived example:
{foo=[baz]}
{baz=[baz]}
These multimaps will be empty if the maps match.
In Java 7, you can create the predicate manually, using something like this:
public static class FilterPredicate<K, V> implements Predicate<Map.Entry<K, V>> {
private final Multimap<K, V> filterAgainst;
public FilterPredicate(Multimap<K, V> filterAgainst) {
this.filterAgainst = filterAgainst;
}
#Override
public boolean apply(Entry<K, V> arg0) {
return !filterAgainst.containsEntry(arg0.getKey(), arg0.getValue());
}
}
Use it as an argument to Multimaps.filterEntries() like this:
Multimap<String, String> firstSecondDifference =
Multimaps.filterEntries(first, new FilterPredicate(second));
Multimap<String, String> secondFirstDifference =
Multimaps.filterEntries(second, new FilterPredicate(first));
Otherwise, the code is the same (with the same result) as the Java 8 version above.

From the ArrayListMultimap.equals doc:
Compares the specified object to this multimap for equality.
Two ListMultimap instances are equal if, for each key, they contain the same values in the same order. If the value orderings disagree, the multimaps will not be considered equal.
So just do mapOne.equals(mapTwo). You won't have a better execution time by trying to do it yourself.

Related

Store the values without overwriting

I have a Map<String, Integer> e.g.
"aaa", 1
"bbb", 2
"ccc", 3
"aaa", 4
The problem is that the HashMap does not store all key and values, as I've understood, when i try add the last pair ("aaa", 4), it will not be added, instead of this, the value for "aaa" (I mean 1) will be overwritten on 4.
I know, that I could create class, where I could store these pairs, but I need another solution. (without creating a new class)
EDIT ------------------------------------
Actually I have much more pairs, and I do not have uniques String or Integers, I mean that, if even I have two similar pairs they will be stored
A map, by definition, will have distinct keys. If you add a key-value pair and the key already exists, the new key-value pair will overwrite the existing key-value pair.
For your scenario, when you have multiple values against a single key, you can explore the following options
Option 1 : Since your key-value pairs are not unique, it can be stored as list of pairs. For every key-value pair, you can create a pair and insert it into the list.
List<Pair<String, Integer>> data = new ArrayList();
Pair<String, Integer> item = new Pair("abc", 1);
data.add(item);
This option does not give you optimized lookup capabilities that comes with Map.
Option 2. Create a Map<String, List<Integer>>. You'll not be able to do simple put operations on the map anymore, but you will be able to store all the items corresponding to each key without loss of information as well as retrieve them faster.
Create a List:
if (!map.containsKey("aaaa")) {
map.put("aaaa", new ArrayList<Integer>());
}
List<Integer> aaaaValues = map.get("aaaa");
aaaaValues.add(1);
aaaaValues.add(4);
...
If your values are unieque, use them as keys.
You don't have to create class. You can use List<org.apache.commons.lang3.tuple.Pair<String, Integer>>
Also one way, override equals and hashCode where you speak that object is unique only if String and Integer parameter is unique in pair
Map<String, Integer> map = new HashMap<String, Integer>(){
#Override
public boolean equals(Object o)
{
// your realization
}
#Override
public int hashCode()
{
// your realization
}
};

Java 8 re-map with modified value

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));
}
}

Merging Map of LinkedHashMap

I have two Map of LinkedHashMap in this format==> Map<String,LinkedHashMap<String,String>> both m1 & m2 have same key values, How we combine this m1 & m2 and make m3 with all elements.
Note :Can you some one give psuedocode I will implement it.Thanks.
Input is like below format:
m1={1={rollno=1,name=chris,height=7ft},2={rollno=2,name=stephen,height=6ft}}
m2={1={rollno=1,name=chris,weight=65},2={rollno=2,name=stephen,weight=73}}
Output :
m3={1={rollno=1,name=chris,height=7ft,weight=65},2={rollno=2,name=stephen,height=6ft,weight=73}}
What I tried :
private static Map<String, LinkedHashMap<String, String>> mergeMap(Map<String,LinkedHashMap<String, String>> m1, Map<String, LinkedHashMap<String, String>> m2) {
Map<String,LinkedHashMap<String, String>> newMap = new LinkedHashMap<String, LinkedHashMap<String, String>>(m1);
for (Map.Entry<String, LinkedHashMap<String, String>> entry : m2.entrySet()) {
LinkedHashMap<String, String> t1=newMap.get(entry.getKey());
newMap.putAll(m2);
}
System.out.println("ouput :"+newMap);
return newMap;
}
You can follow the steps below to merge the maps:
First create a newMap, passing first map - map1 as parameter. You have to use the overloaded constructor - LinkedHashMap(Map) for that. Now you have a map with all the elements of map1. Half of your job is done.
Map<String, Map<String, String>> newMap = new LinkedHashMap<>(map1);
Then you need to move elements from 2nd map to the newMap. For that, you would need to iterate over map2. You can use Map#entrySet() method to iterate over each entry in map2. You would then use Map.Entry#getKey() and Map.Entry#getValue() methods to get the key and value respectively for each entry.
For each key in map2, get the current value from newMap, and merge the value of map2, with the value in newMap. Both the values are Map. You can use Map#putAll() method to merge the two maps. It will automatically ignore the already available keys, and add the extra key-value pair.
Now, after resolving the above issue, you should consider changing your data structure. You should create a class say Person, to store all those attributes, and maintain a Map<Integer, Person>, where key will be rollNo.
What you have shown is just what you have. If you could explain some other details like, how and from where did you get those maps, and why would you possibly have the attributes of same person distributed in two different maps, may be we can help you better to formulate the data structure properly. Having a nested Map might be handled if you have small set of data, but if you have larger set of data, you will face difficult in handling them. You should certainly follow Object Oriented Approach.
You should use java object to store complete information.
like
class Student{
int rollNo;
String name;
String height;
String weight;
}
And store your elements like
Map<Integer,Student> map = new HashMap<Integer,Student>();
it will be much easy to merge and store and manage element like this

Retrieving the previous key-map value before it was overwritten in a HashMap

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

Comparing maps with lists in java

Ive come across a nice problem recently,
I have a Map
LinkedHashMap<String, List<MyCustomObject>>
I need to compare this to another Map, which will be populated in the exact same method is the data in the DB is the same.
What really is happening is , that I am populating map1 at time 0, and populating map2 at time t, using the same java method(when I say this, i mean using the same DB/tables etc)
So if the data on the DB side is same, then these two maps should have the same content, in the same order(hopefully!!)
Now I want to compare these two maps and if they are not equal (i.e some new data was fetched from the DB), then I want to do some action.If they are the same, I just want to leave it like that.
Can someone point me out to tutes that will help me understand how to do that??
Is serialization an answer to this??
Thanks,
Neeraj
just use equals:
if (!map1.equals(map2)) {
//they differ
}
you will have to implement equals (and hashCode!) on MyCustomObject, since (if I understand correctly) the objects will have the same content but different identity (the default equals just compares the pointers - i.e., the memory address in which the object live in).
Make sure that your MyCustomObject class implements an appropriate equals and hashcode methods and then it's as easy as getting the result of map1.equals(map2).
This is what the javadocs have to say about it:
Compares the specified object with
this map for equality. Returns true if
the given object is also a map and the
two Maps represent the same mappings.
More formally, two maps t1 and t2
represent the same mappings if
t1.entrySet().equals(t2.entrySet()).
This ensures that the equals method
works properly across different
implementations of the Map interface.
The other answers are good and here's an illustration that indeed, the map functions iterate through the keys and the values (and compare items from the lists) to do equality comparison. As they say, you're going to need to override hashCode and equals.
static public List<String> getStringList(String... arg) {
ArrayList<String> arrayList = new ArrayList<String>();
for(int x=0;x<arg.length;x++) arrayList.add(arg[x]);
return arrayList;
}
static public void main(String[] arg) {
LinkedHashMap<String, List<String>> mapA = new LinkedHashMap<String, List<String>>();
LinkedHashMap<String, List<String>> mapB = new LinkedHashMap<String, List<String>>();
LinkedHashMap<String, List<String>> mapC = new LinkedHashMap<String, List<String>>();
mapA.put("same", getStringList("a", "b", "c"));
mapB.put("same", getStringList("a", "b", "c"));
mapC.put("same", getStringList("a", "b", "d"));
System.out.println(mapA.equals(mapB) ? "true" : "false");
System.out.println(mapA.equals(mapC) ? "true" : "false");
}

Categories

Resources