How to combine two Maps into one with maintaining the duplicate values - java

I have two hashmaps "map0 and map1", both of them holds keys of type Point and values of type Double as shown below.
I am sure that the key will never be duplicate but the values could be and I want to add both of "map0 and map1" into one Hashmap so that
I do not lose the duplicate values "I want to keep the duplicate values". After i checked some questions in this website, I used .putAll() method as shown below in the code, but the problem is, when I combined both maps "map0, map1" into one combined map, I lost the duplicate values as shown below in the results as map0 has value of 20 and map1 has value of 20 as well!
Please let me know how to combine both maps into one without losing the duplicate values
Code:
public static void main(String[] args) {
HashMap<Point, Double> map0 = new HashMap<Point, Double>();
HashMap<Point, Double> map1 = new HashMap<Point, Double>();
map0.put(new Point(32,59), (double) 56);
map0.put(new Point(398,3), (double) 20);
map0.put(new Point(3,3), (double) 209);
map1.put(new Point(32,596), (double) 561);
map1.put(new Point(396,311), (double) 20);
map1.put(new Point(35,34), (double) 2099);
System.out.println("map0.size:"+map0.size());
System.out.println("map1.size:"+map1.size());
HashMap<Point, Double> combine = new HashMap<Point, Double>();
combine.putAll(map0);
combine.putAll(map1);
ValueComparator vc = new ValueComparator(combine);
TreeMap<Point, Double> tree= new TreeMap<Point, Double>(vc);
tree.putAll(combine);
System.out.println("tree.size:"+tree.size());
System.out.println("tree_sorted:"+tree);
}
static class ValueComparator implements Comparator<Point> {
private HashMap<Point, Double> map = null;
public ValueComparator(HashMap<Point, Double> map) {
// TODO Auto-generated constructor stub
this.map = map;
}
public int compare(Point arg0, Point arg1) {
// TODO Auto-generated method stub
return Double.compare(this.map.get(arg0), this.map.get(arg1));
/*
if (this.map.get(arg0) >= this.map.get(arg1)) {
return 1;
} else {
return -1;
}
*/
}
}
output:
map0.size:3
map1.size:3
tree.size:5
tree_sorted:{{398.0, 3.0}=20.0, {32.0, 59.0}=56.0, {3.0, 3.0}=209.0, {32.0, 596.0}=561.0, {35.0, 34.0}=2099.0}

You cannot have two objects in TreeMap which are 'equal' from the standpoint of given comparator. It is explicitly stated in javadoc:
This is so because the Map interface is defined in terms of the equals operation, but a sorted map performs all key comparisons using its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of the sorted map, equal.
Your further use of sorted collection is not stated in the question so I presume that sorted List will be enough for you. I also recommend to extend the Pointclass to associate value with it. It will better suits to this use.
Also your comparator was not implemented the right way. It should not have the outside map reference.
So consider following code:
public class Example {
public static class ValuedPoint extends Point {
private double value;
public ValuedPoint(double x, double y, double value) {
super(x, y);
this.value = value;
}
public double getValue() {
return value;
}
public String toString() {
return "{" + x + ", " + y + ", " + value + "}";
}
}
private static class ValueComparator implements Comparator<ValuedPoint> {
public int compare(ValuedPoint arg0, ValuedPoint arg1) {
return Double.compare(arg0.getValue(), arg1.getValue());
}
}
public static void main(String[] args) {
Set<ValuedPoint> set0 = new HashSet<ValuedPoint>();
Set<ValuedPoint> set1 = new HashSet<ValuedPoint>();
set0.add(new ValuedPoint(32, 59, 56));
set0.add(new ValuedPoint(398, 3, 20));
set0.add(new ValuedPoint(3, 3, 209));
set1.add(new ValuedPoint(32, 596, 561));
set1.add(new ValuedPoint(396, 311, 20));
set1.add(new ValuedPoint(35, 34, 2099));
System.out.println("set0.size:" + set0.size());
System.out.println("set1.size:" + set1.size());
Set<ValuedPoint> combined = new HashSet<ValuedPoint>();
combined.addAll(set0);
combined.addAll(set1);
System.out.println("combined size:" + combined.size());
ValueComparator comparator = new ValueComparator();
List<ValuedPoint> sortedList = new ArrayList<ValuedPoint>(combined);
Collections.sort(sortedList, comparator);
System.out.println("list size:" + sortedList.size());
System.out.println("sortedList:" + sortedList);
}
}
The output is:
set0.size:3
set1.size:3
combined size:6
list size:6
sortedList:[{398.0, 3.0, 20.0}, {396.0, 311.0, 20.0}, {32.0, 59.0,56.0}, {3.0, 3.0, 209.0}, {32.0, 596.0, 561.0}, {35.0, 34.0, 2099.0}]

