7

I know that Guava has a BiMultimap class internally but didn't outsource the code. I need a data structure which is bi-directional, i.e. lookup by key and by value and also accepts duplicates.

i.e. something like this: (in my case, values are unique, but two values can point to the same key)

0 <-> 5
1 <-> 10
2 <-> 7
2 <-> 8
3 <-> 11

I want to be able to get(7) -> returning 2 and get(2) returning [7, 8]. Is there another library out there which has a data structure I can make use of?

If not, what do you suggest is the better option to handle this case? Is keeping two Multimaps in memory one with and the other with a bad practice?

P.S.: I have read this question: Bidirectional multi-valued map in Java but considering it is dated in 2011, I thought I'll open a more recent question

Community
  • 1
  • 1
Bernice
  • 2,552
  • 11
  • 42
  • 74
  • 1
    What size of data do you expect? – Itay Moav -Malimovka Jul 20 '14 at 12:29
  • @Itay Moav-Malimovka: about 100 entries, not too large.. the thing is that I frequently need to lookup by key or value – Bernice Jul 20 '14 at 12:32
  • 5
    Bernice, I'm not a fan of re-inventing the wheel, but if the Guava offering and the Apache offering are both inadequate for your purpose, then it would probably be less effort to roll your own implementation, than to look around further. After all, this could easily be done with one `HashMap` (for the unique direction) and one `HashMap>` (for the non-unique direction). – Dawood ibn Kareem Jul 20 '14 at 12:32
  • 1
    Start with 2 `Multimap`s wrapped inside your own `BiMultimap`, and verify if it's fast and compact enough for your needs (Is it append-only or do you need to remove entries? How often is it instanciated? How large does it actually get?). Unless it's lacking in some way, you're done for now. – Frank Pavageau Jul 20 '14 at 14:12
  • 2
    Despite [my answer is from 2011](http://stackoverflow.com/a/8439744/708434), nothing has really changed since then. If you have immutable structures I'd still recommend [`ImmutableMultimap#inverse()`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/ImmutableMultimap.html#inverse()) (in your case [solution from comment #15](https://code.google.com/p/guava-libraries/issues/detail?id=394#c15)) would probably be better. – Grzegorz Rożniecki Jul 21 '14 at 06:52
  • 1
    Also, please login, comment and vote for [this issue](https://code.google.com/p/guava-libraries/issues/detail?id=394) - 25 stars is quite much, maybe Guava librarians will reconsider opensourcing `BiMultimap` (obviously there's big demand for it). – Grzegorz Rożniecki Jul 21 '14 at 06:53
  • If you have a number that could be a key/value, you will return what ? i.e : in case you have 2 <-> 7, 2 <-> 3, 3 <-> 10, what will return `get(3)` ? – Oussama Zoghlami Jul 21 '14 at 08:48
  • @Xaerxess thanks for your suggestion. I did vote on it already, it's quite a useful structure to be open sourced. – Bernice Jul 26 '14 at 14:51
  • @OussamaZoghlami I'm guessing that a structure like a `BiMultimap` would have methods like `getByKey(3)` and `getByValue(3)` – Bernice Jul 26 '14 at 14:53
  • It already has it through its multimap and inverseMultimap views. See implementation details of the [Guava's HashBiMultimap](http://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/collect/HashBiMultimap.java?r=637b57166d09a457eb377ca2bfbd436c4870dff4) – Thiago Kronig Aug 01 '14 at 04:36
  • @ThiagoKronig Is the link broken? I don't see any BiMultimap in Guava in the source, wiki or javadocs. – user1803551 Jul 06 '16 at 08:09
  • Also please refer similar topic @ http://stackoverflow.com/questions/8066109/bidirectional-multi-valued-map-in-java/39846050#39846050 – Anver Sadhat Oct 04 '16 at 07:01

2 Answers2

1

What do you mean by

Guava has a BiMultimap class internally but didn't outsource the code

The code of an implementation is here.

I didn't check if this is a working implementation, nor if it made it into a release or if I'm just looking at some kind of snapshot. Everything is out in the open, so you should be able to get it.

From a quick glance at the source code it looks like the implementation does maintain two MultMaps, and this should be fine for the general case.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
0

If you don't need the whole bunch of Guava HashBiMultimap functionality, but just getByKey() and getByValue(), as you specified, I can suggest the approach, where only one HashMultiMap is used as a storage.

The idea is to treat provided key and value as equilibrium objects and put both of them in the storage map as keys and values.

For example: Let we have the following multiMap.put(0, 5), so we should get the storage map containing something like this [[key:0, value:5], [key:5, value:0]].

As far as we need our BiMultiMap to be generic, we also need to provide some wrapper classes, that should be used as storage map type parameters.

Here is this wrapper class:

public class ObjectHolder {

    public static ObjectHolder newLeftHolder(Object object) {
        return new ObjectHolder(object, false);
    }

    public static ObjectHolder newRightHolder(Object object) {
        return new ObjectHolder(object, true);
    }

    private Object object;
    private boolean flag;

    private ObjectHolder(Object object, boolean flag) {
        this.object = object;
        this.flag = flag;
    }

    public Object getObject() {

        return object;
    }

    @Override
    public boolean equals(Object o) {

        if (this == o) return true;
        if (!(o instanceof ObjectHolder)) return false;

        ObjectHolder that = (ObjectHolder) o;

        if (flag != that.flag) return false;
        if (!object.equals(that.object)) return false;

        return true;
    }

    @Override
    public int hashCode() {

        int result = object.hashCode();
        result = 31 * result + (flag ? 1 : 0);
        return result;
    }
}

And here is the MultiMap:

public class BiHashMultiMap<L, R> {

    private Map<ObjectHolder, Set<ObjectHolder>> storage;

    public SimpleBiMultiMap() {
        storage = new HashMap<ObjectHolder, Set<ObjectHolder>>();
    }

    public void put(L left, R right) {
        ObjectHolder leftObjectHolder = ObjectHolder.newLeftHolder(left);
        ObjectHolder rightObjectHolder = ObjectHolder.newRightHolder(right);

        put(leftObjectHolder, rightObjectHolder);
        put(rightObjectHolder, leftObjectHolder);
    }

    private void put(ObjectHolder key, ObjectHolder value) {
        if (!storage.containsKey(key)) {
            storage.put(key, new HashSet<ObjectHolder>());
        }
        storage.get(key).add(value);
    }

    public Set<R> getRight(L left) {
        return this.get(ObjectHolder.newLeftHolder(left));
    }

    public Set<L> getLeft(R right) {
        return this.get(ObjectHolder.newRightHolder(right));
    }

    private <V> Set<V> get(ObjectHolder key) {
        Set<ObjectHolder> values = storage.get(key);
        if (values == null || values.isEmpty()) {
            return null;
        }
        Set<V> result = new HashSet<V>();
        for (ObjectHolder value : values) {
            result.add((V)value.getObject());
        }
        return result;
    }
}

Thing that could seem strange is the left and right prefixed variable everywhere. You can think of them as left is the original key, that was putted to map and right is the value.

Usage example:

BiHashMultiMap<Integer, Integer> multiMap = new BiHashMultiMap<Integer, Integer>();

multiMap.put(0,5);
multiMap.put(1,10);
multiMap.put(2,7);
multiMap.put(3,7);
multiMap.put(2,8);
multiMap.put(3,11);

Set<Integer> left10 = multiMap.getLeft(10); // [1]
Set<Integer> left7 = multiMap.getLeft(7); // [2, 3]
Set<Integer> right0 = multiMap.getRight(0); // [5]
Set<Integer> right3 = multiMap.getRight(3); // [7, 11]

So to get left value we need to provide right value as key and to get right value we need to provide left as a key.

And of course to make map fully function we need to provide other methods, like remove(), contains() and so on.