9

This question was initially misphrased, see the EDIT below. I'll leave it up for context.

I've been thinking about smart ways to build a bijective (i.e. one-to-one) mapping. Mapping a function A->B (many-to-one) is basically what HashMap(A,B) does. If I now wanted to have a data structure that implements something one-to-one with contains() in O(1), would there be something in the java standard libraries that I could use? Mind you, I don't need this for anything right now, this was just something I thought about recently and couldn't come up with a data structure for, so answers aren't a rush. Is there a class like that? If not, what do you think why that is?

All I could find on SO are things about hibernate, that was of no help to me.

EDIT: My question was ill phrased, so some explanation is due.

What I meant was is the "backward" mapping B->A. HashMap(A,B) has contains(A) and contains(B) both in O(1), so that's not even what I meant, sorry for the confusion. What I meant was, is there a datastructure mapping A<->B that has getValue(A) and getKey(B) in O(1)?

I realize this could be done with two HashMaps (A,B) and (B,A) that are maintained to contain the same relation, but I feel there should be one data structure that handles that without having to do it "manually".

G. Bach
  • 3,869
  • 2
  • 25
  • 46
  • Would just extending the class A and adding a property that returns B work for you? – Colin D Jun 22 '12 at 19:29
  • So what do you want which HashMap doesn't do? It can be used for one-to-one mappings. – Peter Lawrey Jun 22 '12 at 19:30
  • @PeterLawrey is HashMap.contains in java O(1)? – Colin D Jun 22 '12 at 19:31
  • Are you looking for a constraint on the one-to-one mapping, because HashMap can be used for one-to-one and with a sufficiently well-chosen hash function and a small enough set will have contains in amortised O(1). – Alex Wilson Jun 22 '12 at 19:31
  • I just realized my question doesn't really bring across what I want; editing. – G. Bach Jun 22 '12 at 19:32
  • @ColinD contains is O(1) provided you have an ideal hashing algo. – Peter Lawrey Jun 22 '12 at 19:33
  • There is a python implementation, no guarantees: http://code.google.com/p/python-data-structures/source/browse/trunk/bijection.py – Woot4Moo Jun 22 '12 at 19:35
  • I think the closest you are going to get is with two HashMaps. If you really needed it, you should create a wrapper class for the two HashMaps and create a clean interface for it, wouldn't bee too tricky. – BlackVegetable Jun 22 '12 at 19:42
  • I agree, wouldn't be tricky to do, my main concern was whether the standard libraries have that already :) Thanks! – G. Bach Jun 22 '12 at 19:44
  • Take a look at this Multimap: https://commons.apache.org/collections/apidocs/org/apache/commons/collections/map/MultiValueMap.html – Woot4Moo Jun 22 '12 at 21:54

5 Answers5

16

I don't think you'll do better than two HashMaps. Writing the wrapper interface is very simple:

class OneToOneMap<Key, Value> {

    public void add(Key k, Value v) {
        if (!keyToVal_.contains(k) && !valToKey_.contains(v)) {
            keyToVal_.add(k, v);
            valToKey_.add(v, k);
        }
    }

    private HashMap<K, V> keyToVal_;
    private HashMap<V, K> valToKey_;
}

I am not sure if this is valid Java, but you get the idea.

japreiss
  • 11,111
  • 2
  • 40
  • 77
7

I don't know of an existing class that does O(1) for both containsKey and containsValue, but you can do it by extending HashMap so that on insert, you add each value to an internal HashSet. Overloading containsValue to do a lookup on the values HashSet. The standard HashMap has O(1) containsKey, but O(n) containsValue.

Likewise, you can enforce 1:1 in the insert and checking for existing values.

Note that if you get a ton of collisions the HashSet lookup can get to O(n) in the worst case.

