Как реализовать карту с несколькими ключами?

Мне нужна структура данных, которая ведет себя как карта, но использует несколько (по-разному) ключей для доступа к своим значениям.
(Пусть не слишком общие, скажем два)

Ключи гарантированно будут уникальными.

Что-то вроде:

MyMap<K1,K2,V> ...

С помощью таких методов, как:

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

Есть ли у вас какие-либо предложения?

Единственное, о чем я могу думать:
Напишите класс, который использует два Maps внутри.

ИЗМЕНИТЬ Некоторые люди предлагают мне использовать кортеж, пару или похожий как ключ для Java Map, но этот не работает для меня:
Я должен уметь, как написано выше, искать значения только одним из двух указанных ключей.
Карты используют хэш-коды ключей и проверяют их равенство.

Ответ 1

Две карты. Один Map<K1, V> и один Map<K2, V>. Если у вас должен быть один интерфейс, напишите класс-оболочку, который реализует указанные методы.

Ответ 2

Коллекции 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 = ??

Ответ 3

Я все еще предлагаю решение карты 2, но с твистом

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

Эта схема позволяет вам иметь произвольное количество ключевых "псевдонимов".

Он также позволяет вам обновлять значение с помощью любой клавиши, не получив синхронизацию карт.

Ответ 4

Еще одно решение - использовать 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), иначе вы должны быть в порядке с первым.

Ответ 5

Как насчет объявления следующего класса "Ключ":

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

Ответ 6

Предложение, предложенное некоторыми респондентами:

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

Ответ 7

Почему бы просто не отказаться от требования о том, что ключ должен быть конкретным типом, т.е. просто использовать Map < Object, V > .

Иногда дженерики просто не стоят дополнительной работы.

Ответ 8

Я создал это, чтобы решить аналогичную проблему.

структура данных

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

Ответ 9

Я вижу следующие подходы:

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

Ответ 10

sol: cancatenate обе клавиши и сделать окончательный ключ, используйте это как ключ.

для значений ключа,

concatenate ket-1, а key-2 с пришедшим "," в beetween, используйте это как оригинальный ключ.

key = key-1 + "," + key-2;

myMap.put(ключ, значение);

аналогично при извлечении значений.

Ответ 11

Звучит как кортеж Python. Следуя в этом духе, вы можете создать непреложный класс своего собственного проекта, который реализует Comparable, и вы его получите.

Ответ 12

все многопользовательские ключи, вероятно, не работают, вызывают put ([key1, key2], val), а get ([null, key2]) заканчиваются с использованием значений [key1, key2] и [null, key2]. Если карта поддержки не содержит хеш-кодов на ключ, тогда поиск выполняется медленно.

Я думаю, что путь к использованию - это использование индексатора (см. примеры key1, key2 выше), и если дополнительный индексный ключ является свойствами сохраненного значения, вы можете использовать имя свойства и отражение для создания карт второго уровня, когда вы put (key, val) и добавить дополнительный метод get (propertyname, propertyvalue), чтобы использовать этот индекс.

возвращаемый тип get (propertyname, propertyvalue) может быть коллекцией, поэтому даже ни один уникальный ключ не индексируется....

Ответ 13

Я использовал такую ​​реализацию для нескольких ключевых объектов. Это позволяет мне использовать бесчисленное количество ключей для карты. Он масштабируемый и довольно простой. Но он имеет ограничения: ключи упорядочиваются в соответствии с порядком аргументов в конструкторе и не будут работать с 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)));
    }
}

Ответ 14

Как MultiMap, так и MultiKeyMap из Commons или Guava будут работать.

Однако быстрое и простое решение может заключаться в том, чтобы расширить класс карты, обрабатывая составной ключ самостоятельно, учитывая, что ключи имеют примитивный тип.

Ответ 15

Определите класс, который имеет экземпляр K1 и K2. Затем используйте этот класс как ваш тип ключа.

Ответ 16

См. Коллекции Google. Или, как вы полагаете, используйте карту внутри, и на этой карте используется пара. Вам нужно будет написать или найти Pair < > ; это довольно легко, но не является частью стандартных коллекций.

Ответ 17

Похоже, ваше решение вполне правдоподобно для этой необходимости, я честно не вижу проблемы с этим, если ваши два ключевых типа действительно отличаются друг от друга. Просто заставляет вас писать свою собственную реализацию для этого и при необходимости решать проблемы синхронизации.

Ответ 18

Если вы намереваетесь использовать комбинацию из нескольких ключей как один, то возможно apache commnons MultiKey - ваш друг, Я не думаю, что это будет работать один за другим, хотя..

Ответ 19

В зависимости от того, как он будет использоваться, вы можете сделать это двумя картами Map<K1, V> и Map<K2, V> или двумя картами Map<K1, V> и Map<K2, K1>. Если один из ключей более постоянный, чем другой, второй вариант может иметь больше смысла.

Ответ 20

Мне кажется, что методы, которые вы хотите в своем вопросе, поддерживаются непосредственно 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?
   .....
}

Просто некоторые идеи...

Ответ 21

Я бы предложил структуру

Map<K1, Map<K2, V>>

хотя поиск второго ключа может оказаться неэффективным

Ответ 22

Я рекомендую что-то вроде этого:

    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)

Преимущества: он прост, обеспечивает безопасность типов и не сохраняет повторяющиеся данные (как это делают два решения карт, хотя все равно сохраняет повторяющиеся значения). Недостатки: не общий.

Ответ 24

Как насчет использования структуры данных trie?

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

Корень trie будет пустым. Братья и сестры первого уровня будут вашими первичными ключами на карте, а младшие братья второго уровня будут вашими вторичными ключами, а третьим уровнем будут терминальные узлы, у которых значение будет равно null, чтобы указать завершение этой ветки. Вы также можете добавить более двух клавиш, используя ту же схему.

Посмотрите, просто DFS.

Ответ 25

Как насчет чего-то вроде этого:

В его заявлении говорится, что ключи уникальны, поэтому сохранение одинаковых объектов значений в отношении разных ключей вполне возможно, и когда вы отправляете какой-либо ключ, соответствующий указанному значению, мы могли бы вернуться к объекту значения.

Смотрите код ниже:

Значение Класс объекта,

    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

Ответ 26

Если ключи уникальны, тогда нет необходимости в двух картах, карте карт, 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.

Ответ 27

Загрязненное и простое решение, если вы используете карты только для сортировки, скажем, заключается в том, чтобы добавить к ключу очень маленькое значение до тех пор, пока значение не будет существовать, но не добавьте минимум (например Double.MIN_VALUE), потому что это вызовет ошибку. Как я уже сказал, это очень грязное решение, но делает код более простым.