我需要一个行为类似地图的数据结构, 但使用多个(不同类型的)键来访问其值 (我们不要太笼统,让我们说两个键)
密钥保证是唯一的。
类似的东西:
MyMap<K1,K2,V> ...
使用以下方法:
getByKey1(K1 key)...
getByKey2(K2 key)...
containsKey1(K1 key)...
containsKey2(K2 key)...
您有什么建议吗?
我唯一能想到的是:
写一个内部使用两个地图的类。
修改
有些人建议我使用元组,对或类似的键作为关键字
Java的地图,但这对我不起作用:
如上所述,我必须能够仅通过指定的两个键中的一个来搜索值
地图使用密钥的哈希码并检查它们的相等性。
答案 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 ;;它非常简单,但不是标准集合的一部分。