14

What I need is a collection which allows multiple keys to access a single object.

I need to apply frequent alterations to this object.

It also must be efficient for 500k+ entries.

Duncan
  • 1,758
  • 6
  • 21
  • 34
  • Did you reverse your question by accident? Because you state "...Map allows for one key to access multiple values", which Isn't true of the Map interface. It's a one to one mapping between the key and value (though, of course, your value can be a collection) – Falaina Aug 06 '09 at 08:31
  • When you say *"frequent alterations to this object"* what do you mean exactly? Do you mean that the object is mutable and you change its state? Or do you mean that you need to replace one mapping with another (and replace for each associated key)? – oxbow_lakes Aug 06 '09 at 08:47

5 Answers5

21

Any implementation of java.util.Map<K,V> will do this - there is no restriction on how many times a particular value can be added under separate keys:

Map<String,Integer> m = new HashMap<String, Integer>();
m.put("Hello", 5);
m.put("World", 5);
System.out.println(m); // { Hello->5, World->5 }  

If you want a map where a single key is associated with multiple values, this is called a multi-map and you can get one from the google java collections API or from Apache's commons-collections

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 5
    the problem with this approach is that you can't rapidly remove the object from all keys associated with it – njzk2 Nov 14 '11 at 16:16
  • @njzk2 that's not necessarily a requirement. "It must be efficient for 500k+ entries" is very vague. – Daniel Kaplan Feb 23 '17 at 18:07
8

I sort of interpreted his request differently. What if one wants two completely different keysets to access the same underlying values. For example:

    "Hello"    ------|
                     |----> firstObject
       3       ------|

    "Monkey"   ------|
                     |----> secondObject
       72      ------|

       14      -----------> thirdObject

   "Baseball"  ------|
                     |----> fourthObject
       18      ------|

Obviously having two maps, one for the integer keys and one for the String keys, isn't going to work, since an update in one map won't reflect in the other map. Supposing you modified the Map<String,Object>, updating "Monkey" to map to fifthObject. The result of this modification is to change the Entry<String,Object> within that map, but this of course has no effect on the other map. So whilst what you intended was:

    "Monkey"   ------|
                     |----> fifthObject
       72      ------|

what you'd get in reality would be this:

    "Monkey"   -----------> fifthObject

       72      -----------> secondObject

what I do in this situation is to have the two side by side maps, but instead of making them say Map<String, Integer> I would make them Map<String, Integer[]>, where the associated array is a single member array. The first time I associate a key with a value, if no array exists yet and the key returns null, I create the array, and associate any other key I wish to with it (in that key's map). Subsequently, I only modify the array's contents, but never the reference to the array itself, and this works a charm.

    "Monkey"   -------> fifthObjectArray ------|
                                               |-----> fifthObjectArray[0]
       72      -------> fifthObjectArray ------|
fragorl
  • 1,698
  • 15
  • 19
  • And it doesn't have to be two different classes that comprise the keysets either - they could both be strings, for example – fragorl Jun 17 '10 at 23:30
5

Uhm…

Map map = new HashMap();
Object someValue = new Object();
map.put(new Object(), someValue);
map.put(new Object(), someValue);

Now the map contains the same value twice, accessible via different keys. If that’s not what you’re looking for you should rework your question. :)

Bombe
  • 81,643
  • 20
  • 123
  • 127
3

this may do what you want:

