如何使用多个键实现Map?

时间:2009-05-04 22:07:39

标签: java data-structures

我需要一个行为类似地图的数据结构, 但使用多个(不同类型的)键来访问其值 (我们不要太笼统,让我们说两个键)

密钥保证是唯一的。

类似的东西:

MyMap<K1,K2,V> ...

使用以下方法:

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

您有什么建议吗?

我唯一能想到的是:
写一个内部使用两个地图的类。

修改 有些人建议我使用元组或类似的键作为关键字 Java的地图,但这对我不起作用
如上所述,我必须能够仅通过指定的两个键中的一个来搜索值 地图使用密钥的哈希码并检查它们的相等性。

27 个答案:

答案 0 :(得分:90)

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

答案 1 :(得分:48)

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

现在看起来已经输入了commons-collections。

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

这将完全支持您的用例:

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

答案 2 :(得分:39)

我仍然会建议2地图解决方案,但是有一个推文

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

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

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

答案 3 :(得分:32)

另一种解决方案是使用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();
    }
});

如果您正在尝试创建一些自定义地图,您应该选择第二个选项(如@Karatheodory建议的那样),否则您应该可以使用第一个选项。

答案 4 :(得分:15)

你宣布以下“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<Key, Double> map = new HashMap<Key,Double>();

声明关键对象

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

填写地图

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

从地图中获取对象

Double result = map.get(key);

答案 5 :(得分:5)

提案,正如一些回答者所建议的那样:

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);
    }
}

答案 6 :(得分:4)

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

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

答案 7 :(得分:3)

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

数据结构网络

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);

答案 8 :(得分:2)

我可以看到以下方法:

a)使用2张不同的地图。你可以按照你的建议将它们包装在一个类中,但即便如此也可能是一种矫枉过正。只需直接使用地图: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

的地图

答案 9 :(得分:2)

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

表示关键值,

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

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

myMap.put(键,值);

同样在追求价值的同时。

答案 10 :(得分:2)

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

我认为要采用的方法是使用索引修饰符(参见上面的key1,key2示例),如果额外的索引键是存储值的属性,则可以使用属性名称和反射来构建第二个地图。 put(key,val)并添加一个额外的方法get(propertyname,propertyvalue)来使用该索引。

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

答案 11 :(得分:1)

我将这种实现用于多个关键对象。 它允许我使用无数个键来映射。它可扩展且非常简单。 但它有局限性:键是按照构造函数中的参数顺序排序的,因为使用了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)));
    }
}

答案 12 :(得分:1)

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

答案 13 :(得分:0)

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

Label

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

答案 14 :(得分: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

答案 15 :(得分:0)

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

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

答案 16 :(得分:0)

使用trie数据结构怎么样?

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

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

查找简单的DFS。

答案 17 :(得分:0)

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

答案 18 :(得分:0)

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

答案 19 :(得分:0)

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

答案 20 :(得分:0)

我推荐这样的东西:

    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)

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

答案 21 :(得分:0)

我会建议结构

Map<K1, Map<K2, V>>

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

答案 22 :(得分:0)

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

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

请注意,在地图中,get()containsKey()等都会采用Object个参数。没有什么可以阻止您使用一个get()方法委派给您组合的所有复合地图(如您的问题和其他答案中所述)。也许你需要进行类型注册,这样你就不会遇到类转换问题(如果它们特殊而且天真地实现。

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

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?
   .....
}

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

答案 23 :(得分:0)

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

答案 24 :(得分:0)

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

答案 25 :(得分:0)

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

答案 26 :(得分:0)

Google Collections。或者,如您所知,在内部使用地图,并使该地图使用一对。你必须写或找到Pair&lt;&gt ;;它非常简单,但不是标准集合的一部分。