Suppose I have a map in Java which looks like this:
{
39:"39 to 41",
41:"41 to 43",
43:"43 to 45",
45:">=45"
}
If the keys are in sorted order(either using treemap or linkedhashmap).Now if i try to get a value which is >=39 and <41.Then I should get the String "39 to 41".How do I do this efficiently?
It looks like you want more than a SortedMap; you want a NavigableMap! Specifically you can use the floorKey operation.
Here's an example:
NavigableMap<Integer,String> map =
new TreeMap<Integer, String>();
map.put(0, "Kid");
map.put(11, "Teens");
map.put(20, "Twenties");
map.put(30, "Thirties");
map.put(40, "Forties");
map.put(50, "Senior");
map.put(100, "OMG OMG OMG!");
System.out.println(map.get(map.floorKey(13))); // Teens
System.out.println(map.get(map.floorKey(29))); // Twenties
System.out.println(map.get(map.floorKey(30))); // Thirties
System.out.println(map.floorEntry(42).getValue()); // Forties
System.out.println(map.get(map.floorKey(666))); // OMG OMG OMG!
Note that there are also ceilingKey, lowerKey, higherKey, and also …Entry instead of …Key operations as well which returns a Map.Entry<K,V> instead of just the K.
Try Java 6 java.util.NavigableMap. http://download.oracle.com/javase/6/docs/api/java/util/NavigableMap.html.
In special use floorKey/floorEntry.
By example: floorKey(40) should return 39. floorEntry would return the value you are looking for.
With a sorted map, you could do something like that:
SortedMap<Integer,String> head = map.headMap(value+1);
if (head.isEmpty()) {
return null;
} else {
return head.get(head.lastKey());
}
I'm not sure that's going to be easy. One suggestion would be to "fill in the gaps", ie put in a value 40->"39 to 41" etc etc. I suppose that will only be possible if you know the whole range of numbers possible in the map.
Or mabybe something that overrides the get to check to see if the value is in the map, and expanding out until it finds something. I'm not sure that's going to be possible in its current guise, as you'd have to end up parsing the value strings.
You can recursively look for lower boundary.
public String descriptionFor(int value) {
String description = map.get(value);
return description == null ? descriptionFor(value--) : description;
}
You will need to have a minimum boundary.
You'd have to implement such a map yourself, I believe. You're right that it would have to be sorted; the implementation of get would have to iterate through the keys until it finds the largest key that is less than or equal to the argument.
If you subclass TreeMap it would initially appear that you can get this working via simply overriding the get() method. However, to maintain as much of the Map contract as possible you'll have to override other methods for consistency.
And what about e.g. containsKey()? Does your main contain a mapping for 40? If you return false, then a client can decide not to call get() based on this information; for these reason (and the formal definition) you have to return true. But then it makes it hard to determine whether the map "really contains" a given mapping; if you're looking to do something such as update without overwriting anything that already exists.
The remove() method might be tricky too. From my reading of the interface,
// Calling map.remove "Removes the mapping for a key from this map if it is present."
map.remove(x);
// Now that the mapping is removed, I believe the following must hold
assert map.get(x) == null;
assert map.containsKey(x);
Acting consistently here would be very tricky. If you have a mapping from 35-40 for example, and you call remove(38), then as I understand it you'd have to return null for any subsequent gets for the key 38, but return the aforementioned mapping for keys 35-37 or 39-40.
So while you can make a start on this by overriding TreeMap, perhaps the whole concept of Map is not quite what you want here. Unless you need this behaviour to slot into existing methods that take Map, it might be easier to create it yourself as a distinct class since it's not quite a Map, the way you're defining it.
Related
I'm using a MultiValueMap from Apache Collections, to collect different types of word (Nouns, Verbs etc) and I want to check that I have at least one of each word type before continuing.
The general outline is like so (after initiating the keys):
MultiValueMap wordMap = new MultiValueMap().decorate(new HashMap(), LinkedList.class);
while (wordMap.notAllEmpty()){
wordMap.put(wordType,word) // eg, Noun, Giraffe
}
But I don't have a method for notAllEmpty(). I tried .values().contains(null) but the empty map doesn't contain null. Likewiese .values().isEmpty() doesn't work as all values are put together.
Is there a succinct way to check for this condition, perhaps with a method from the API?
It sounds like your trying to continue with your loop until there is at least one value per key. This is going to open you to a large possibility of an infinite loop if your data set isn't complete. To get to the point though you need to look at the keys, not the values. If you know how many word types there are than you should probably use the following.
int totalWordTypes = 10;
while (wordMap.keySet().size() < totalWordTypes) {
//...
}
Otherwise you'll need a collection of the word types you are looking for and use something like...
while (!wordMap.keySet().containsAll(wordTypesCollection)) {
//...
}
Under what circumstances given a correct implementation of hashCode and equals() can the following code return false?
myLinkedHashMap.containsKey(myLinkedHashMap.keySet().iterator().next())
Most likely scenario I can think of would be even though hashCode is "deterministic", it may be based on mutable fields. If you change the fields used to compute hashCode after it's put in the Map, then you won't be able to find it anymore.
Edit: should clarify you 'usually' won't be able to find it anymore. Occasionally it will still work since two numbers can still rehash into the same bucket. This, of course, only adds to the confusion when it happens!
Every hash algorithm I have seen is "deterministic", in that for a given set of input values, you get the same hash value.
If the hash code is computed based on mutable properties of the object, the hash code will change after it's in the hash map if any of those mutable properties are changed.
It's not clear what you mean by "deterministic", but any hash-changing mutation to the key after it's been inserted into the hash map could easily have that effect.
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
Map<List<String>, String> map = new LinkedHashMap<List<String>, String>();
map.put(strings, "");
System.out.println(map.containsKey(map.keySet().iterator().next())); // true
strings.add("Foo");
System.out.println(map.containsKey(map.keySet().iterator().next())); // false
}
}
The hash code of ArrayList<T> is deterministic, but that doesn't mean it won't change if the contents of the list changes.
If the hashCode() is based on instance attributes that are mutable and those attributes are changed after the insertion, the hashCode() call during the iteration will return something different. And the equals() should be based on these same attributes, it will be expected to fail as well.
When another thread has removed all the next items from an Map in the middle of an iteration, there will be no more next().
I would not use the hashCode() values as keys, I would you the objects themselves.
If your hashCode and equals don't agree with one another, this could return false. For example, if the equals method always returns false, this will return false, since there isn't any object that would ever compare equal to the keys in the map.
Hope this helps!
You might want to check hasNext() first.
You could remove the first key in another thread between getting the first keys and calling containsKey.
Why does java.util.Map.values() allow you to delete entries from the returned Collection when it makes no sense to remove a key value pair based on the value? The code which does this would have no idea what key the value(and hence a key) being removed is mapped from. Especially when there are duplicate values, calling remove on that Collection would result in an unexpected key being removed.
it makes no sense to remove a key value pair based on the value
I don't think you're being imaginative enough. I'll admit there probably isn't wide use for it, but there will be valid cases where it would be useful.
As a sample use case, say you had a Map<Person, TelephoneNumber> called contactList. Now you want to filter your contact list by those that are local.
To accomplish this, you could make a copy of the map, localContacts = new HashMap<>(contactList) and remove all mappings where the TelephoneNumber starts with an area code other than your local area code. This would be a valid time where you want to iterate through the values collection and remove some of the values:
Map<Person, TelephoneNumber> contactList = getContactList();
Map<Person, TelephoneNumber> localContacts = new HashMap<Person, TelephoneNumber>(contactList);
for ( Iterator<TelephoneNumber> valuesIt = localContacts.values().iterator(); valuesIt.hasNext(); ){
TelephoneNumber number = valuesIt.next();
if ( !number.getAreaCode().equals(myAreaCode) ) {
valuesIt.remove();
}
}
Especially when there are duplicate values, calling remove on that Collection would result in an unexpected key being removed.
What if you wanted to remove all mappings with that value?
It has to have a remove method because that's part of Collection. Given that, it has the choice of allowing you to remove values or throwing an UnsupportedOperationException. Since there are legitimate reasons that you might want to remove values, why not choose to allow this operation?
Maybe there's a given value where you want to remove every instance
of it from the Map.
Maybe you want to trim out every third
key/value pair for some reason.
Maybe you have a map from hotel
room number to occupancy count and you want to remove everything from
the map where the occupancy count is greater than one in order to
find a room for someone to stay in.
...if you think about it more
closely, there are plenty more examples like this...
In short: there are plenty of situations where this might be useful and implementing it doesn't harm anyone who doesn't use it, so why not?
I think there is quite often a use for removing a value based on a key; other answers show examples. Given that, if you want to remove a certain value, why would you only want one particular key of it removed? Even if you did, you'd have to know which key you wanted to remove (or not, as the case may be), and then you should just remove it by key anyway.
The Collection returned is a special Collection, and its semantics are such that it knows how values in it relate back to the Map it came from. The javadoc indicates what Collection operation the returned collection supports.
OK so this is a BIT different. I have a new HashMap
private Map<String, Player> players = new HashMap<String, Player>();
How do I remove last known item from that? Maybe somethign like this?
hey = Player.get(players.size() - 1);
Player.remove(hey);
The problem is, a HashMap is not sorted like a list. The internal order depends on the hashCode() value of the key (e.g. String). You can use a LinkedHashMap which preserves the insert order. To remove the last entry on this you can use an iterator in combination with a counter which compares to the size and remove the last entry.
It's so easy. Try this:
Map<String, Player> players = new LinkedHashMap<String, Players>();
List<String> list = new ArrayList<String>(players.keySet());
map.remove(list.get(list.size()-1));
I'm a little bit confused. First of all, you're saying that you've got a new ArrayList and you're illustrating this with a line that creates a new HashMap. Secondly, does the Player class really have static methods like get(int) and remove(Object)?
HashMap doesn't have a particular order, ArrayList (as any other List) does.
Removing from an ArrayList
If you've got a list of players, then you can do the following:
private List<Player> players = new ArrayList<Player>();
// Populate the list of players
players.remove(players.size() - 1);
Here, I've used the remove(int) method of List, which allows to remove an item at an arbitrary index.
Removing from a HashMap
If you've got a map of players, there's no such thing as "the last item". Sure, you can iterate over the map and one of the items will pop out last, but that doesn't mean anything. Therefore, first you have to find out what you want to remove. Then you can do the following:
private Map<String, Player> players = new HashMap<String, Player>();
// Populate the map of players
// Find the key of the player to remove
players.remove(toRemove);
Here, I've used the remove(Object) method of Map. Note that in order to remove some key-value pair, you have to show the key, not the value.
There's no "first" and "last" in a HashMap. It's unordered. Everything is accessible by its key, not by index.
You cannot delete from HashMap like that. You need to use LinkedHashMap.
Simple, just do something of this effect.
1) Get a keyset iterator;
2) Create a Key somelastKey = null
3) Iterate through the iterator and assigning somelastKey until iterator finishes.
4) finally, do players.remove(somelastKey);
Bear in mind that HashMap is unordered, it depends on Object's hashCode to determine insertion order.
Instead of using HashMap, try using LinkedHashMap which keeps a predictable iteration order.
Hope this helps....
You'll probably have to extend HashMap, override put so that it caches the key, and then create a new method that just removes the key that was cached.
Unfortunately, this will only let you remove the most recently added. If you need to remove the most recently added multiple times (without inserting in-between the removes), you're out of luck.
In that case, I'd probably do the same overrides, just write the keys to a List. So you'd have both a list and a Map.
When adding:
String key; Player value;
lastKey = key;
map.put(key, value);
//...later...
Player lastAdded = map.remove(lastKey);
Other than that there's really no way without using a LinkedHashMap or in some way creating your own wrapper map or extending HashMap.
You shouldn't be using a raw hashmap anywhere because things like this happen.
Get in the habit of wrapping your collections in business logic classes.
See, in your case right now you need to associate these two related variables--your hashmap and a "Last entered" item so you can remove it.
If you need to remove the last item from some other class, you need to pass both items.
Any time you find yourself passing 2 or more items together into more than one API, you are probably missing a class.
Create a new class that contains the hashmap and a "lastAdded" variable. Have put and remove methods that are just forwarded to the hashmap, but the put method would also set the lastAdded variable.
Also be sure to add a removeLast() method.
NEVER allow access to your hashmap outside this class, it needs to be completely private (this is what I mean by wrapped). In this way you can ensure it doesn't get out of sync with the lastAdded variable (also completely private).
Just to reiterate getters and setters for these variables would be a terrible idea (as they are with nearly all actual OO code).
You will quickly find a bunch of other methods that NEED to be in this class in order to access data inside your hashmap--methods that never felt right in their current location. You will probably also notice that those methods always have an additional parameter or two passed in--those parameters should probably be members of your new class.
Once you get in the habit of doing actual OO design (via refactoring in this case), you'll find your code MUCH more manageable. To illustrate this point, if you find later that you need multiple levels of "delete last", it will be TRIVIAL to add to your class because it will be extremely clear exactly what methods can modify your hashtable and where your new "stack" of lastItems should be located--in fact it's probably a 2 line code change.
If you do not make this wrapper class, various locations will each have code to set "lastAdded" when they add code to the hashtable. Each of those locations will have to be modified, some may be in other classes requiring you to pass your new stack around with the hashtable. It will be easier to get them out of synch if you forget to change one location.
I'm profiling some old java code and it appears that my caching of values using a static HashMap and a access method does not work.
Caching code (a bit abstracted):
static HashMap<Key, Value> cache = new HashMap<Key, Value>();
public static Value getValue(Key key){
System.out.println("cache size="+ cache.size());
if (cache.containsKey(key)) {
System.out.println("cache hit");
return cache.get(key);
} else {
System.out.println("no cache hit");
Value value = calcValue();
cache.put(key, value);
return value;
}
}
Profiling code:
for (int i = 0; i < 100; i++)
{
getValue(new Key());
}
Result output:
cache size=0
no cache hit
(..)
cache size=99
no cache hit
It looked like a standard error in Key's hashing code or equals code.
However:
new Key().hashcode == new Key().hashcode // TRUE
new Key().equals(new Key()) // TRUE
What's especially weird is that cache.put(key, value) just adds another value to the hashmap, instead of replacing the current one.
So, I don't really get what's going on here. Am I doing something wrong?
edit
Ok, I see that in the real code the Key gets used in other methods and changes, which therefore get's reflected in the hashCode of the object in the HashMap. Could that be the cause of this behaviour, that it goes missing?
On a proper #Override of equals/hashCode
I'm not convinced that you #Override (you are using the annotation, right?) hashCode/equals properly. If you didn't use #Override, you may have defined int hashcode(), or boolean equals(Key), neither of which would do what is required.
On key mutation
If you are mutating the keys of the map, then yes, trouble will ensue. 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.
Here's an example:
Map<List<Integer>,String> map =
new HashMap<List<Integer>,String>();
List<Integer> theOneKey = new ArrayList<Integer>();
map.put(theOneKey, "theOneValue");
System.out.println(map.containsKey(theOneKey)); // prints "true"
theOneKey.add(42);
System.out.println(map.containsKey(theOneKey)); // prints "false"
By the way, prefer interfaces to implementation classes in type declarations. Here's a quote from Effective Java 2nd Edition: Item 52: Refer objects by their interfaces
[...] you should favor the use of interfaces rather than classes to refer to objects. If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types.
In this case, if at all possible, you should declare cache as simply a Map instead of a HashMap.
I'd recommend double and triple checking the equals and hashCode methods. Note that it's hashCode, not hashcode.
Looking at the (abstracted) code, everything seems to be in order. It may be that the actual code is not like your redacted version, and that this is more a reflection of how you expect the code to work and not what is happening in practice!
If you can post the code, please do that. In the meantime, here are some pointers to try:
After adding a Key, use exactly the same Key instance again, and verify that it produces a cache hit.
In your test, verify the hashcodes are equal, and that the objects are equal.
Is the Map implementation really a HashMap? WeakHashMap will behave in the way you describe once the keys are no longer reachable.
I'm not sure what your Key class is, but (abstractly similarly to you) what I'd do for a simple check is:
Key k1 = new Key();
Key k2 = new Key();
System.out.println("k1 hash:" + k1.hashcode);
System.out.println("k2 hash:" + k2.hashcode);
System.out.println("ks equal:" + k1.equals(k2));
getValue(k1);
getValue(k2);
if this code shows the anomaly -- same hashcode, equal keys, yet no cache yet -- then there's cause to worry (or, better, debug your Key class;-). The way you're testing, with new Keys all the time, might produce keys that don't necessarily behave the same way.