I'm implementing a NavigableMap-implementing LinkedHashMap in Java. (There don't seem to be many (any?) reasons why LinkedHashMap doesn't implement NavigableMap already, but I digress...)
I've written lowerKey(), lowerEntry(), higherKey(), and higherEntry() by iterating the entrySet(). I don't see any way in these cases to avoid iterating the entire entrySet().
For floorKey(), floorEntry(), ceilingKey(), and ceilingEntry(), in the case that the key exists, I'd like to avoid the expense of iterating the entrySet(), considering that I can already get the value with plain-old get().
Is there a way to get the Map.Entry for a particular key, rather than just the value? Thanks.
You have the key, and you can get the value associated with the key using get, now all you gotta do is to make a Map.Entry, and we can do that with the Map.entry factory method:
var value = theBackingLinkedHashMap.get(key);
if (value == null) {
return null;
}
return Map.entry(key, value);
The entry returned by entry does have two caveats that you should be aware of:
does not allow null keys, so your NavigableLinkedHashMap would need to not allow null keys either
is immutable, so you cannot call setValue.
But other than that, it will work as if you got the Map.Entry from inside the backing LinkedHashMap, and it does fulfil the contract of ceilingEntry, floorEntry etc, since they just ask for a "a key-value mapping", and doesn't require that it has to have the mutability as the map itself or anything like that. For example, this is ceilingEntry:
Returns a key-value mapping associated with the least key greater than or equal to the given key, or null if there is no such key.
I would use TreeSet to keep the keys in the NavigableMap class. See example below:
import java.util.*;
public class Main{
public static void main(String[] args) {
NavMap<Integer, String> map = new NavMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.put(4, "four");
map.put(10, "ten");
System.out.println(map.lowerKey(3)); //2
System.out.println(map.higherKey(3)); //4
System.out.println(map.higherEntry(3)); // 4:four
System.out.println(map.ceilingKey(7)); //10
System.out.println(map.floorKey(7)); //4
System.out.println(map.floorEntry(10)); // 4:four
}
}
class NavMap<K extends Comparable,V extends Comparable> extends LinkedHashMap<K,V>{
private TreeSet<K> keys = new TreeSet<>();
public Set<K> keySet(){ return keys; }
public K lowerKey (K k){ return keys.lower(k); }
public K higherKey (K k){ return keys.higher(k); }
public K floorKey (K k){ return keys.floor(k); }
public K ceilingKey(K k){ return keys.ceiling(k);}
public Map.Entry<K,V> lowerEntry(K k) { return newEntry(lowerKey(k), get(lowerKey(k)));}
public Map.Entry<K,V> higherEntry(K k) { return newEntry(higherKey(k), get(higherKey(k)));}
public Map.Entry<K,V> floorEntry(K k) { return newEntry(floorKey(k), get(floorKey(k)));}
public Map.Entry<K,V> ceilingEntry(K k) { return newEntry(ceilingKey(k),get(ceilingKey(k)));}
private Map.Entry<K,V> newEntry(K k, V v) { return new AbstractMap.SimpleEntry<>(k,v);}
public V put(K key, V value){
keys.add(key);
return super.put(key, value);
}
}
Output:
2
4
4=four
10
4
10=ten
#Sweeper's comment on his answer got me thinking. The solution I came up with was to maintain a Map from Key to Entry inside my class. That way I have a way of having O(1) access to the entrySet. Like so:
Map<K, Map.Entry<K, V>> entryMap = new HashMap<>();
for(final currentEntry : entrySet())
{
entryMap.put(currentEntry.getKey(), currentEntry);
}
I just need to update this Map every time an operation runs which changes the keySet. I only need to update the one entry in this Map that would be affected. That sounds like it wouldn't be very expensive.
You could do something like this.
Map<String,Integer> map = Map.of("Foo", 123, "Bar", 234);
Function<String, Entry<String,Integer>> getEntry =
getEntryFnc(map);
System.out.println(getEntry.apply("Foo"));
System.out.println(getEntry.apply("Bar"));
System.out.println(getEntry.apply("Baz"));
prints
Foo=123
Bar=234
Baz=null
Returns a lambda which builds an entry using the supplied key and map.
public static <K,V> Function<K, Entry<K,V>> getEntryFnc(Map<K,V> map) {
return key->
new AbstractMap.SimpleEntry<>(key, map.get(key));
};
}
I have a array of String which has few words and I'm trying to find the most frequent (number of occurrences) words of k from the given array. I also have a constraint that consider the most frequent words are of frequency 2 then I need to consider the one which comes first in alphabetical order then the next ones.
I created a TreeMap to hold the words vs it's frequency count. Now the keys (ie' words) in the map would be sorted alphabetically. Now I was trying to sort the map based on it's values with the following snippet.
public static <K, V extends Comparable<V>> Map<K, V>
sortByValues(final Map<K, V> map) {
Comparator<K> valueComparator =
new Comparator<K>() {
public int compare(K k1, K k2) {
int compare =
map.get(k2).compareTo(map.get(k1));
if (compare == 0)
return 1;
else
return compare;
}
};
Map<K, V> sortedByValues =
new TreeMap<K, V>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
Now I'm iterating the map in the following ways,
Method1
Map sortedMap = sortByValues(map);
ArrayList<String> result = new ArrayList<String>();
for (Map.Entry<String, Integer> entry : sortedMap.entrySet()) {
if (k-- <= 0) {
return result;
}
result.add(entry.getKey());
}
Method2
Map sortedMap = sortByValues(map);
ArrayList<String> result = new ArrayList<String>();
// Get a set of the entries on the sorted map
Set set = sortedMap.entrySet();
// Get an iterator
Iterator i = set.iterator();
// Display elements
while(i.hasNext()) {
if (k-- <= 0) {
return result;
}
Map.Entry me = (Map.Entry)i.next();
result.add((String) me.getKey());
}
Consider the following content in TreeMap initially,
// Put elements to the map
treemap.put("love", 2);
treemap.put("i", 2);
treemap.put("coding", 1);
Method1 returns ["coding","i"] as output whereas Method2 way of iterating gives me ["i","love"].
Doubts
I'm confused with the two ways of iterating the map?
If I sort the TreeMap which is already sorted based on keys, if i sort again based by values will the sorting by keys be retained for equal value elements?
Well, you don't mind I will write my "test" project;
First of all I had created class that implements AbstractMap.
public class TestClass <K, V> extends AbstractMap <K, V>
TestClass has the private LinkedList that take as a parameter (It is another class that implements Map.Entry:
private int size = 1000;
private LinkedList <InfoClass <K, V>> [] array = new LinkedList [size];
After, I had created the method that checks and replaces duplicates:
public V put (K key, V value){ // Void doesn't work, therefore we need to return any value;
V temp = null;
boolean found = false;
int index = Math.abs(key.hashCode()) % size;
if (array[index] == null)
array[index] = new LinkedList <InfoClass <K, V>> (); // If the specified member of array is null, create new member;
LinkedList <InfoClass <K, V>> list = array[index];
InfoClass <K, V> info = new InfoClass <K, V> (key, value);
ListIterator <InfoClass <K, V>> it = list.listIterator();
while (it.hasNext()){
InfoClass<K, V> temp2 = it.next(); // Create a temp instance;
if (temp2.getKey().equals(key)){ // If there is a duplicate of value, just replace by new one;
found = true;
temp = temp2.getValue();
it.set(info);
break;
}
}
if (!found) // if there is not a duplicate, add new one;
array[index].add(info);
return temp;
}
Next method returns a value, otherwise this returns null if a member of array doesn't exist:
public V get (Object key){ // The parameter K doesn't work;
int index = Math.abs(key.hashCode()) % size;
if (array[index] == null)
return null;
else
for (InfoClass <K, V> info : array[index])
if (info.getKey().equals(key))
return info.getValue();
return null;
}
This method forms a set of AbstractMap:
public Set<java.util.Map.Entry<K, V>> entrySet() {
Set <Map.Entry<K, V>> sets = new HashSet <Map.Entry<K, V>> ();
for (LinkedList <InfoClass<K, V>> temp1 : array){
if (temp1 == null)
continue;
else{
for (InfoClass<K,V> temp2 : temp1)
sets.add(temp2);
}
}
return sets;
}
Ok, create new object in main method:
public static void main (String [] args){
TestClass <Integer, String> TC = new TestClass <Integer, String> ();
TC.putAll(CollectionDataMap.newCollection(new Group.Ints(), new Group.Name(), 10));
System.out.println(TC);
System.out.println(TC.get(1));
TC.put(1, "Hello this world");
System.out.println(TC);
}
Hope I explained correctly. I have a question, what is difference between LinkedList and LinkedHashMap (HashMap) if they work the same way?
Thank you very much!
LinkedList can contain the same element multiple times if the same element is added multiple times.
HashSet can only contain the same object once even if you add it multiple times, but it does not retain insertion order in the set.
LinkedHashSet can only contain the same object once even if you add it multiple times, but it also retains insertion order.
HashMap maps a value to a key, and the keys are stored in a set (so it can be in the set only once). HashMap doesn't preserve insertion order for the key set, while LinkedHashMap does retain insertion order for the keys.
I used LinkedHashMap<String, Double> . I want to take separate values from it. If it is Array we can use .get[2] ,.get[5] etc. for take 2nd and 5th value. But for LinkedHashMap<String, Double> how to do it. I used following code. But it print all the values contained in LinkedHashMap<String, Double>. I need to take separately.
Set set = mylist.entrySet();
Iterator i = set.iterator();
while(i.hasNext()) {
Map.Entry me1 = (Map.Entry)i.next();
System.out.print(me1.getKey());
System.out.println(me1.getValue());
You may use the LinkedHashMap#get(Object key) method
Which will return the value corresponding to the key parameter. Since your keys are String, you can not use an int to retrieve them.
Example
If your LinkedHashMap contains ["key", 2.5], calling
System.out.println(lnkHashMap.get("key"));
will print
2.5
Addition
If you're using java-8, there is a workaround using a Stream object.
Double result = hashmap.values()
.stream()
.skip(2)
.findFirst()
.get();
This will skip the two first values and get to the third one directly and return it.
If not, here is a solution
public <T> T getValueByIndex (Map<? extends Object, T> map, int index){
Iterator<T> it = map.values().iterator();
T temp = null;
for (int i = 0 ; i < index ; i++){
if (it.hasNext()){
temp = it.next();
} else {
throw new IndexOutOfBoundsException();
}
}
return temp;
}
It could be the case that you are using the wrong data structure for your purpose.
If you look closely to the LinkedHashMap API you will notice that it is indeed a Map and the only way to access a previously stored value is by providing its key.
But if you really think you need to access the ith value of the LinkedHashMap according to its insertion-order (or access-order) you can use a simple utility method like the following:
Java 8 Solution
private static <K, V> Optional<V> getByInsertionOrder(LinkedHashMap<K, V> linkedHashMap, int index) {
return linkedHashMap.values().stream()
.skip(index)
.findFirst();
}
Java 7 Soution
private static <K, V> V getByInsertionOrder(LinkedHashMap<K, V> linkedHashMap, int index) {
if (index < 0 || index >= linkedHashMap.size()) {
throw new IndexOutOfBoundsException();
}
Iterator<Entry<K, V>> iterator = linkedHashMap.entrySet().iterator();
for (int i = 0; i < index; i++) {
iterator.next();
}
return iterator.next().getValue();
}
I want to write a comparator that will let me sort a TreeMap by value instead of the default natural ordering.
I tried something like this, but can't find out what went wrong:
import java.util.*;
class treeMap {
public static void main(String[] args) {
System.out.println("the main");
byValue cmp = new byValue();
Map<String, Integer> map = new TreeMap<String, Integer>(cmp);
map.put("de",10);
map.put("ab", 20);
map.put("a",5);
for (Map.Entry<String,Integer> pair: map.entrySet()) {
System.out.println(pair.getKey()+":"+pair.getValue());
}
}
}
class byValue implements Comparator<Map.Entry<String,Integer>> {
public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
if (e1.getValue() < e2.getValue()){
return 1;
} else if (e1.getValue() == e2.getValue()) {
return 0;
} else {
return -1;
}
}
}
I guess what am I asking is: Can I get a Map.Entry passed to the comparator?
You can't have the TreeMap itself sort on the values, since that defies the SortedMap specification:
A Map that further provides a total ordering on its keys.
However, using an external collection, you can always sort Map.entrySet() however you wish, either by keys, values, or even a combination(!!) of the two.
Here's a generic method that returns a SortedSet of Map.Entry, given a Map whose values are Comparable:
static <K,V extends Comparable<? super V>>
SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
#Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
return res != 0 ? res : 1;
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
Now you can do the following:
Map<String,Integer> map = new TreeMap<String,Integer>();
map.put("A", 3);
map.put("B", 2);
map.put("C", 1);
System.out.println(map);
// prints "{A=3, B=2, C=1}"
System.out.println(entriesSortedByValues(map));
// prints "[C=1, B=2, A=3]"
Note that funky stuff will happen if you try to modify either the SortedSet itself, or the Map.Entry within, because this is no longer a "view" of the original map like entrySet() is.
Generally speaking, the need to sort a map's entries by its values is atypical.
Note on == for Integer
Your original comparator compares Integer using ==. This is almost always wrong, since == with Integer operands is a reference equality, not value equality.
System.out.println(new Integer(0) == new Integer(0)); // prints "false"!!!
Related questions
When comparing two Integers in Java does auto-unboxing occur? (NO!!!)
Is it guaranteed that new Integer(i) == i in Java? (YES!!!)
polygenelubricants answer is almost perfect. It has one important bug though. It will not handle map entries where the values are the same.
This code:...
Map<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);
for (Entry<String, Integer> entry : entriesSortedByValues(nonSortedMap)) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
Would output:
ape:1
frog:2
pig:3
Note how our cow dissapeared as it shared the value "1" with our ape :O!
This modification of the code solves that issue:
static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
#Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
return res != 0 ? res : 1; // Special fix to preserve items with equal values
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
In Java 8:
LinkedHashMap<Integer, String> sortedMap = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(/* Optional: Comparator.reverseOrder() */))
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1, LinkedHashMap::new));
A TreeMap is always sorted by the keys, anything else is impossible. A Comparator merely allows you to control how the keys are sorted.
If you want the sorted values, you have to extract them into a List and sort that.
This can't be done by using a Comparator, as it will always get the key of the map to compare. TreeMap can only sort by the key.
Olof's answer is good, but it needs one more thing before it's perfect. In the comments below his answer, dacwe (correctly) points out that his implementation violates the Compare/Equals contract for Sets. If you try to call contains or remove on an entry that's clearly in the set, the set won't recognize it because of the code that allows entries with equal values to be placed in the set. So, in order to fix this, we need to test for equality between the keys:
static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
#Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
int res = e1.getValue().compareTo(e2.getValue());
if (e1.getKey().equals(e2.getKey())) {
return res; // Code will now handle equality properly
} else {
return res != 0 ? res : 1; // While still adding all entries
}
}
}
);
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
"Note that the ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be consistent with equals if the sorted set is to correctly implement the Set interface... the Set interface is defined in terms of the equals operation, but a sorted set performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal."
(http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html)
Since we originally overlooked equality in order to force the set to add equal valued entries, now we have to test for equality in the keys in order for the set to actually return the entry you're looking for. This is kinda messy and definitely not how sets were intended to be used - but it works.
I know this post specifically asks for sorting a TreeMap by values, but for those of us that don't really care about implementation but do want a solution that keeps the collection sorted as elements are added, I would appreciate feedback on this TreeSet-based solution. For one, elements are not easily retrieved by key, but for the use case I had at hand (finding the n keys with the lowest values), this was not a requirement.
TreeSet<Map.Entry<Integer, Double>> set = new TreeSet<>(new Comparator<Map.Entry<Integer, Double>>()
{
#Override
public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2)
{
int valueComparison = o1.getValue().compareTo(o2.getValue());
return valueComparison == 0 ? o1.getKey().compareTo(o2.getKey()) : valueComparison;
}
});
int key = 5;
double value = 1.0;
set.add(new AbstractMap.SimpleEntry<>(key, value));
A lot of people hear adviced to use List and i prefer to use it as well
here are two methods you need to sort the entries of the Map according to their values.
static final Comparator<Entry<?, Double>> DOUBLE_VALUE_COMPARATOR =
new Comparator<Entry<?, Double>>() {
#Override
public int compare(Entry<?, Double> o1, Entry<?, Double> o2) {
return o1.getValue().compareTo(o2.getValue());
}
};
static final List<Entry<?, Double>> sortHashMapByDoubleValue(HashMap temp)
{
Set<Entry<?, Double>> entryOfMap = temp.entrySet();
List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
return entries;
}
import java.util.*;
public class Main {
public static void main(String[] args) {
TreeMap<String, Integer> initTree = new TreeMap();
initTree.put("D", 0);
initTree.put("C", -3);
initTree.put("A", 43);
initTree.put("B", 32);
System.out.println("Sorted by keys:");
System.out.println(initTree);
List list = new ArrayList(initTree.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
#Override
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
return e1.getValue().compareTo(e2.getValue());
}
});
System.out.println("Sorted by values:");
System.out.println(list);
}
}
//convert HashMap into List
List<Entry<String, Integer>> list = new LinkedList<Entry<String, Integer>>(map.entrySet());
Collections.sort(list, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
If you want to use a Hash map you can add a condition in Comparator to check by values first & if values are equal perform a sort on keys
HashMap<String , Integer> polpularity = new HashMap<>();
List<String> collect = popularity.entrySet().stream().sorted((t2, t1) -> {
if (t2.getValue() > t1.getValue()) {
return -1;
} else if (t2.getValue() < t1.getValue()) {
return +1;
} else {
return t2.getKey().compareTo(t1.getKey());
}
}).map(entry -> entry.getKey()).collect(Collectors.toList());
If you don't want to take care of the latter condition then use a Treemap which will offer you sorting by itself, this can be done in an elegant single line of code:
TreeMap<String, Integer> popularity = new TreeMap<>();
List<String> collect = popularity.entrySet().stream().sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).map(entry -> entry.getKey()).collect(Collectors.toList());
TreeMap is always sorted by the keys.
If you want TreeMap to be sorted by the values, so you can simply construct it also.
Example:
// the original TreeMap which is sorted by key
Map<String, Integer> map = new TreeMap<>();
map.put("de",10);
map.put("ab", 20);
map.put("a",5);
// expected output:
// {a=5, ab=20, de=10}
System.out.println(map);
// now we will constrcut a new TreeSet which is sorted by values
// [original TreeMap values will be the keys for this new TreeMap]
TreeMap<Integer, String> newTreeMapSortedByValue = new TreeMap();
treeMapmap.forEach((k,v) -> newTreeMapSortedByValue.put(v, k));
// expected output:
// {5=a, 10=de, 20=ab}
System.out.println(newTreeMapSortedByValue);
Only 1 Line Of Code Solution
Normal Order
map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(x->{});
Reverse Order
map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).forEachOrdered(x -> {});