Sort a Map based on size - java

I have a map like so:
Map<List<Item>, Double> items = new HashMap<List<Item>, Double>();
I would like to sort this hashmap based on the size of the List<Item> where the largest sized ones are first. I don't care about the ordering within same sized objects though.
So far I've tried to use a TreeSet like so:
SortedSet<Map.Entry<List<Item>, Double>> sortedItems = new TreeSet<Map.Entry<List<Item>, Double>>(
new Comparator<Map.Entry<List<Item>, Double>>() {
#Override
public int compare(
Entry<List<Item>, Double> o1,
Entry<List<Item>, Double> o2) {
return o2.getKey().size() - o1.getKey().size();
}
});
sortedItems.addAll(items.entrySet());
However, the sortedItems object is only taking one of every sized list. It is treating equally sized lists as duplicates and is ignoring them. How can I fix this issue.
EDIT: So from what I can tell, when 2 lists of the same size are being compared, my compare method is returning 0. This tells the set that the entries are equal and they are treated as duplicates. So I guess the only way to fix this is to ensure that the compare method never returns 0. So I wrote this code:
#Override
public int compare(
Entry<List<AuctionItem>, Double> o1,
Entry<List<AuctionItem>, Double> o2) {
if (o1.getKey().size() <= o2.getKey().size()) {
return -1;
} else {
return 1;
}
}

You are using a TreeSet with Comparator, and as per your implementation of the compare(), it returns 0 if the size of the list is same. As TreeSet cannot contain duplicates, it adds only one of those lists whose size is equal. You do not need to create a TreeSetin order to sort your Map, because SETS should be used when the elements resemble mathematical sets (NO duplicate elements). You could possibly do :
List<Map.Entry<List<Item>, Double>> list =
new LinkedList<Map.Entry<List<Item>, Double>>( map.entrySet() );
Collections.sort( list, new Comparator<Map.Entry<List<Item>, Double>>()
{
public int compare( Map.Entry<List<Item>, Double> o1, Map.Entry<List<Item>, Double> o2 )
{
return (o1.getKey().size().compareTo( o2.getKey.size() );
}
} );
That is, put the entries of map in a List and then sort the list.

If you attempt to put an item into a TreeSet for which your custom Comparator returns 0, the item will not be placed into the Set.
You have to use a Comparator which doesn't return 0. For Lists whose sizes are equal, you have to define a consistent, arbitrary order.
Here is an easy and convinient way to do so:
new Comparator<Map.Entry<List<Item>, Double>>() {
#Override
public int compare(Entry<List<Item>, Double> o1,
Entry<List<Item>, Double> o2) {
int diff = o2.getKey().size() - o1.getKey().size();
return diff != 0 ? diff :
System.identityHashCode(o2.getKey()) -
System.identityHashCode(o1.getKey());
}
};
Basically what I do is this: if the 2 lists have different size, size2 - size1 will do. If they have the same size, I return the difference of their identity hashcodes which will always differ from 0 because the identity hashcode is the memory address which is different for distinct objects. And it is always the same for an object, so the comparator will always return the same order for 2 lists having the same size.
Using this comparator you will get a sorted set which allows lists having the same size, and it will be sorted by list size.

It seems odd that you are using a list of values (items) as the key to your hashmap; however, I'll give it a go.
At the core any Map is not an ordered collection. In short, if "Pete" has a height of 67 inches and "Bob" has a height of 72 inches, then without some extra bit of information, it is not possible to determine if Bob should come before or after Pete.
Ordered by "key" or in this case, "name" one might impose alphabetical ordering, in which case "Bob" comes before "Pete".
Ordered by "value" or in this case, "height" one might impose smallest to largest ordering, in which case "Pete" comes before "Bob".
I'm sure that you know what you want to order by (the size of the list), but this example means to illustrate that a Map alone is a poor data structure for ordering. Even ordered maps in Java only sort by insertion order.
My suggestion is to keep two collections in the same wrapping class. One that contains an ordered list of the keys, and another that contains the Map. Walk the ordered list of keys and return the map values in that order if you want an ordered set of the values by their key characteristics. It will be easier to understand, and much more readable.
Also realize that the Map is not listening to the key values, as such, if someone who has a key decides to add an entry to the List that the key maintains, the Map will not know of the alteration and will not recompute the bucket for the key's former value. As such, to use a Map properly, you need to approach it in one of two ways.
Make the map keys immutable, copying the values upon input and returning Collections.unmodifiableList(...) wrappers on output.
Accept Map keys that can be listened to, and make the map a subscriber to the key updates that might occur. When the map detects a key change, the values are removed from the old key location and re-added back to the map with the new key.

If you don't need constant sorting over time, you can do that:
Map<List<Item>, Double> items = new HashMap<>();
List<Map.Entry<List<Item>, Double>> list = new ArrayList<>(items.entrySet());
Collections.sort(list, new Comparator<Map.Entry<List<Item>, Double>>() {
#Override
public int compare(
Map.Entry<List<Item>, Double> o1,
Map.Entry<List<Item>, Double> o2) {
return Integer.compare(o2.getKey().size(), o1.getKey().size());
}
}
});
If you need a TreeSet or a TreeMap, then the use of a Comparator is fine but you have to define what happens when the lists size are equals: you need to use another criteria to determine ordering (like comparing each items a, b of both list, until a.compareTo(b) != 0).

