Put Method Writning in Map Class? - java

I am coding the MapClass right now, but I can't seem to figure out the put method. This is what I have so far:
public V put(K key, V value)
{
for(MapEnt<K,V> x:data)
{
if(x.getKey().equals(key))
{
V reval = x.getValue();
x.setValue(value);
return reval;
}
else
{
}
}
return null;
}
I'm having trouble with what to put in the else to add an entry. I have an ArrayList of keys and values.
Thank you so much!

Nothing (i.e. remove the else part). You don't want to do anything there in case a entry with a appropriate key is found later in the list.
After the loop you need to add a new entry, since you know there is no entry with an appropriate key in the entry list.
BTW: If you want to allow the key to be null, use Objects.equals to check equality instead of calling equals (which may yield a NullPointerException)

There are better ways to do this (the details depend on the type of map you're implementing). However this might be what you're asking for.
// Returns matching value or null if no value exists for this key.
public V put( K key, V value )
{
V existingValue = null;
// The downside of this for each loop is that it will iterate through all
// entries, even if a match is found part way through.
for ( MapEnt<K, V> x : data )
{
if ( x.getKey().equals( key ) )
{
// Match found.
existingValue = x.getValue();
x.setValue( value );
}
}
if ( existingValue == null )
{
// No match was found. add new entry (exactly where you add it will
// Depend on the type of map you are implementing.
}
return existingValue;
}

Related

Java data structure with multiple keys and not allows duplicate keys

I'm wondering if there are any Java data structures or libraries that would allow me to have multiple keys while not having duplicate keys?
In my use case I have two different types of keys: Personal Identification Number and Driver License. I want to use either keys to look up a value (their car in this example). When I try to add a duplicate key (pin-1), there should be an error as pin-1 is already assigned.
structure.add("pin-1", "driverLicense-1", Car.TOYOTA);
structure.add("pin-2", "driverLicense-2", Car.FORD);
structure.add("pin-1", "driverLicense-3", CAR.FORD); // invalid because pin-1 is already assigned
The only library I have encountered that does something like this is Google’s Guava and its HashBasedTable class implementing Table. The issue I have with this library is that it does not prevent duplicate keys.
Table<String, String, Car> table = HashBasedTable.create();
table.put("pin-1", "driverLicense-1", Car.TOYOTA);
table.put("pin-2", "driverLicense-2", Car.FORD);
table.put("pin-1", "driverLicense-3", Car.FORD); // is valid
I would use two Maps, wrapped in a class which handles updating them together, e.g.:
class CarOwnership {
private final Map<String,Car> byPin = new HashMap<>();
private final Map<String,Car> byLicense = new HashMap<>();
public void put(String pin, String licence, Car car) {
if (byPin.containsKey(pin)) {
throw new RuntimeException("duplicate pin " + pin);
}
if (byLicense.containsKey(licence)) {
throw new RuntimeException("duplicate licence " + license);
}
byPin.put(pin, car);
byLicense.put(licence, car);
}
public Car findByPin(String pin) {
return byPin.get(pin);
}
...
}
The advantage of having a container around the actual data structure you're using is that it allows you to put your error handling in one place, and gives you somewhere to perform validation.
This is distinct from HashBasedTable, which is a Map of Maps, and so needs both components of the key to reach the value.
From what I understand, this can be achieved by making an entry in a map for each key passed. So, there is only one map, but each call to put() potentially adds multiple entries (one entry for each key). Not sure if there is a built-in API in Java for this, but here is one simply implementation.
public class MultikeyMap<K, V>{
/* Holds all the values. */
private Map<K, V> map = new HashMap<>();
/** Creates one entry in this.map for each key passed here. */
public void put( V value, K... keys ) {
if( keys == null || keys.length == 0 ) throw new NullPointerException();
/* For every key, create an entry. */
for( K key : keys ) map.putIfAbsent( key, value );
}
public V get( K key ) {
if( key == null ) throw new NullPointerException();
return map.get( key );
}
#Override
public String toString(){
return this.map.toString();
}
public static void main( String[] args ){
MultikeyMap<String, Car> m = new MultikeyMap<>();
m.put( Car.HONDA, "pin-1", "dl-1" );
System.out.println( m );
m.put( Car.TATA, "pin-2", "dl-2" );
System.out.println( m );
m.put( Car.TOYOTA, "pin-1", "dl-3" );
System.out.println( m );
}
private static enum Car{
TOYOTA, HONDA, MARUTI, TATA;
}
}
Here's the output from running main().
{pin-1=HONDA, dl-1=HONDA}
{pin-2=TATA, pin-1=HONDA, dl-2=TATA, dl-1=HONDA}
{pin-2=TATA, dl-3=TOYOTA, pin-1=HONDA, dl-2=TATA, dl-1=HONDA}
Some points about this design:
When an existing key is encountered, it doesn't throw an exception; only ignores it.
If we need various types in the key, then we may have to use Map<Object, Car>.

Java - how to get a key object (or entry) stored in HashMap by key?

I'd like to get the "canonical" key object for each key usable to query a map. See here:
Map<UUID, String> map = new HashMap();
UUID a = new UUID("ABC...");
map.put(a, "Tu nejde o zamykání.");
UUID b = new UUID("ABC...");
String string = map.get(b); // This gives that string.
// This is what I am looking for:
UUID againA = map.getEntry(b).key();
boolean thisIsTrue = a == againA;
A HashMap uses equals(), which is the same for multiple unique objects. So I want to get the actual key from the map, which will always be the same, no matter what object was used to query the map.
Is there a way to get the actual key object from the map? I don't see anything in the interface, but perhaps some clever trick I overlooked?
(Iterating all entries or keys doesn't count.)
Is there a way to get the actual key object from the map?
OK, so I am going to make some assumptions about what you mean. After all, you said that your question doesn't need clarification, so the obvious meaning that I can see must be the correct one. Right? :-)
The answer is No. There isn't a way.
Example scenario (not compileable!)
UUID uuid = UUID.fromString("xxxx-yyy-zzz");
UUID uuid2 = UUID.fromString("xxxx-yyy-zzz"); // same string
println(uuid == uuid2); // prints false
println(uuid.equals(true)); // prints true
Map<UUID, String> map = new ...
map.put(uuid, "fred");
println(map.get(uuid)); // prints fred
println(map.get(uuid2)); // prints fred (because uuid.equals(uuid2) is true)
... but, the Map API does not provide a way to find the actual key (in the example above it is uuid) in the map apart from iterating the key or entry sets. And I'm not aware of any existing Map class (standard or 3rd-party) that does provide this1.
However, you could implement your own Map class with an additional method for returning the actual key object. There is no technical reason why you couldn't, though you would have more code to write, test, maintain, etcetera.
But I would add that I agree with Jim Garrison. If you have a scenario where you have UUID objects (with equality-by-value semantics) and you also want to implement equality by identity semantics, then there is probably something wrong with your application's design. The correct approach would be to change the UUID.fromString(...) implementation to always return the same UUID object for the same input string.
1 - This is not to say that such a map implementation doesn't exist. But if it does, you should be able to find it if you look hard enough Note that Questions asking us to find or recommend a library are off-topic!
There is a (relatively) simple way of doing this. I’ve done so in my applications from time to time, when needed ... not for the purpose of == testing, but to reduce the number of identical objects being stored when tens of thousand of objects exist, and are cross-referenced with each other. This significantly reduced my memory usage, and improved performance ... while still using equals() for equality tests.
Just maintain a parallel map for interning the keys.
Map<UUID, UUID> interned_keys = ...
UUID key = ...
if (interned_keys.contains(key))
key = interned_keys.get(key)
Of course, it is far better when the object being stored knows what its own identity is. Then you get the interning basically for free.
class Item {
UUID key;
// ...
}
Map<UUID, Item> map = ...
map.put(item.key, item);
UUID key = ...
key = map.get(key).key; // get interned key
I think there are valid reasons for wanting the actual key. For example, to save memory. Also keep in mind that the actual key may store other objects. For instance, suppose you have a vertex of a graph. The vertex can store the actual data (Say a String, for instance), as well as the incident vertices. A vertex hash value can be dependent only on the data. So to look up a vertex with some data,
D, look up a vertex with data, D,and with with no incident values. Now if you can return the actual vertex in the map you will be able to get the actual incident to the vertex.
It seems to me that many map implementations could easily provide a getEntry method. For example, the HashMap implementation for get is:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
One could use the getNode method to return an Entry:
public getEntry(Object key){
Node<K,V> e = getNode(hash(key),key);
if(e == null) return null;
return new Entry<>(e.key,e.value);
}
The easiest way is to duplicate the reference to the key in the value using a generic Pair type, like this:
HashMap<UUID,Pair<UUID,String>> myMap = new HashMap<>();
When you put them in the map, you provide the reference to the key to the pair. The cost is one reference per entry.
void add(UUID uuid, String str)
{
myMap.put(uuid,Pair.of(uuid,str));
}
Pair<UUID,String> get(UUID uuid)
{
return myMap.get(uuid);
}
Then getFirst() of the Pair is your key. getSecond() is the value.
Whatever you do, it's going to cost you in either time or space.
Your Pair class will be something like:
public class Pair<A,B>
{
private final A a;
private final B b;
public Pair(A a, B b)
{
this.a = a;
this.b = b;
}
/**
* #return the first argument of the Pair
*/
public A getFirst()
{
return this.a;
}
/**
* #return the second argument of the Pair
*/
public B getSecond()
{
return this.b;
}
/**
* Create a Pair.
*
* #param a The first argument (of type A)
* #param b The second argument (of type B)
*
* #return A Pair of A and B
*/
public static <A,B> Pair<A,B> of(A a, B b)
{
return new Pair<>(a,b);
}
// Don't forget to get your IDE to produce a hashcode()
// and equals() method for you, depending
// on if you allow nulls or not, or DIY.
}
it could help. You can use a for each like below.
Map<String,Object> map = new HashMap<>();
map.put("hello1", new String("Hello"));
map.put("hello2", new String("World"));
map.put("hello3", new String("How"));
map.put("hello4", new String("Are u"));
for(Map.Entry<String,Object> e: map.entrySet()){
System.out.println(e.getKey());
}

Java's HashMap key replacement when storing existing value

According to Java HashMap documentation, put method replaces the previously contained value (if any): https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#put-K-V-
Associates the specified value with the specified key in this map. If
the map previously contained a mapping for the key, the old value is
replaced.
The documentation however does not say what happens to the (existing) key when a new value is stored. Does the existing key get replaced or not? Or is the result undefined?
Consider the following example:
public class HashMapTest
{
private static class Key {
private String value;
private Boolean b;
private Key(String value, Boolean b) {
this.value = value;
this.b = b;
}
#Override
public int hashCode()
{
return value.hashCode();
}
#Override
public boolean equals(Object obj)
{
if (obj instanceof Key)
{
return value.equals(((Key)obj).value);
}
return false;
}
#Override
public String toString()
{
return "(" + value.toString() + "-" + b + ")";
}
}
public static void main(String[] arg) {
Key key1 = new Key("foo", true);
Key key2 = new Key("foo", false);
HashMap<Key, Object> map = new HashMap<Key, Object>();
map.put(key1, 1L);
System.out.println("Print content of original map:");
for (Entry<Key, Object> entry : map.entrySet()) {
System.out.println("> " + entry.getKey() + " -> " + entry.getValue());
}
map.put(key2, 2L);
System.out.println();
System.out.println("Print content of updated map:");
for (Entry<Key, Object> entry : map.entrySet()) {
System.out.println("> " + entry.getKey() + " -> " + entry.getValue());
}
}
}
When I execute the following code using Oracle jdk1.8.0_121, the following output is produced:
Print content of original map:
> (foo-true) -> 1
Print content of updated map:
> (foo-true) -> 2
Evidence says that (at least on my PC) the existing key does not get replaced.
Is this the expected/defined behaviour (where is it defined?) or is it just one among all the possible outcomes? Can I count on this behaviour to be consistent across all Java platforms/versions?
Edit: this question is not a duplicate of What happens when a duplicate key is put into a HashMap?. I am asking about the key (i.e. when you use multiple key instances that refer to the same logical key), not about the values.
From looking at the source, it doesn't get replaced, I'm not sure if it's guaranteed by the contract.
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
It finds the existing mapping and replaces the value, nothing is done with the new key, they should be the same and immutable, so even if a different implementation can replace the key it shouldn't matter.
You can't count on this behavior but you should write your code in a way that it won't matter.
When a new pair is added, the map uses hasCode,equals to check if the key already present in the map. If the key already exists the old value is replaced with a new one. The key itself remains unmodified.
Map<Integer,String> map = new HashMap<>();
map.put(1,"two");
System.out.println(map); // {1=two}
map.put(1,"one");
System.out.println(map); // {1=one}
map.put(2,"two");
System.out.println(map); // {1=one, 2=two}
There is an issue with your equals and hashCode contract. ke1 and key2 are identical according to your implementation:
#Override
public boolean equals(Object obj)
{
if (obj instanceof Key)
{
return value.equals(((Key)obj).value);
}
return false;
}
you need to compare Boolean b as well
Key other = (Key) obj;
return value.equals(other.value) && b.equals(other.b);
The same rule apples to hasCode
#Override
public int hashCode()
{
return value.hashCode();
}
return value.hashCode() + b.hashCode();
with these changes key1 and key2 are different
System.out.println(key1.equals(key2));
and the output for your map will be
> (foo-true) -> 1
> (foo-false) -> 2
It is not replaced - neither it should. If you know how a HashMap works and what hashCode and equals is (or more precisely how they are used) - the decision of not touching the Key is obvious.
When you put the other Key/Entry in the map for the second time, that key is first look-up in the map - according to hashCode/equals, so according to the map IFF keys have the same hashCode and are equal according to equals they are the same. If so, why replace it? Especially since if it would have been replaced, that might rigger additional operations or at least additional code to not trigger anything else if keys are equal.
Apparently the current HashSet implementation relies on this HashMap behaviour in order to be compliant to the HashSet documentation.
With that i mean that when you add a new element in an HashSet the documentation says that if you try to add an element in an HasSet that already contains the element, the HashSet is not changed and so the element is not substituted,
In the openjdk8 implementation the HashSet uses an HashMap keys to hold the values and in the HashSet.add method it calls the HashMap.put method to add the value, thus relying on the fact that the put method will not substitute the object
Although this still not a direct specification in the documentation and it's subject to variations in the JRE implementation, it probably provides a stronger
assurance that this will probably not change in the future

Mark an empty space into an array without using null

I am extending AbstractMap and I want to implement my own hash-map using two parallel arrays:
K[] keys;
V[] values;
Suppose I want to store null values as well, how could I initialize these two arrays so that I can differentiate between a space in the array where I could place some new key-value pairs and a space where I am storing a null?
Might I suggest not using two arrays, and instead do something along the lines of:
class Node {
K key;
V value;
}
Node[] nodes;
Then a non-entry is an element in nodes that is equal to null.
If the values can be null but the keys cannot be null then having a null key would mean that there is no key.
If the key can also be null you can use a parallel array of booleans to store whether each space is taken or not.
K[] keys;
V[] values;
boolean[] hasValue;
Not quite sure the details of your question, but you could always have some special object for your "blank".
private static final Object BLANK = new Object();
Then if the item in the array == BLANK, then consider it to be an empty slot.
Since there can only be one null key, you can simply have a special reference value (not in the array) that holds the value of the object mapped from this null key (and possibly a boolean indicating if this value has been set). Unfortunately this will probably complicate iteration.
E.g.
private boolean isNullMapped = false;
private V nullValue = null;
public put(K key, V value)
{
if (key == null) { nullValue = value; }
...
}
Alternatively, you can wrap all keys in a wrapper object (supposing you still want to use parallel arrays instead of entries), and if the value contained in this wrapper object is null, then it represents the null key.
E.g.
private static class KeyWrapper<K>
{
public K key;
}
Lastly, as a question for consideration, if you are not having entries in your arrays, but instead are directly holding arrays of K and V, then how are you accounting for different keys that happen to share the same hash code? The java.util implementation has arrays of entries that also act as linked lists to account for this possibility (and incidentally, the null key is always mapped to array index 0).
Storing a null value is not a problem in your scenario. So long as keys[n] != null, just return values[n] whether values[n] is null or not.
Remember that you are not being asked to key on n but objects of type K so every access of the Map will require a search through keys to find the key they are looking for.
However, if you want to allow the storage of a value against a null key then using something like private static final Object NULL_KEY = "NULL" would probably do the trick as the other suggestions point out.
private static final Object NULL_KEY = "NULL";
K[] keys;
V[] values;
private int find(K key) {
for (int i = 0; i < keys.length; i++) {
if (keys[i] == key) {
return i;
}
}
return -1;
}
public V put(K key, V value) {
V old = null;
if (key != null) {
int i = find(key);
if (i >= 0) {
old = values[i];
values[i] = value;
} else {
// ...
}
} else {
return put((K) NULL_KEY, value);
}
return old;
}
public V get(K key) {
if (key != null) {
int i = find(key);
if (i >= 0) {
return values[i];
}
return null;
} else {
return (get((K) NULL_KEY));
}
}
In the java.util implementation a special object representing null is used.

Updating a java map entry

I'm facing a problem that seems to have no straighforward solution.
I'm using java.util.Map, and I want to update the value in a Key-Value pair.
Right now, I'm doing it lik this:
private Map<String,int> table = new HashMap<String,int>();
public void update(String key, int val) {
if( !table.containsKey(key) ) return;
Entry<String,int> entry;
for( entry : table.entrySet() ) {
if( entry.getKey().equals(key) ) {
entry.setValue(val);
break;
}
}
}
So is there any method so that I can get the required Entry object without having to iterate through the entire Map? Or is there some way to update the entry's value in place? Some method in Map like setValue(String key, int val)?
jrh
Use
table.put(key, val);
to add a new key/value pair or overwrite an existing key's value.
From the Javadocs:
V put(K key, V value): Associates the specified value with the specified key in this map (optional operation). If the map previously contained a mapping for the key, the old value is replaced by the specified value. (A map m is said to contain a mapping for a key k if and only if m.containsKey(k) would return true.)
If key is present table.put(key, val) will just overwrite the value else it'll create a new entry. Poof! and you are done. :)
you can get the value from a map by using key is table.get(key); That's about it
You just use the method
public Object put(Object key, Object value)
if the key was already present in the Map then the previous value is returned.

Categories

Resources