Thomas
  • 5,074
  • 1
  • 16
  • 12
  • Nice, so there probably isn't a class in the standard libraries that does that already. I thought along the same lines while thinking about how to implement it. – G. Bach Jun 22 '12 at 19:43
  • can any one help me in posting some sample code for this – Shiva Komuravelly Aug 11 '16 at 05:07
  • I know this is 9 years old, but I just went on this adventure myself and thought I would share my solution, since it did take some work: https://pastebin.com/Wmks9Hut – Wasabi Thumbs May 18 '21 at 18:54
4

If you're willing to use third-party libraries, Guava provides a nice API for this as BiMap. Instead of a getKey method, it provides an inverse() view which returns a BiMap<V, K>. (It does, of course, provide constant-time containsValue.)

At the moment, HashBiMap is basically internally two HashMaps kept in sync -- though it's very consistent about how it keeps them matching each other -- but the implementation might get smarter in the future.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • 1
    On the topic of third party libraries, Apache has [BidiMap](http://commons.apache.org/collections/apidocs/org/apache/commons/collections/BidiMap.html) – Thomas Jun 24 '12 at 05:39
3

I had the same need and came up with this BiMap that might be useful. It just uses one hashmap and returns an entry object so that the return value can be given a specific type and also to allow a mapping to null.

public class BiMap<T1, T2>
{
    public static class Entry<T1, T2>
    {
        public final T1 item1;
        public final T2 item2;

        public Entry( T1 item1, T2 item2 )
        {
            this.item1 = item1;
            this.item2 = item2;
        }
    }

    private final HashMap< Object, Entry<T1, T2> > mappings = new HashMap<>();
    private int loopedLinkCount;

    public void put( T1 item1, T2 item2 )
    {
        remove(item1);
        remove(item2);
        Entry<T1, T2> entry = new Entry<T1, T2>( item1, item2 );
        mappings.put( item1, entry );
        mappings.put( item2, entry );
        if( Objects.equals(item1, item2) ){
            loopedLinkCount++;
        }
    }

    /**
     * 
     * @param key 
     * @return an entry containing the key and it's one to one mapping or null if there is
     * no mapping for the key.
     */
    public Entry<T1, T2> get( Object key )
    {
        return mappings.get( key );
    }

    public Entry<T1, T2> remove( Object key )
    {
        Entry<T1, T2> entry = mappings.remove( key );
        if( entry == null ){
            return null;
        }
        if( Objects.equals( entry.item2, entry.item1 ) ){
            loopedLinkCount--;
            return entry;
        }
        return mappings.remove( Objects.equals( key, entry.item1 )  ?  entry.item2  :  entry.item1 );
    }

    public Set< Entry<T1, T2> > entrySet()
    {
        return new HashSet<>( mappings.values() );
    }

    public int size()
    {
        return ( mappings.size() + loopedLinkCount ) / 2;
    }
}
user2219808
  • 542
  • 6
  • 15
2

My initial thought is just use a standard map, if you have a perfect hash function you can use a HashMap as a one-to-one mapping. If I understand what you are doing the following would suffice:

Map<String,Object> map = new HashMap<String,Object>();  
Woot4Moo
  • 23,987
  • 16
  • 94
  • 151
  • 1
    This doesn't restrict to a 1 to 1 mapping. Many keys can have the same value. EDIT: I was wrong... – BlackVegetable Jun 22 '12 at 19:32
  • 1
    @BlackVegetable incorrect. If you read the exact phrasing of my response I said perfect hash function. – Woot4Moo Jun 22 '12 at 19:33
  • I'm sorry. I didn't read it carefully enough. You are totally correct! – BlackVegetable Jun 22 '12 at 19:34
  • +1 since it answers my initial question, which - as I realized - wasn't phrased to mean what I actually wanted to ask. But yes, a perfect hashing function would guarantee a one-to-one relation between keys and values, thanks for that! – G. Bach Jun 22 '12 at 19:38
  • Pretty sure two HashMaps would fill this requirement for O(1) access that map A->B and the other B->A. Updating will cost more, of course and you have double the space requirement, but it is O(1) for both accesses. – BlackVegetable Jun 22 '12 at 19:41