tags:

views:

123

answers:

5

In map we have key, value pair.If we try to put same key in map 2 times it will produce a error.Now i want similar kind of behavior for value too.In short when i put a new key,value into the map both key and value should be unique, or else it should through an exception, How can i achive this?

+5  A: 

Sounds like you want a Guava Bimap.

A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as that of its keys. This constraint enables bimaps to support an "inverse view", which is another bimap containing the same entries as this bimap but with reversed keys and values.

(Guava is a great library in general, btw. Well worth using.)

Jon Skeet
Gah! You're too quick! (+1)
Noel M
@jon:I dont want to use a third part lib
akshay
@akshay: Then you'll basically have to implement something like a Bimap yourself. There's nothing built into the JRE as far as I know. The simplest way of doing this would be to use two HashMaps encapsulated in a new class (or a Map and a Set, as Visage suggested). In future, if you have restrictions which prohibit the most natural solution, it would be nice to say so in the question.
Jon Skeet
@akshay: Then reinvent that wheel. Simple as that. Repeat the work that many smart people have already done for you. Oh, they also did all the testing and documentation and everything. Yes, simply do that all over again. With some luck, you may even end up doing a better job than them.
polygenelubricants
I think you're being harsh. If he wants a simple solution to a simple problem then it may well be a better idea to use a single class in his app, rather than potentially including a 3rd party lib containing hundreds of classes that he doesnt need, no matter how well written they may be.
Visage
@Visage. It's licensed under the Apache License. I'm pretty sure you could include only the files you needed. Reluctance to take on 3rd party libraries usually stems instead from not having a proper build infrastructure in place or a lack of desire to understand the license. Neither are good reasons, and the likelihood for any reasonable size project is that you're going to end up with 3rd party libs anyway, at which point you kick yourself for not using them sooner.
Mark Peters
You're right, but I still think its a nuanced decision, which didnt deserve the 'You're an idiot if you dont' (Im paraphrasing) response by polygenelubricants
Visage
Why would we need a bimap for this? Checking the presence of keys/values is possible with standard `java.util.Map`.
eljenso
I don't think you're paraphrasing, I think you're putting words in polygene's mouth. He made very good points as to why using a proven library is a better idea and used a tone that emphasizes them.
Mark Peters
@Mark Yeah, forgot about that completely. However, for small amounts of inserts it probably should be ok. I've updated my answer.
eljenso
+2  A: 

Create a new class that contains a Map, plus a Set of values. On insert, if the value is in the set, throw an exception (or return false, or do nothing) otherwise add it to the map.

So you use the Map to enforce unique keys, and the Set to enforce unique Values.

If you want a bi-directional Map then just replace the set with a Map

Visage
+1 bearing in mind the comment on Jon's post.
Noel M
+3  A: 

You may want to subclass HashMap to create your own bimap:

Changed my mind - there's no need to subclass HashMap. Composition is a much cleaner approach, the following example decorates a concrete Map with a BiMap behavour.

public class BiMap<K, V> implements Map<K, V> {

    private Map<K, V> inner;
    private Set<V> values;

    public BiMap(Map<K, V> map) {
        if (map == null || !map.isEmpty())
            throw new IllegalArgumentException("This implementation requires an empty map");
        inner = map;
        values = new HashSet<V>();
    }

    public boolean containsKey(Object key) { return inner.containsKey(key); }
    public boolean containsValue(Object value) { return values.contains(value); }
    public Set<java.util.Map.Entry<K, V>> entrySet() { return inner.entrySet(); }
    public V get(Object key) { return inner.get(key); }
    public boolean isEmpty() { return inner.isEmpty(); }
    public Set<K> keySet() { return inner.keySet(); }
    public int size() { return inner.size(); }
    public Set<V> values() { return Collections.unmodifiableSet(values); }
    public boolean equals(Object obj) { return inner.equals(obj); }
    public int hashCode() { return inner.hashCode(); }
    public String toString() { return inner.toString(); }

    public void clear() {
        values.clear();
        inner.clear();
    }

    public V put(K key, V value) {
        if (values.contains(value)) {
            throw new IllegalArgumentException("Value already exists in map");
        }
        values.add(value);
        return inner.put(key, value);
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
            put(entry.getKey(), entry.getValue());
        }
    }

    public V remove(Object key) {
        values.remove(key);
        return inner.remove(key);
    }    
}

It's more code compared to subclassing an existing map, but it's worth it. Now we're completely independant of any existing Map implementation.

