Ответ 1
Две карты. Один Map<K1, V>
и один Map<K2, V>
. Если у вас должен быть один интерфейс, напишите класс-оболочку, который реализует указанные методы.
Мне нужна структура данных, которая ведет себя как карта,
но использует несколько (по-разному) ключей для доступа к своим значениям.
(Пусть не слишком общие, скажем два)
Ключи гарантированно будут уникальными.
Что-то вроде:
MyMap<K1,K2,V> ...
С помощью таких методов, как:
getByKey1(K1 key)...
getByKey2(K2 key)...
containsKey1(K1 key)...
containsKey2(K2 key)...
Есть ли у вас какие-либо предложения?
Единственное, о чем я могу думать:
Напишите класс, который использует два Maps внутри.
ИЗМЕНИТЬ
Некоторые люди предлагают мне использовать кортеж, пару или похожий как ключ для
Java Map, но этот не работает для меня:
Я должен уметь, как написано выше, искать значения только одним из двух указанных ключей.
Карты используют хэш-коды ключей и проверяют их равенство.
Две карты. Один Map<K1, V>
и один Map<K2, V>
. Если у вас должен быть один интерфейс, напишите класс-оболочку, который реализует указанные методы.
Коллекции Commons предоставляют только то, что вы ищете: http://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html
Похоже, теперь набираются коллективные коллекции.
Типовую версию можно найти по адресу: https://github.com/megamattron/collections-generic
Это будет точно поддерживать ваш прецедент:
MultiKeyMap<k1,k2,...,kn,v> multiMap = ??
Я все еще предлагаю решение карты 2, но с твистом
Map<K2, K1> m2;
Map<K1, V> m1;
Эта схема позволяет вам иметь произвольное количество ключевых "псевдонимов".
Он также позволяет вам обновлять значение с помощью любой клавиши, не получив синхронизацию карт.
Еще одно решение - использовать Google 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), иначе вы должны быть в порядке с первым.
Как насчет объявления следующего класса "Ключ":
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);
Предложение, предложенное некоторыми респондентами:
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);
}
}
Почему бы просто не отказаться от требования о том, что ключ должен быть конкретным типом, т.е. просто использовать Map < Object, V > .
Иногда дженерики просто не стоят дополнительной работы.
Я создал это, чтобы решить аналогичную проблему.
структура данных
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();
}
}
Получить значение:
(Примечание * Верните объект обратно в вставленный тип. В моем случае это был объект Event)
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);
Я вижу следующие подходы:
a) Используйте 2 разных карты. Вы можете обернуть их в класс, как вы предлагаете, но даже это может быть излишним. Просто используйте карты напрямую: key1Map.getValue(k1), key2Map.getValue(k2)
b) Вы можете создать класс ключей, поддерживающий тип, и использовать его (untested).
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
sol: cancatenate обе клавиши и сделать окончательный ключ, используйте это как ключ.
для значений ключа,
concatenate ket-1, а key-2 с пришедшим "," в beetween, используйте это как оригинальный ключ.
key = key-1 + "," + key-2;
myMap.put(ключ, значение);
аналогично при извлечении значений.
Звучит как кортеж Python. Следуя в этом духе, вы можете создать непреложный класс своего собственного проекта, который реализует Comparable, и вы его получите.
все многопользовательские ключи, вероятно, не работают, вызывают put ([key1, key2], val), а get ([null, key2]) заканчиваются с использованием значений [key1, key2] и [null, key2]. Если карта поддержки не содержит хеш-кодов на ключ, тогда поиск выполняется медленно.
Я думаю, что путь к использованию - это использование индексатора (см. примеры key1, key2 выше), и если дополнительный индексный ключ является свойствами сохраненного значения, вы можете использовать имя свойства и отражение для создания карт второго уровня, когда вы put (key, val) и добавить дополнительный метод get (propertyname, propertyvalue), чтобы использовать этот индекс.
возвращаемый тип get (propertyname, propertyvalue) может быть коллекцией, поэтому даже ни один уникальный ключ не индексируется....
Я использовал такую реализацию для нескольких ключевых объектов. Это позволяет мне использовать бесчисленное количество ключей для карты. Он масштабируемый и довольно простой. Но он имеет ограничения: ключи упорядочиваются в соответствии с порядком аргументов в конструкторе и не будут работать с 2D-массивами из-за использования Arrays.equals(). Чтобы исправить это, вы можете использовать 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)));
}
}
Как MultiMap, так и MultiKeyMap из Commons или Guava будут работать.
Однако быстрое и простое решение может заключаться в том, чтобы расширить класс карты, обрабатывая составной ключ самостоятельно, учитывая, что ключи имеют примитивный тип.
Определите класс, который имеет экземпляр K1 и K2. Затем используйте этот класс как ваш тип ключа.
См. Коллекции Google. Или, как вы полагаете, используйте карту внутри, и на этой карте используется пара. Вам нужно будет написать или найти Pair < > ; это довольно легко, но не является частью стандартных коллекций.
Похоже, ваше решение вполне правдоподобно для этой необходимости, я честно не вижу проблемы с этим, если ваши два ключевых типа действительно отличаются друг от друга. Просто заставляет вас писать свою собственную реализацию для этого и при необходимости решать проблемы синхронизации.
Если вы намереваетесь использовать комбинацию из нескольких ключей как один, то возможно apache commnons MultiKey - ваш друг, Я не думаю, что это будет работать один за другим, хотя..
В зависимости от того, как он будет использоваться, вы можете сделать это двумя картами Map<K1, V>
и Map<K2, V>
или двумя картами Map<K1, V>
и Map<K2, K1>
. Если один из ключей более постоянный, чем другой, второй вариант может иметь больше смысла.
Мне кажется, что методы, которые вы хотите в своем вопросе, поддерживаются непосредственно 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?
.....
}
Просто некоторые идеи...
Я бы предложил структуру
Map<K1, Map<K2, V>>
хотя поиск второго ключа может оказаться неэффективным
Я рекомендую что-то вроде этого:
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)
Преимущества: он прост, обеспечивает безопасность типов и не сохраняет повторяющиеся данные (как это делают два решения карт, хотя все равно сохраняет повторяющиеся значения). Недостатки: не общий.
Другое возможное решение, обеспечивающее возможность более сложных ключей, можно найти здесь: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html
Как насчет использования структуры данных trie?
http://en.wikipedia.org/wiki/Trie
Корень trie будет пустым. Братья и сестры первого уровня будут вашими первичными ключами на карте, а младшие братья второго уровня будут вашими вторичными ключами, а третьим уровнем будут терминальные узлы, у которых значение будет равно null, чтобы указать завершение этой ветки. Вы также можете добавить более двух клавиш, используя ту же схему.
Посмотрите, просто DFS.
Как насчет чего-то вроде этого:
В его заявлении говорится, что ключи уникальны, поэтому сохранение одинаковых объектов значений в отношении разных ключей вполне возможно, и когда вы отправляете какой-либо ключ, соответствующий указанному значению, мы могли бы вернуться к объекту значения.
Смотрите код ниже:
Значение Класс объекта,
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
Если ключи уникальны, тогда нет необходимости в двух картах, карте карт, mapOfWhateverThereIs. Должна быть только одна единственная карта и простой метод обертки, который помещает ваши ключи и значение в эту карту. Пример:
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");
}
Затем используйте свою карту, как обычно. Вам даже не нужны эти фантазии getByKeyN и содержитKeyN.
Загрязненное и простое решение, если вы используете карты только для сортировки, скажем, заключается в том, чтобы добавить к ключу очень маленькое значение до тех пор, пока значение не будет существовать, но не добавьте минимум (например Double.MIN_VALUE), потому что это вызовет ошибку. Как я уже сказал, это очень грязное решение, но делает код более простым.