You define the comparison criteria for your keys (Point) is the related value (Double), thus the two points with value 20 are identical to your Comparator.
If you want to keep the duplicates, change the Comparator (i.e. your definition of equality) or use a different data structure, as a map does not allow for duplicate keys.

Related

Java multiple keys direct to the same values

Is there a way to points several keys to the same value?
i.e.
HashMap<String, Float> mymap = new HashMap<>();
mymap.put("hello",5f);
mymap.put("bye",5f);
~somehow point bye and hello to the same value~
mymap.put("bye", mymap.get("bye") +5f)
mymap.get("hello") == 10
Java HashMap stores references to Objects. If you store same object with two different keys, the keys will point to the same value.
But that is not your problem. Your problem is that you are using Float values and Float is an immutable data type. You can not change it's value once it has been created. To achieve what you want to do you need to either create a mutable Float or store the float in a container and store that container in the map. One of the most simple containers would be a single element array (though I would only use it in an example code, never in a production code as it is error prone and it is "self undocumentable").
HashMap<String, Float[]> mymap = new HashMap<>();
Float[] val = new Float[] { 5f };
mymap.put("hello", val);
mymap.put("bye", val);
...
mymap.get("bye")[0] = mymap.get("bye")[0] + 5f;
mymap.get("hello")[0] == 10f
You would need a mutable object as Value for that, for example:
static class FLoatHolder {
private float f;
public FLoatHolder(float f) {
this.f = f;
}
public float getF() {
return f;
}
public void setF(float f) {
this.f = f;
}
}
Map<String, FLoatHolder> map = new HashMap<>();
FLoatHolder fh = new FLoatHolder(5f);
map.put("bye", fh);
map.put("hello", fh);
FLoatHolder holder = map.get("bye");
holder.setF(holder.getF() + 0.5f);
map.put("bye", holder);
System.out.println(map.get("hello").getF());
If you just want two keys to point to the same value, that is perfectly fine. Maps don't care what they point to, just that there aren't conflicting keys.
If you want to add the integer values together, then your pseudocode works as you intend.
If you want pointer like behavior where changing the value of key A affects the value of key B, then you'd have to make a wrapper object and use fields.
Something like:
class Pointer<T> {
private T t;
public Pointer(T t) {
set(t);
}
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
...
Map<String, Pointer> map = new HashMap<>();
Pointer<Integer> ptr = new Pointer<>(5);
map.put("A", ptr);
map.put("B", ptr);
System.out.println(map.get("A").get());
System.out.println(map.get("B").get());
ptr.set(25);
System.out.println(map.get("A").get());
System.out.println(map.get("B").get());
If you want something else you may need to elaborate or consider another data structure.

Iterate through a hashmap?

I am trying to make kind of highscores in Java.
Basically I want a hashmap to hold the double value (so index starts from the highest double, so it's easier for me to sort highscores) and then the second value will be the client object, like this:
private HashMap<Double, TempClient> players = new HashMap<Double, TempClient>();
And to insert a new value:
TempClient client = new TempClient(kills, rank, deaths, name);
this.players.put(client.getKdr(), client);
Now, of course I can't iterate through the hashmap because it gets the list item by key, not index.
How can I iterate through a hashmap? or any good ideas for my case?
I tried it in a Foo class:
Output:
0.5
0.6
0.9
0.1
2.5
Code:
public class Foo {
public static void main(String[] args) {
HashMap<Double, String> map = new LinkedHashMap<Double, String>();
map.put(0.5, "hey");
map.put(0.6, "hey1");
map.put(0.9, "hey2");
map.put(0.1, "hey425");
map.put(2.5, "hey36");
for (Double lol : map.keySet()) {
System.out.println(lol);
}
}
}
You can iterate like this.
for (Double k : players.keySet())
{
TempClient p = players.get(k);
// do work with k and p
}
If you want to keep keys sorted, use e.g. a TreeMap.
If you want to keep the keys in the order you inserted
them in there, use e.g. a LinkedHashMap.
The best way is to iterate through hashmap is using EntrySet.
for (Map.Entry<Double, TempClient> entry : map.entrySet()) {
Double key= entry.getKey();
TempClient value= entry.getValue();
// ...
}
You'd be better off making your TempClient objects implement Comparable, adding them to a list, and then just using Collections.sort().
Since you can't sort items in a HashMap, nor you can sort them by value in a TreeMap you could use a TreeSet with a custom class:
class Score implements Comparable<Score>
{
final Player player;
final int score;
Score(Player player, int score) {
this.player = player;
this.score = score;
}
public int compareTo(Score other) {
return Integer.compare(this.score, other.score);
}
public int hashCode() { return player.hashCode(); }
public boolean equals(Object o) { return this.player.equals(...); }
}
TreeSet<Score> scores = new TreeSet<Score>();
score.add(new Score(player, 500));
for (Score s : scores) {
..
}
This will have both the advantages:
it will be iterable
it will keep scores automatically sorted
It should work easily with consistente between equals, hashCode and compareTo but maybe you should tweak something (since it's untested code).

How to use Collections.sort() in Java?

I got an object Recipe that implements Comparable<Recipe> :
public int compareTo(Recipe otherRecipe) {
return this.inputRecipeName.compareTo(otherRecipe.inputRecipeName);
}
I've done that so I'm able to sort the List alphabetically in the following method:
public static Collection<Recipe> getRecipes(){
List<Recipe> recipes = new ArrayList<Recipe>(RECIPE_MAP.values());
Collections.sort(recipes);
return recipes;
}
But now, in a different method, lets call it getRecipesSort(), I want to sort the same list but numerically, comparing a variable that contains their ID. To make things worse, the ID field is of the type String.
How do I use Collections.sort() to perform the sorts in Java?
Use this method Collections.sort(List,Comparator) . Implement a Comparator and pass it to Collections.sort().
class RecipeCompare implements Comparator<Recipe> {
#Override
public int compare(Recipe o1, Recipe o2) {
// write comparison logic here like below , it's just a sample
return o1.getID().compareTo(o2.getID());
}
}
Then use the Comparator as
Collections.sort(recipes,new RecipeCompare());
The answer given by NINCOMPOOP can be made simpler using Lambda Expressions:
Collections.sort(recipes, (Recipe r1, Recipe r2) ->
r1.getID().compareTo(r2.getID()));
Also introduced after Java 8 is the comparator construction methods in the Comparator interface. Using these, one can further reduce this to 1:
recipes.sort(comparingInt(Recipe::getId));
1 Bloch, J. Effective Java (3rd Edition). 2018. Item 42, p. 194.
Create a comparator which accepts the compare mode in its constructor and pass different modes for different scenarios based on your requirement
public class RecipeComparator implements Comparator<Recipe> {
public static final int COMPARE_BY_ID = 0;
public static final int COMPARE_BY_NAME = 1;
private int compare_mode = COMPARE_BY_NAME;
public RecipeComparator() {
}
public RecipeComparator(int compare_mode) {
this.compare_mode = compare_mode;
}
#Override
public int compare(Recipe o1, Recipe o2) {
switch (compare_mode) {
case COMPARE_BY_ID:
return o1.getId().compareTo(o2.getId());
default:
return o1.getInputRecipeName().compareTo(o2.getInputRecipeName());
}
}
}
Actually for numbers you need to handle them separately check below
public static void main(String[] args) {
String string1 = "1";
String string2 = "2";
String string11 = "11";
System.out.println(string1.compareTo(string2));
System.out.println(string2.compareTo(string11));// expected -1 returns 1
// to compare numbers you actually need to do something like this
int number2 = Integer.valueOf(string1);
int number11 = Integer.valueOf(string11);
int compareTo = number2 > number11 ? 1 : (number2 < number11 ? -1 : 0) ;
System.out.println(compareTo);// prints -1
}
Use the method that accepts a Comparator when you want to sort in something other than natural order.
Collections.sort(List, Comparator)
Sort the unsorted hashmap in ascending order.
// Sorting the list based on values
Collections.sort(list, new Comparator<Entry<String, Integer>>() {
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2)
{
return o2.getValue().compareTo(o1.getValue());
}
});
// Maintaining insertion order with the help of LinkedList
Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
for (Entry<String, Integer> entry : list) {
sortedMap.put(entry.getKey(), entry.getValue());
}

Sorting Map<ArrayList, List<Entity>> by Key - ArrayList is set of Dates

Below is code for the Comparator, however after SortedMap.putAll(), the SortedMap has lesser number of Map Entries as compared to the source Map.
Could anyone please help?
Comparator<ArrayList> arrayListComparer = new Comparator<ArrayList>() {
#Override
public int compare(ArrayList arrA, ArrayList arrB) {
DateFormat formatter = new SimpleDateFormat("MMM-yyyy");
Date dateA = new Date();
Date dateB = new Date();
try {
dateA = formatter.parse(arrA.get(0).toString());
dateB = formatter.parse(arrB.get(0).toString());
} catch (ParseException ex) {
Logger.getLogger(ValueComparator.class.getName()).log(Level.SEVERE, null, ex);
}
if (dateA.before(dateB)) {
return 0;
} else if (dateA.after(dateB)) {
return 1;
} else {
return -1;
}
}
};
SortedMap sorted_map = new TreeMap(arrayListComparer);
sorted_map.putAll(map);
When you're using SortedMap or SortedSet your Comparator should return 0 only if two objects are equals. Because it treats key equality by this criteria.
See natural ordering: http://docs.oracle.com/javase/6/docs/api/java/lang/Comparable.html
For example, if one adds two keys a and b such that (!a.equals(b) &&
a.compareTo(b) == 0) to a sorted set that does not use an explicit
comparator, the second add operation returns false (and the size of
the sorted set does not increase) because a and b are equivalent from
the sorted set's perspective.
It would help if you could show us an example of your Map, but I'll risk an answer anyway.
What's probably causing this is that you have entries in your Map to which your compare method (in your comparator), returns 0, this means the keys are equal. If they are equal, then it's the same key... and you can't have duplicate keys in a Map.
Can you see the problem?
I'll put a little example on:
public static void main(String[] args) {
Map<Bla, String> map = new HashMap<Bla, String>();
map.put(new Bla(1,1), "bla1");
map.put(new Bla(1,2), "bla2");
map.put(new Bla(1,3), "bla3");
System.out.println(map.size());
TreeMap<Bla, String> treeMap = new TreeMap<Bla,String>(new Comparator<Bla>() {
#Override
public int compare(Bla bla, Bla bla1) {
return new Integer(bla.a).compareTo(bla1.a);
}
});
treeMap.putAll(map);
System.out.println(treeMap.size());
}
private static class Bla{
private Bla(int a, int b) {
this.a = a;
this.b = b;
}
public int a;
public int b;
}
This will output
3
1
Because my comparator says that all three keys are the same (only comparing the "a" value).

ArrayList as key in HashMap

Would it be possible to add an ArrayList as the key of HashMap. I would like to keep the frequency count of bigrams. The bigram is the key and the value is its frequency.
For each of the bigrams like "he is", I create an ArrayList for it and insert it into the HashMap. But I am not getting the correct output.
public HashMap<ArrayList<String>, Integer> getBigramMap(String word1, String word2) {
HashMap<ArrayList<String>, Integer> hm = new HashMap<ArrayList<String>, Integer>();
ArrayList<String> arrList1 = new ArrayList<String>();
arrList1 = getBigram(word1, word2);
if (hm.get(arrList1) != null) {
hm.put(arrList1, hm.get(arrList1) + 1);
} else {
hm.put(arrList1, 1);
}
System.out.println(hm.get(arrList1));
return hm;
}
public ArrayList<String> getBigram(String word1, String word2) {
ArrayList<String> arrList2 = new ArrayList<String>();
arrList2.add(word1);
arrList2.add(word2);
return arrList2;
}
Yes you can have ArrayLists as a keys in a hash map, but it is a very bad idea since they are mutable.
If you change the ArrayList in any way (or any of its elements), the mapping will basically be lost, since the key won't have the same hashCode as it had when it was inserted.
The rule of thumb is to use only immutable data types as keys in a hash map. As suggested by Alex Stybaev, you probably want to create a Bigram class like this:
final class Bigram {
private final String word1, word2;
public Bigram(String word1, String word2) {
this.word1 = word1;
this.word2 = word2;
}
public String getWord1() {
return word1;
}
public String getWord2() {
return word2;
}
#Override
public int hashCode() {
return word1.hashCode() ^ word2.hashCode();
}
#Override
public boolean equals(Object obj) {
return (obj instanceof Bigram) && ((Bigram) obj).word1.equals(word1)
&& ((Bigram) obj).word2.equals(word2);
}
}
Why can't you use something like this:
class Bigram{
private String firstItem;
private String secondItem;
<getters/setters>
#Override
public int hashCode(){
...
}
#Override
public boolean equals(){
...
}
}
instead of using the dynamic collection for limited number of items (two).
From the documentation:
Note: great care must be exercised if mutable objects are used as map
keys. The behavior of a map is not specified if the value of an object is
changed in a manner that affects equals comparisons while the
object is a key in the map. A special case of this prohibition is that it
is not permissible for a map to contain itself as a key. While it is
permissible for a map to contain itself as a value, extreme caution is
advised: the equals and hashCode methods are no longer
well defined on such a map.
You have to take care when you are using mutable objects as keys for the sake of hashCode and equals.
The bottom line is that it is better to use immutable objects as keys.
Try this ,this will work.
public Map<List, Integer> getBigramMap (String word1,String word2){
Map<List,Integer> hm = new HashMap<List, Integer>();
List<String> arrList1 = new ArrayList<String>();
arrList1 = getBigram(word1, word2);
if(hm.get(arrList1) !=null){
hm.put(arrList1, hm.get(arrList1)+1);
}
else {
hm.put(arrList1, 1);
}
System.out.println(hm.get(arrList1));
return hm;
}
I've come up with this solution. It is obviously not usable in all cases, for example over stepping the hashcodes int capacity, or list.clone() complications(if the input list gets changed, key stays the same as intended, but when the items of List are mutable, cloned list has the same reference to its items, which would result in changing the key itself).
import java.util.ArrayList;
public class ListKey<T> {
private ArrayList<T> list;
public ListKey(ArrayList<T> list) {
this.list = (ArrayList<T>) list.clone();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
for (int i = 0; i < this.list.size(); i++) {
T item = this.list.get(i);
result = prime * result + ((item == null) ? 0 : item.hashCode());
}
return result;
}
#Override
public boolean equals(Object obj) {
return this.list.equals(obj);
}
}
---------
public static void main(String[] args) {
ArrayList<Float> createFloatList = createFloatList();
ArrayList<Float> createFloatList2 = createFloatList();
Hashtable<ListKey<Float>, String> table = new Hashtable<>();
table.put(new ListKey(createFloatList2), "IT WORKS!");
System.out.println(table.get(createFloatList2));
createFloatList2.add(1f);
System.out.println(table.get(createFloatList2));
createFloatList2.remove(3);
System.out.println(table.get(createFloatList2));
}
public static ArrayList<Float> createFloatList() {
ArrayList<Float> floatee = new ArrayList<>();
floatee.add(34.234f);
floatee.add(new Float(33));
floatee.add(null);
return floatee;
}
Output:
IT WORKS!
null
IT WORKS!
Sure it possible. I suppose the issue in your put. Try obtain key for bigram, increment it, remove entry with this bigram and insert updated value
Unlike Array, List can be used as the key of a HashMap, but it is not a good idea, since we should always try to use an immutable object as the key.
.toString() method getting the String represtenation is a good key choice in many cases, since String is an immuteable object and can prefectly stands for the array or list.
Please check below my code in order to understand if key is ArrayList in Map and how JVM will do it for inputs:
here i write hashCode and equals method for TesthashCodeEquals class.
package com.msq;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class TesthashCodeEquals {
private int a;
private int b;
public TesthashCodeEquals() {
// TODO Auto-generated constructor stub
}
public TesthashCodeEquals(int a, int b) {
super();
this.a = a;
this.b = b;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
public int hashCode() {
return this.a + this.b;
}
public boolean equals(Object o) {
if (o instanceof TesthashCodeEquals && o != null) {
TesthashCodeEquals c = (TesthashCodeEquals) o;
return ((this.a == c.a) && (this.b == c.b));
} else
return false;
}
}
public class HasCodeEquals {
public static void main(String[] args) {
Map<List<TesthashCodeEquals>, String> m = new HashMap<>();
List<TesthashCodeEquals> list1=new ArrayList<>();
list1.add(new TesthashCodeEquals(1, 2));
list1.add(new TesthashCodeEquals(3, 4));
List<TesthashCodeEquals> list2=new ArrayList<>();
list2.add(new TesthashCodeEquals(10, 20));
list2.add(new TesthashCodeEquals(30, 40));
List<TesthashCodeEquals> list3=new ArrayList<>();
list3.add(new TesthashCodeEquals(1, 2));
list3.add(new TesthashCodeEquals(3, 4));
m.put(list1, "List1");
m.put(list2, "List2");
m.put(list3, "List3");
for(Map.Entry<List<TesthashCodeEquals>,String> entry:m.entrySet()){
for(TesthashCodeEquals t:entry.getKey()){
System.out.print("value of a: "+t.getA()+", value of b: "+t.getB()+", map value is:"+entry.getValue() );
System.out.println();
}
System.out.println("######################");
}
}
}
.
output:
value of a: 10, value of b: 20, map value is:List2
value of a: 30, value of b: 40, map value is:List2
######################
value of a: 1, value of b: 2, map value is:List3
value of a: 3, value of b: 4, map value is:List3
######################
so this will check the number of objects in List and the values of valriabe in object. if number of objects are same and the values of instance variables is also same then it will consider duplicate key and override the key.
now if i change only the value of object on list3
list3.add(new TesthashCodeEquals(2, 2));
then it will print:
output
value of a: 2, value of b: 2, map value is:List3
value of a: 3, value of b: 4, map value is:List3
######################
value of a: 10, value of b: 20, map value is:List2
value of a: 30, value of b: 40, map value is:List2
######################
value of a: 1, value of b: 2, map value is:List1
value of a: 3, value of b: 4, map value is:List1
######################
so that It always check the number of objects in List and the value of instance variable of object.
thanks
ArrayList.equals() is inherited from java.lang.Object - therefore equals() on ArrayList is independent of the content of the list.
If you want to use an ArrayList as a map key, you will need to override equals() and hashcode() in order to make two arraylists with the same content in the same order return true on a call to equals() and return the same hashcode on a call to hashcode().
Is there any particular reason you have to use an ArrayList as opposed to say a simple String as the key?
edit: Ignore me, as Joachim Sauer pointed out below, I am so wrong it's not even funny.

Categories

Resources