Andreas_D
While your solution would work, Im always wary about subclassing classes that I dont control - it can lead to all kinds of subtle bugs - especially in areas such as concurrency.
Visage
@Visage - Normally I'd agree with you on this point as well, but the `Map` contract is well-defined, and we already know that `HashMap` is explicitly not synchronized, so I don't find fault with this in this case (especially versus the alternative of the OP trying to correctly implement the `Map` contract themselves). +1 to you both.
Tim Stone
I'm with Visage here...I think composition/decoration is more suitable. It's not that hard to delegate all methods to the underlying map except `put`...there's even IDE support for it, at least in Eclipse.
Mark Peters
I usually don't downvote but extending HashMap made me shiver - yikes.
Dimitris Andreou
@Mark - Fair point. Since the `BiMap` is only a `Map`, I just personally don't see enough difference between inheritance and delegation here to say this isn't an acceptable approach. (I'd instinctively choose composition myself, but still). Also, I never noticed that Eclipse feature before...Whoops. Good to know!
Tim Stone
In all fairness, I'd mention the single advantage of this inheritance approach, meant for semi-paranoid people. With inheritance, you get to save 3 words (e.g. 12 bytes) per instance. (But then, you wouldn't be using a HashSet *which itself uses composition instead of extending (shiver again!) HashMap*, because the latter similarly spends those 3 words). Doug Lea does similar tricks a lot in java.util.concurrent, but he keeps them hidden, which is fine. By the way, don't use contains() and then add(), just add() directly and check its return value.
Dimitris Andreou
@Dimitris - makes me shiver too, but the answer was for akshay. I wouldn't subclass HashMap in my own code too, but sometimes, I prefer giving a simple and intuitive solution which leaves enough room for later improvement.
Andreas_D
@Tim and @Andreas: I don't think it's an unacceptable approach and it's suitable to demonstrate the concept. My main issue with inheritance here is that it couples you to an implementation of an underlying map. If you want to change the backing map down the road, you can't without breaking your clients' code (since they could be declaring your BiMap as a HashMap, for example). By the way I'd be hesitant to call it a BiMap until it can do inverse lookups, which isn't supported by this implementation.
Mark Peters
@Mark - The previous example was far from complete and incorrect. After reading Josh Blochs recommendations again, I implemented a decorator and I'm much, much happier with it. Still improveable but decoupled from HashMap (a BiMap based on TreeMap was impossible...)
Andreas_D
@Mark - Admittedly, I came to a similar realization after thinking about it for a bit. There was the problem too of `BiMap` actually *not* being a `HashMap`, since the behaviour of `put()` had changed in an incompatible way (the possibility for `IllegalArgumentException` to be thrown; fine for a `Map`, but not a `HashMap`), which I had failed to consider.
Tim Stone
Looks great now, +1. Obviously this goes far beyond what is needed for an example, but here are just a couple suggestions: 1) Declare `values()` to return a Set<V>. 2) Return `Collections.unmodifiableSet(values)` to protect your invariants.
Mark Peters
Much better now, thanks for incorporating the suggestions. +2 from me. (+1 - (-1) :) )
Dimitris Andreou
@Mark - changed the code. Valid remark!
Andreas_D
A: 

Before inserting a value into the map, do a check using Map.containsKey and/or Map.containsValue (not sure if you want to replace values for keys that are already present).

Edit: Wrong answer when having lots of inserts, still valid for small data sets.

16384 insertions of random Strings: map 12828 ms bimap 31 ms

1024 insertions of random Strings: map 32 ms bimap 15 ms

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import com.google.common.collect.HashBiMap;

public class InsertTest
{
    private Map<Integer, String> map = new HashMap<Integer, String>();
    private Map<Integer, String> bimap = HashBiMap.create();

    public void run()
    {
        String[] values = new String[1024];
            Random r = new Random();
        for (int i = 0; i < values.length; i++)
        {
          values[i] = Long.toString(Math.abs(r.nextLong()), 36);
        }
        test("map", map, values);
        test("bimap", bimap, values);
    }

    private static void test(String name, Map<Integer, String> map, String[] values)
    {
        int key = 0;
        long start = System.currentTimeMillis();
        for (String value : values)
        {
            if (!map.containsValue(value))
            {
                map.put(key++, value);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(name + " " + (end - start));
    }

    public static void main(String[] args)
    {
        new InsertTest().run();
    }
}
eljenso
Expensive! That makes insertion O(n).
Thomas
Oopsie. I still think for a limited number of inserts the containsKey/Value approach is viable. I'm not used to working with huge amounts of data, so I always get by with what appear to be inefficient solutions. Thanks for pointing it out though. Lesson learned.
eljenso
+1 for learning. Also, I think this is the best solution for small data sets.
Skip Head
+1  A: 

Disclaimer: I would definitely go with Google Collections. It's probably the best-quality library I've ever used (It has great API, great code under the hood, and great documentation).

You can implement your own Map based on one of existing Map implementations:

public class UniqueValuesMap<K,V> implements Map<K,V> {

   private final Map<K, V> innerMap = new HashMap<K, V> ();

   public int size() {
       return innerMap.size();
   }

   ...
   //all other methods

   public V put(K key, V value) {
      if (innerMap.values().contains (value) {
          throw new IllegalArgumentException ("some msg");
      }
      return innerMap.put (key, value);
   }

   public void putAll(Map<? extends K, ? extends V> m) {
       // implementation
   }

}
Roman