Related

Remove a specific value from a key (HashMaps)

I have the following HashMap (HashMap<String, String[]>) and was wondering, if there is a method to remove a specific String from the array of a specific key.
I've found only methods to remove one key basing on a value, but for example, I have:
("key1", new String[]{"A", "B", "C"})
How can I remove only B?
Here's s plain Java solution:
map.computeIfPresent("key1", (k, v) -> Arrays.stream(v)
.filter(s -> !s.equals("B")).toArray(String[]::new));
You would get the values for the specific key and remove the given value from it, then put it back into the map.
public void <K> removeValueFromKey(final Map<K, K[]> map, final K key, final K value) {
K[] values = map.get(key);
ArrayList<K> valuesAsList = new ArrayList<K>(values.length);
for (K currentValue : values) {
if (!currentValue.equals(value)) {
valuesAsList.add(currentValue);
}
}
K[] newValues = new K[valuesAsList.size()];
newValues = valuesAsList.toArray(newValues);
map.put(key, newValues);
}
Be aware that the runtime of course is linear to the size of the given array. There is no faster way, because you need to iterate over each element of the array to find all values that are equal to the given value.
However, you could do a faster implementation with other data structures, if that is practicable. For example sets would be better than arrays, or any other data structure that implements contains is faster than O(n).
The same holds for space complexity; you have a peak where you need to hold both arrays in the memory. This is because the size of an array cannot be changed; the method will construct a new array. Thus you will have two arrays in the memory, O(2n).
A Collection<String> may be a better solution, depending on how often you'll call the method, compared to how many elements a map holds.
Another thing is that you can speed up the progress by guessing a good initial capacity for the ArrayList.

Best way to load strings into a cache

I am building a couple of methods which are supposed to create a cache of input strings, load them in to a list, and then determine the number of occurrences of each string in that list, ranking them in order of the most common elements.
The string, or elements themselves are coming from a JUnit test. It's calling up a method called
lookupDistance(dest)
where "dest" is a String (destination airport code), and the lookupDistance returns the distance between two airport codes....
There's the background. The problem is that I want to load all of the "dest" strings in to a cache. What's the best way to do that?
I have skeleton code that has a method called:
public List<String> mostCommonDestinations()
How would I add "dest" strings to the List in a transparent way? The JUnit test case is only calling lookupDistance(dest), so how can I also redirect those "dest" strings to the List in this method?
How would I then quantify the number of occurrences of each element and say, rank the top three or four?
Have a Map<String, Integer> destinations = new HashMap<>();
In lookupDistance(dest), do something like this (untested pseudocode):
Integer count = destinations.get(dest);
if (count == null) {
destinations.put(dest, Integer.valueOf(1));
} else {
count = Integer.valueOf(count.intValue() + 1);
}
This way, you count the occurences of each dest.
Go through the Map and find the highest counts. That's a bit tricky. One approach might be:
List> list = new ArrayList<>();
list.addAll(destinations.entrySet());
// now you have a list of "entries", each of which maps from dest to its respective counter
// that list now has to be sorted
Collections.sort(list, comparator);
The comparator we used in this invocation has still to be written. It has to take two arguments, which are elements of the list, and compare them according to their counter value. the sort routine will do the rest.
Comparator<Map.Entry<String, Integer>> comparator = new Comparator<>() {
public #Override int compare(Map.Entry<String, Integer> a, Map.Entry<String, Integer> b) {
return a.getValue().intValue() - b.getValue().intValue();
}
}
Ok, so we have a sorted List of Entrys now from which you can pick the top 5 or so. Think that's pretty much it. All this looks more complicated than it should be, so I'm curios for other solutions.
You can add known destination at startup and keep adding new strings to cache as they arrive. That's one way. The other way is to cache strings as they are requested, keeping them for future request. In that case your lookupDistance should also cache string.
Start by making a small class that contains a Hashmap. The key would be your destination string, and the value can either be an object if you want to keep multiple information or just a number specifying how many times that string is used. I would recommend using a data object.
Please note that code below is just to you an idea, more like a pseudo-code.
class Cache {
private Hashmap<String, CacheObject>;
public void Add(string, CacheObject);
public CacheObject Lookup(string);
public CacheObject Remove(string);
public static Cache getInstance(); //single cache
}
class CacheObject {
public int lookupCount;
public int lastUsed;
}
In your lookupDistance you can simply do
if(Cache.getInstance().Lookup(string) == null) {
Cache.getInstance().Add(string, new CacheObject() { 1, Date.now});
}