import java.util.*;
class Value {
    public String toString() {
        return x.toString();
    }
    Integer x=0;
}
public class Main {
    public static void main(String[] arguments) {
        Map m=new HashMap();
        final Value v=new Value();
        m.put(1,v);
        m.put(2,v);
        System.out.println(m.get(1));
        System.out.println(m.get(2));
        v.x=42;
        System.out.println(m.get(1));
        System.out.println(m.get(2));
    }
Ray Tayek
  • 9,841
  • 8
  • 50
  • 90
1

Your Question actually got me thinking of making this class to handle such a thing. I am currently working on a 2D game engine and your question completely made me think of exactly what I needed.

By the way you've worded it, I believe what you want is;

An object that holds keys and values, but you can also get the common keys values hold (I use this object specifically to cut down on cpu at the cost of using just a little more memory.)

This Class' The K type is the Primary Key type. The T type is the HashSet Value Type.

The way you implement and use this object is:

MapValueSet<ObjectType1,ObjectType2> mainmap = new  

MapValueSet<ObjectType1,ObjectType2>()
HashSet<Integer> tags = new HashSet<Integer>();
       public void test(){
            ObjectType1 = new ObjectType1();
            ObjectType2 = new ObjectType2();

            tags.add(mainmap.put(ObjectType1,ObjectType2);
            mainmap.get(ObjectType1,Integer);
       } 

You will need to hold the unique tags in a set or arraylist in any class you implement this because if you didn't you'd be storing entities and not know which one was which. So store the integer you get from the put() method into an arraylist or set, and iterate through that.

You can check this Class's values if they exist, or which key objects the value is set to.

Here is the Class MapValueSet;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class MapValueSet<K,T> {

        Indexer indxK = new Indexer();
        Indexer indxT = new Indexer();

        Map<K,Integer> kTags = new HashMap<K,Integer>();
        Map<T,Integer> tTags = new HashMap<T,Integer>();

        Map<Integer,HashSet<Integer>> MapK = new HashMap<Integer,HashSet<Integer>>();

        Map<Integer,HashSet<Integer>> MapT = new HashMap<Integer,HashSet<Integer>>();

public int put(K k, T t){
    int tag = -1;
    if(!kTags.containsKey(k)){
        kTags.put(k, indxK.getNextTag());
    }

    if(!MapK.containsKey(kTags.get(k))){
        MapK.put(kTags.get(k), new HashSet<Integer>()); 
    }

    if(!tTags.containsKey(t)){
        tTags.put(t, tag = indxT.getNextTag());
    }

    if(!MapT.containsKey(tTags.get(t))){
        MapT.put(tag = tTags.get(t), new HashSet<Integer>());
    }       
        MapK.get(kTags.get(k)).add(tTags.get(t));
        MapT.get(tag = tTags.get(t)).add(kTags.get(k)); 

    return tag;
}

       @SuppressWarnings("unchecked")
         public T get(K k, int tag){
            Object[] tArr = tTags.keySet().toArray();
            for(int i = 0; i < tArr.length; i++){
              if(tTags.get((T)tArr[i])== tag){
                    return (T)tArr[i];
             }
           }
           return null;
        }

        public boolean removeAtKey(K k, T t){
                int kTag = -1;
                int tTag = -1;

                if(kTags.get(k) != null){
                kTag = kTags.get(k);
                }

                if(tTags.get(t) != null){
                tTag = tTags.get(t);
                }

                if(kTag == -1 || tTag == -1){
                        System.out.println("Keys are Blank at: removeAtKey(k,t)");
                        return false;
                }

                boolean removed = false;

                        if(MapK.get(kTag) != null){
                                removed = MapK.get(kTag).remove(tTag);
                        }
                        if(MapT.get(tTag) != null){
                                MapT.get(tTag).remove(kTag);
                        }

                        if(!MapK.containsKey(kTag)){
                                kTags.remove(k);
                                indxK.removeTag(kTag);
                        }

                        if(MapK.containsKey(kTag)){
                                tTags.remove(t);
                                indxT.removeTag(tTag); 

                        }

                return removed;
        }

        public void removeAtValue(T t){
                if(!tTags.containsKey(t)){
                        return;
                }
                Object[] keyArr = MapT.get(tTags.get(t)).toArray();

                for(int i = 0; i < keyArr.length; i++){
                        MapK.get(keyArr[i]).remove(tTags.get(t));
                }

                        indxT.removeTag(tTags.get(t));
                        MapT.remove(tTags.get(t));
                        tTags.remove(t);
        }

        public boolean mapContains(T t){
                if(tTags.get(t) == null){
                        return false;
                }
                int tTag = tTags.get(t);

                return MapT.get(tTag) != null && !MapT.get(tTag).isEmpty();
        }

        public boolean containsKey(K k){

                if(kTags.get(k) == null){
                        return false;
                }

                return MapK.containsKey(kTags.get(k));
        }

        public boolean keyContains(K k, T t){

                if(kTags.get(k) != null && tTags.get(t) != null){
                        return MapK.get(kTags.get(k)).contains(tTags.get(t));
                }

                return false;

        }

        @Override
        public String toString(){

                String s = "";

                s = s+ "Key      Map: " + MapK.toString() + "\n";
                s = s+ "Value    Map: " + MapT.toString() + "\n";
                s = s+ "KeyTag   Map: " + kTags.toString() + "\n";
                s = s+ "ValueTag Map: " + tTags.toString() + "\n";
                s = s+ "KeyTag   List: " + indxK.activeSet().toString() + "\n";
                s = s+ "ValueTag List: " + indxT.activeSet().toString();

                return s;              
        }


}
Mice
  • 11
  • 1