首页 文章

如何使用多个键实现Map? [重复]

提问于
浏览
135

这个问题在这里已有答案:

我需要一个行为类似于Map的数据结构,但使用多个(不同类型的)键来访问其值 .
(让's not be too general, let' s说 two 键)

Keys are guaranteed to be unique.

就像是:

MyMap<K1,K2,V> ...

使用以下方法:

getByKey1(K1 key)...
getByKey2(K2 key)...
containsKey1(K1 key)...
containsKey2(K2 key)...

Do you have any suggestions?

我唯一能想到的是:
编写一个内部使用两个Maps的类 .

EDIT 有些人建议我使用 tuplepair 或类似的作为Java Map 的关键,但这对我来说是 would not work
如上所述,我必须能够仅通过指定的两个键中的一个来搜索值 .
Map 使用密钥的哈希码并检查它们的相等性 .

27 回答

  • 0

    为什么不放弃密钥必须是特定类型的要求,即只使用Map <Object,V> .

    有时,仿制药不值得额外的工作 .

  • 2

    一个脏的和一个简单的解决方案,如果你只是为了排序而使用 Map ,那么就是给一个键添加一个非常小的值,直到该值不存在,但不添加最小值(例如Double.MIN_VALUE),因为它会导致错误 . 就像我说的,这是一个非常脏的解决方案,但它使代码更简单 .

  • 3

    那么你宣布以下“Key”类:

    public class Key {
       public Object key1, key2, ..., keyN;
    
       public Key(Object key1, Object key2, ..., Object keyN) {
          this.key1 = key1;
          this.key2 = key2;
          ...
          this.keyN = keyN;
       }
    
       @Override   
       public boolean equals(Object obj) {
          if (!(obj instanceof Key))
            return false;
          Key ref = (Key) obj;
          return this.key1.equals(ref.key1) && 
              this.key2.equals(ref.key2) &&
              ...
              this.keyN.equals(ref.keyN)
       }
    
        @Override
        public int hashCode() {
            return key1.hashCode() ^ key2.hashCode() ^ 
                ... ^ keyN.hashCode();
        }
    
    }
    

    声明 Map

    Map<Key, Double> map = new HashMap<Key,Double>();
    

    声明关键对象

    Key key = new Key(key1, key2, ..., keyN)
    

    填写 Map

    map.put(key, new Double(0))
    

    从 Map 中获取对象

    Double result = map.get(key);
    
  • 1

    我可以看到以下方法:

    a)使用2个不同的 Map . 你可以按照你的建议将它们包装在一个类中,但即便如此也可能是一种矫枉过正 . 只需直接使用 Map :key1Map.getValue(k1),key2Map.getValue(k2)

    b)您可以创建一个类型感知密钥类,并使用它(未经测试) .

    public class Key {
      public static enum KeyType { KEY_1, KEY_2 }
    
      public final Object k;
      public final KeyType t;
    
      public Key(Object k, KeyType t) {
        this.k = k;
        this.t= t;
      }
    
      public boolean equals(Object obj) {
        KeyType kt = (KeyType)obj;
        return k.equals(kt.k) && t == kt.t;
      }
    
      public int hashCode() {
       return k.hashCode() ^ t.hashCode();
      }
    }
    

    顺便说一句,在很多常见的情况下, key1 的空间和 key2 的空间不相交 . 在这种情况下,您实际上不需要做任何特别的事情 . 只需定义一个包含条目 key1=>v 以及 key2=>v 的 Map

  • 0

    sol:cancatenate两个键并制作一个最终键,使用它作为键 .

    对于关键值,

    在beetween中连接ket-1和key-2以及“,”,将其用作原始密钥 .

    key = key-1“,”key-2;

    myMap.put(键,值);

    同样在重温 Value 观的同时 .

  • 3

    Google Collections . 或者,如您所知,在内部使用 Map ,并使该 Map 使用一对 . 你很容易,但不是标准集合的一部分 .

  • 51

    我推荐这样的东西:

    public class MyMap {
    
          Map<Object, V> map = new HashMap<Object, V>();
    
    
          public V put(K1 key,V value){
            return map.put(key, value);
          }
    
          public V put(K2 key,V value){
            return map.put(key, value);
          }
    
          public V get(K1 key){    
            return map.get(key);
          }
    
          public V get(K2 key){    
            return map.get(key);
          }
    
          //Same for conatains
    
        }
    

    然后你就可以使用它:
    myMap.put(k1,value)myMap.put(k2,value)

    Advantages :它很简单,强制执行类型安全,并且不存储重复数据(因为两个 Map 解决方案都有,但仍存储重复值) .
    Drawbacks :不通用 .

  • 0

    两张 Map . 一个 Map<K1, V> 和一个 Map<K2, V> . 如果必须有单个接口,请编写实现所述方法的包装类 .

  • 0

    听起来你的解决方案对于这种需求是非常合理的,老实说,如果你的两个关键类型真的不同,我就不会发现问题 . 只需要为此编写自己的实现,并在需要时处理同步问题 .

  • 14

    Commons-collections提供您正在寻找的东西:https://commons.apache.org/proper/commons-collections/apidocs/

    看起来现在是commons-collections的类型 .

    可在以下位置找到打字版本:https://github.com/megamattron/collections-generic

    这将完全支持您的用例:

    MultiKeyMap<k1,k2,...,kn,v> multiMap = ??
    
  • 31

    如果键是唯一的,则不需要2个 Map , Map Map ,mapOfWhateverThereIs . 只需要一个单一的 Map ,只需要一个简单的包装方法,将您的键和值放入该 Map 中 . 例:

    Map<String, String> map = new HashMap<>();
    
    public void addKeysAndValue(String key1, String key2, String value){
        map.put(key1, value);
        map.put(key2, value);
    }
    
    public void testIt(){
        addKeysAndValue("behemoth", "hipopotam", "hornless rhino");
    }
    

    然后像往常一样使用你的 Map . 你甚至不需要那些花哨的getByKeyN和containsKeyN .

  • 0

    我创建了这个来解决类似的问题 .

    数据结构

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    
    public class HashBucket {
        HashMap<Object, ArrayList<Object>> hmap;
    
        public HashBucket() {
            hmap = new HashMap<Object, ArrayList<Object>>();
        }
    
        public void add(Object key, Object value) {
            if (hmap.containsKey(key)) {
                ArrayList al = hmap.get(key);
                al.add(value);
            } else {
                ArrayList al = new ArrayList<Object>();
                al.add(value);
                hmap.put(key, al);
            }
        }
    
        public Iterator getIterator(Object key) {
            ArrayList al = hmap.get(key);
            return hmap.get(key).iterator();
    
        }
    
    }
    

    检索值:

    (注意*将对象强制转换为插入的类型 . 在我的情况下,它是我的事件对象)

    public Iterator getIterator(Object key) {
            ArrayList al = hmap.get(key);
            if (al != null) {
                return hmap.get(key).iterator();
            } else {
                List<Object> empty = Collections.emptyList();
                return empty.iterator();
            }
    
        }
    

    插入

    Event e1 = new Event();
    e1.setName("Bob");
    e1.setTitle("Test");
    map.add("key",e1);
    
  • 1

    听起来像Python元组 . 遵循这种精神,你可以创建一个自己设计的不可变类,实现Comparable,你就拥有它 .

  • 0

    这样的事情怎么样:

    他的声明说键是唯一的,所以很可能在不同的键上保存相同的值对象,当你发送任何匹配上述值的键时,我们就可以回到值对象了 .

    见下面的代码:

    一个值Object Class,

    public class Bond {
        public Bond() {
            System.out.println("The Name is Bond... James Bond...");
        }
        private String name;
        public String getName() { return name;}
        public void setName(String name) { this.name = name; }
    }
    
    public class HashMapValueTest {
    
        public static void main(String[] args) {
    
            String key1 = "A";
            String key2 = "B";
            String key3 = "C";
    
            Bond bond = new Bond();
            bond.setName("James Bond Mutual Fund");
    
            Map<String, Bond> bondsById = new HashMap<>();
    
            bondsById.put(key1, bond);
            bondsById.put(key2, bond);
            bondsById.put(key3, bond);
    
            bond.setName("Alfred Hitchcock");
    
            for (Map.Entry<String, Bond> entry : bondsById.entrySet()) {
                System.out.println(entry.getValue().getName());
            }
    
        }
    
    }
    

    结果是:

    The Name is Bond... James Bond...
    
    Alfred HitchCock
    
    Alfred HitchCock
    
    Alfred HitchCock
    
  • 2

    另一种解决方案是使用Google's Guava

    import com.google.common.collect.Table;
    import com.google.common.collect.HashBasedTable;
    
    Table<String, String, Integer> table = HashBasedTable.create();
    

    用法非常简单:

    String row = "a";
    String column = "b";
    int value = 1;
    
    if (!table.contains(row, column)) {
        table.put(row, column, value);
    }
    
    System.out.println("value = " + table.get(row, column));
    

    方法 HashBasedTable.create() 基本上是这样做的:

    Table<String, String, Integer> table = Tables.newCustomTable(
            Maps.<String, Map<String, Integer>>newHashMap(),
            new Supplier<Map<String, Integer>>() {
        public Map<String, Integer> get() {
            return Maps.newHashMap();
        }
    });
    

    如果你想创建一些自定义 Map ,你应该选择第二个选项(如@Karatheodory建议的那样),否则你应该对第一个选项没问题 .

  • 40

    所有multy键可能都会失败,导致put([key1,key2],val)和get([null,key2])最后使用[key1,key2]和[null,key2]的等号 . 如果支持映射不包含每个密钥的散列桶,那么查找真的很慢 .

    我认为要走的路是使用一个索引装饰器(参见上面的key1,key2示例),如果额外的索引键是存储值的属性,你可以使用属性名称和反射来构建第二个映射,当你放入(key,val)并添加一个额外的方法get(propertyname,propertyvalue)使用该索引 .

    get(propertyname,propertyvalue)的返回类型可以是Collection,因此即使没有唯一键也被索引....

  • 0

    Commons或Guava的MultiMap或MultiKeyMap都可以使用 .

    但是,考虑到键是原始类型,快速简单的解决方案可能是自己扩展Map类购买处理复合键 .

  • 0

    可以在此处找到另一种可能提供更复杂密钥的可能解决方案:http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html

  • 5

    我仍然会建议2 Map 解决方案,但有一个推特

    Map<K2, K1> m2;
    Map<K1, V>  m1;
    

    此方案允许您拥有任意数量的键“别名” .

    它还允许您通过任何键更新值,而不会使 Map 不同步 .

  • 0

    如果你打算使用几个键的组合作为一个,那么也许apache commnons MultiKey是你的朋友 . 我不认为它会一个接一个地工作..

  • 0

    在我看来,您在问题中想要的方法直接由Map支持 . 你似乎想要的是

    put(K1 key, K2 key, V value)
    put(K1 key, V value)
    put(K2 key, V value)
    

    请注意,在map中, get()containsKey() 等都需要 Object 参数 . 没有什么可以阻止您使用一个 get() 方法委派给您组合的所有复合 Map (如您的问题和其他答案中所述) . 也许你得到了类铸造问题(如果他们特别天真地实施的话) .

    基于类型的注册还允许您检索要使用的“正确”映射:

    Map<T,V> getMapForKey(Class<T> keyClass){
      //Completely naive implementation - you actually need to 
      //iterate through the keys of the maps, and see if the keyClass argument
      //is a sub-class of the defined map type.  And then ordering matters with 
      //classes that implement multiple interfaces...
      Map<T,V> specificTypeMap = (Map<T,V) maps.get(keyClass);
      if (specificTypeMap == null){
         throw new IllegalArgumentException("There is no map keyed by class" + keyClass);
      }
      return maps.get(keyClass);
    }
    
    V put(Object key, V value) {
      //This bit requires generic suppression magic - but 
      //nothing leaves this class and you're testing it right? 
      //(You can assert that it *is* type-safe)
      Map map = getMapForKey(key.getClass());
      map.put(object, key);
    }
    
    void put(Object[] keys, V value) { //Or put(V value, Object ... keys)
       //Might want to catch exceptions for unsupported keys and log instead?
       .....
    }
    

    只是一些想法......

  • 0

    定义具有K1和K2实例的类 . 然后将其用作类作为键类型 .

  • 0

    如何使用trie数据结构?

    http://en.wikipedia.org/wiki/Trie

    trie的根将是空白的 . 第一级兄弟将成为 Map 的主键,第二级兄弟将成为您的辅助键,第三级将是具有值的终端节点将为null以指示该分支的终止 . 您还可以使用相同的方案添加两个以上的密钥 .

    查找是简单的DFS .

  • 1

    我会建议结构

    Map<K1, Map<K2, V>>
    

    虽然搜索第二个密钥可能效率不高

  • 0

    我将这种实现用于多个关键对象 . 它允许我使用无数个键来映射 . 它可扩展且非常简单 . 但它有局限性:键是按照构造函数中的参数顺序排序的,因为使用了Arrays.equals(),它不能用于2D数组 . 要修复它 - 你可以使用Arrays.deepEquals();

    希望它会对你有所帮助 . 如果您知道为什么它不能用作解决此类问题的任何原因 - 请告诉我!

    public class Test {
    
        private static Map<InnumerableKey, Object> sampleMap = new HashMap<InnumerableKey, Object>();
    
        private static class InnumerableKey {
    
            private final Object[] keyParts;
    
            private InnumerableKey(Object... keyParts) {
                this.keyParts = keyParts;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (!(o instanceof InnumerableKey)) return false;
    
                InnumerableKey key = (InnumerableKey) o;
    
                if (!Arrays.equals(keyParts, key.keyParts)) return false;
    
                return true;
            }
    
            @Override
            public int hashCode() {
                return keyParts != null ? Arrays.hashCode(keyParts) : 0;
            }
        }
    
        public static void main(String... args) {
            boolean keyBoolean = true;
            double keyDouble = 1d;
            Object keyObject = new Object();
    
            InnumerableKey doubleKey = new InnumerableKey(keyBoolean, keyDouble);
            InnumerableKey tripleKey = new InnumerableKey(keyBoolean, keyDouble, keyObject);
    
            sampleMap.put(doubleKey, "DOUBLE KEY");
            sampleMap.put(tripleKey, "TRIPLE KEY");
    
            // prints "DOUBLE KEY"
            System.out.println(sampleMap.get(new InnumerableKey(true, 1d)));
            // prints "TRIPLE KEY"
            System.out.println(sampleMap.get(new InnumerableKey(true, 1d, keyObject)));
            // prints null
            System.out.println(sampleMap.get(new InnumerableKey(keyObject, 1d, true)));
        }
    }
    
  • 0

    根据它的使用方式,您可以使用两个 Map Map<K1, V>Map<K2, V> 或两个 Map Map<K1, V>Map<K2, K1> 来执行此操作 . 如果其中一个键比另一个键更永久,则第二个选项可能更有意义 .

  • 87

    Proposal, as suggested by some answerers:

    public interface IDualMap<K1, K2, V> {
    
        /**
        * @return Unmodifiable version of underlying map1
        */
        Map<K1, V> getMap1();
    
        /**
        * @return Unmodifiable version of underlying map2
        */
        Map<K2, V> getMap2();
    
        void put(K1 key1, K2 key2, V value);
    
    }
    
    public final class DualMap<K1, K2, V>
            implements IDualMap<K1, K2, V> {
    
        private final Map<K1, V> map1 = new HashMap<K1, V>();
    
        private final Map<K2, V> map2 = new HashMap<K2, V>();
    
        @Override
        public Map<K1, V> getMap1() {
            return Collections.unmodifiableMap(map1);
        }
    
        @Override
        public Map<K2, V> getMap2() {
            return Collections.unmodifiableMap(map2);
        }
    
        @Override
        public void put(K1 key1, K2 key2, V value) {
            map1.put(key1, value);
            map2.put(key2, value);
        }
    }
    

相关问题