How to sort a map

I have a Map to sort as follows:
Map<String, String> map = new HashMap();
It contains the following String keys:
String key = "key1.key2.key3.key4"
It contains the following String values:
String value = "value1.value2"
where the key and value can vary by their number of dot sections from key1/value1 to key1.key2.key3.key4.key5/value1.value2.value3.value4.value5 non-homogeneously
I need to compare them according to the number of dots present in keys or in values according to the calling method type key / value :
sortMap(Map map, int byKey);
or
sortMap(Map map, int byValue);
The methods of course will return a sorted map.
Any help would be appreciated.
There is no way to impose any sort of order on HashMap.
If you want to order elements by some comparison on the keys, then use a TreeMap with some Comparator on the keys, or just use their default Comparable ordering.
If you want to order by the values, the only real option is to use a LinkedHashMap, which preserves the order that entries were put into the map, and then to sort the entries before inserting them into the map, or perhaps some non-JDK Map implementation. There are dirty hacks that make a key comparator that actually secretly compares the values, but these are dangerous and frequently lead to unpredictable behavior.
For starters, you will need to be using an instance of SortedMap. If the map doesn't implement that interface, then it has an undefined/arbitrary iteration order and you can't control it. (Generally this is the case, since a map is a way of associating values with keys; ordering is an auxiliary concern.)
So I'll assume you're using TreeMap, which is the canonical sorted map implementation. This sorts its keys according to a Comparator which you can supply in the constructor. So if you can write such a comparator that determines which is the "lower" of two arbitrary keys (spoiler alert: you can), this will be straightforward to implement.
This will, however, only work when sorting by key. I don't know if it makes much sense to sort a map by value, and I'm not aware of any straightforward way to do this. The best I can think of is to write a Comparator<Map.Entry> that sorts on values, call Map.getEntrySet and push all the entries into a list, then call Collections.sort on the list. It's not very elegant or efficient but it should get the job done if performance isn't your primary concern.
(Note also that if your keys aren't immutable, you will run into a lot of trouble, as they won't be resorted when externally changed.
You should use a TreeMap and implement a ValueComparator or make the key and value objects that implement Comparable.
Must be a duplicate here...
edit: duplicate of (to name just one) Sort a Map<Key, Value> by values (Java)
I did it by the following:
#SuppressWarnings({ "unchecked", "rawtypes" })
public static Map sortMap(Map unsortedMap) {
List list = new LinkedList(unsortedMap.entrySet());
// sort list based on comparator
Collections.sort(list, new Comparator() {
public int compare(Object o1, Object o2) {
String value1 = (String)((Map.Entry) (o1)).getValue();
String value2 = (String)((Map.Entry) (o2)).getValue();
// declare the count
int count1 = findOccurances(value1, '.');
int count2 = findOccurances(value2, '.');
// Go to thru the comparing
if(count1 > count2){
return -1;
}
if(count1 < count2){
return 1;
}
return 0;
}
});
// put the sorted list into map again
Map sortedMap = new LinkedHashMap();
for (Iterator it = list.iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
With the following helper method:
private static int findOccurances(String s, char chr) {
final char[] chars = s.toCharArray();
int count = 0;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == chr) {
count++;
}
}
return count;
}
Here, I can put some switch on the comparing part with an additional int argument to change between asc/desc.
I can change between values and keys through a switch of another int argument value to get my answer.

How do I sort elements in a hash table in alphabetical order?

How do I sort hash table elements alphabetically? For example, my elements are:
cijfercode, Zweeds, Doorloper, Kruizword, Crypto, Woordzoker
edit: I also got a solution for sorting the hashtable elements. Here is the solution:
java.util.Vector vec = new java.util.Vector(hashtableList.keySet());
Collections.sort(vec);
If these "elements" are keys you can store them in a TreeMap, which will produce a consistent order based on the natural ordering of the keys. Note you don't need to do much except create a new map with the old map passed to the constructor:
Map<String,?> map = ?
Map<String,?> orderedMap = new TreeMap<String,?>(map);
Then, iterate like normal:
for(String key : orderedMap.keys()){
}
If your "elements" are values, then you can insert them as keys into a TreeMap keeping track of the original keys, read the sorted order of values as before (basically creating an inverted index):
Map<?,String> map = ?
Map<String,List<?>> orderedVals = new TreeMap<String,List<?>>();
for(Entry<?,String> map : map.entrySet()){
List<?> keys = orderedVals.get(map.getValue());
if(keys == null){
keys = new ArrayList<?>();
orderedVals.put(map.getValue(), keys);
}
keys.add(map.getKey());
}
// now orderedVals has keys in sorted order
for(String val : orderedVals.keys()){
}
Of course, if you're not actually using anything related to the fact these things are in a "hashtable" (I read this as something implementing Map), then you can load up a List of your choosing, and sort it:
List<String> list = new ArrayList<String>(map.values()); // or use map.keys()
Collections.sort(list);
If you're not happy with the default sort order for String, feel free to write your own comparator:
Collections.sort(list, new Comparator<String>(){
public int compare(String left, String right){
return // your impl
}
});
compare must return a negative integer when the left comes first, 0 if left and right are the same, and a positive integer if right comes first.
Mark Elliot's idea is correct. I don't like the whole Map<?, List<?>> idea though; I've been far too spoilt on Guava. So here's a Guava version of the same idea:
SortedSetMultimap<String, ?> sorted = Multimaps.invertFrom(
Multimaps.forMap(map), TreeMultimap.create());
for (Map.Entry<String, ?> entry : sorted.entries()) {
// ...
}
This is, like, a third of the size of Mark's code. :-)
java.util.Vector vec =new java.util.Vector(hashtableList.keySet());
Collections.sort(vec);
Please check http://discuss.joelonsoftware.com/default.asp?joel.3.19588.13 for an interesting discussion on this.
Consider http://download.oracle.com/javase/1.4.2/docs/api/java/util/TreeMap.html too.

Improving performance of merging lots of sorted maps into one sorted map - java

I have a method that gets a SortedMap as input, this map holds many SortedMap objects, the output of this method should be one SortedMap containing all elements of the maps held in the input map. the method looks like this:
private SortedMap mergeSamples(SortedMap map){
SortedMap mergedMap = new TreeMap();
Iterator sampleIt = map.values().iterator();
while(sampleIt.hasNext())
{
SortedMap currMap = (SortedMap) sampleIt.next();
mergedMap.putAll(currMap);
}
return mergedMap;
}
This is a performance killer, what can I improve here?
I don't see anything wrong with your code; all you can really do is try alternative implementations of SortedMap. First one would be ConcurrentSkipListMap and then look at Commons Collections, Google Collections and GNU Trove. The latter can yield very good results especially if your maps' keys and values are primitive types.
Is it a requirement for the input to be a SortedMap? To me it would seem easier if the input was just a Collection or List. That might speed up creating the input, and might make iteration over all contained maps faster.
Other than that I believe the most likely source of improving the performance of this code is by improving the speed of the compareTo() implementation of the values in the the sorted maps being merged.
Your code is as good as it gets. However, it seems to me that the overall design of the data structure needs some overhaul: You are using SortedMap<?, SortedMap<?, ?>, yet the keys of the parent map are not used.
Do you want to express a tree with nested elements with that and your task is it to flatten the tree? If so, either create a Tree class that supports your approach, or use an intelligent way to merge the keys:
public class NestedKey implements Comparable<NestedKey> {
private Comparable[] entries;
public NestedKey(Comparable... entries) {
assert entries != null;
this.entries = entries;
}
public int compareTo(NestedKey other) {
for(int i = 0; i < other.entries.length; i++) {
if (i == entries.length)
return -1; // other is longer then self <=> self is smaller than other
int cmp = entries[i].compareTo(other.entries[i]);
if (cmp != 0)
return cmp;
}
if (entries.length > other.entries.length)
return 1; // self is longer than others <=> self is larger than other
else
return 0;
}
}
The NestedKey entry used as a key for a SortedMap compares to other NestedKey objects by comparing each of its entries. NestedKeys that are in all elements present, but that have more entries are assumed to be larger. Thus, you have a relationship like this:
NestedKey(1, 2, 3) < NestedKey(1, 2, 4)
NestedKey(1, 3, 3) < NestedKey(2, 1, 1)
NestedKey(1, 2, 3) < NestedKey(2)
If you use only one SortedMap that uses NestedKey as its keys, then its .values() set automatically returns all entries, flattened out. However, if you want to use only parts of the SortedMap, then you must use .subMap. For example, if you want all entries wite NestedKeys between 2 and 3 , use .subMap(new NestedKey(2), new NestedKey(3))

Categories