Как создать HashMap с двумя ключами (пара ключей, значение)?


118

У меня есть 2D-массив целых чисел. Я хочу, чтобы они были помещены в HashMap. Но я хочу получить доступ к элементам из HashMap на основе индекса массива. Что-то вроде:

Для A [2] [5], map.get(2,5)который возвращает значение, связанное с этим ключом. Но как мне создать хэш-карту с парой ключей? Или, в общем, несколько ключей: Map<((key1, key2,..,keyN), Value)таким образом, чтобы я мог получить доступ к элементу с помощью get (key1, key2, ... keyN).

РЕДАКТИРОВАТЬ: через 3 года после публикации вопроса я хочу добавить к нему немного больше

Я нашел другой способ для NxN matrix.

Индексы массива iи jмогут быть представлены как единый keyследующим образом:

int key = i * N + j;
//map.put(key, a[i][j]); // queue.add(key); 

А индексы можно вернуть keyследующим образом:

int i = key / N;
int j = key % N;

Простое решение - сопоставить один ключ с другой хэш-картой.
Mihai8

1
Пожалуйста, не отвечайте на вопрос в вопросе. Ваша правка интересна, поэтому не стесняйтесь публиковать ее в качестве ответа.
Ole VV

@Crocode вау! математика, лежащая в основе ответа в Edit, блестящая. Просто интересно, работает ли это вообще для любых двух целых чисел i и j.
likejudo

@Crocode будет ли циклически повторяться i и j, если они кратны N?
likejudo

Ответы:


190

Есть несколько вариантов:

2 измерения

Карта карт

Map<Integer, Map<Integer, V>> map = //...
//...

map.get(2).get(5);

Обертка ключевого объекта

public class Key {

    private final int x;
    private final int y;

    public Key(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Key)) return false;
        Key key = (Key) o;
        return x == key.x && y == key.y;
    }

    @Override
    public int hashCode() {
        int result = x;
        result = 31 * result + y;
        return result;
    }

}

Реализация equals()и hashCode()здесь имеет решающее значение. Тогда вы просто используете:

Map<Key, V> map = //...

и:

map.get(new Key(2, 5));

Table из Гуавы

Table<Integer, Integer, V> table = HashBasedTable.create();
//...

table.get(2, 5);

Tableиспользует карту карт внизу.

Размеры N

Обратите внимание, что специальный Keyкласс - единственный подход, который масштабируется до n измерений. Вы также можете рассмотреть:

Map<List<Integer>, V> map = //...

но это ужасно с точки зрения производительности, а также читабельности и правильности (непростой способ установить размер списка).

Может быть, взгляните на Scala, где у вас есть кортежи и caseклассы (замена всего Keyкласса однострочным).


3
Привет, у других есть x'или два значения при выполнении hashCode. Почему вы используете 31? Я думал, что это как-то связано с 32-битным целым числом, но когда я думаю об этом, это не имеет смысла, потому что x = 1 и y = 0 по-прежнему отображаются на тот же хэш-код, что и x = 0 и y = 31
pete

1
@pete Джошуа Блох, в Эффективной Java, глава 3. s 9., рекомендует: «1. Сохраните некоторое постоянное ненулевое значение, скажем, 17, в переменной типа int с именем result ...», что лучше с точки зрения коллизий, если оно прайм. См. Также: stackoverflow.com/questions/3613102/…
fncomp

2
Почему бы не использовать вместо ключевого объекта Wrapper Map.Entry<K, V>в качестве ключа?
Roland

1
О чем Map<Pair<Key1, Key2>, Value>?
Хоакин Юрчук

1
обратите внимание, что hashCode()это также может быть реализовано с помощью одной строки какObjects.hash(x,y)
xdavidliu

23

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

Во-первых, вы должны знать о реализации hashCode()и equals(). Вам нужно будет это сделать.

Во-вторых, при внедрении hashCode()убедитесь, что вы понимаете, как это работает. Данный пример пользователя

public int hashCode() {
    return this.x ^ this.y;
}

на самом деле одна из худших реализаций, которые вы можете сделать. Причина проста: у вас много одинаковых хешей! И hashCode()должен возвращать значения типа int, которые, как правило, бывают редкими, в лучшем случае уникальными. Используйте что-то вроде этого:

public int hashCode() {
  return (X << 16) + Y;
}

Это быстро и возвращает уникальные хэши для ключей от -2 ^ 16 до 2 ^ 16-1 (от -65536 до 65535). Это подходит практически в любом случае. Очень редко вы выходите за эти границы.

В-третьих, при реализации equals()также знайте, для чего он используется, и знайте, как вы создаете свои ключи, поскольку они являются объектами. Часто вы делаете ненужные инструкции if, потому что у вас всегда будет один и тот же результат.

Если вы создаете такие ключи: map.put(new Key(x,y),V);вы никогда не сравните ссылки на свои ключи. Потому что каждый раз, когда вы хотите получить доступ к карте, вы будете делать что-то вроде map.get(new Key(x,y));. Поэтому вам equals()не нужно утверждение вроде if (this == obj). Этого никогда не будет .

Вместо того , чтобы if (getClass() != obj.getClass())в вашем equals()лучшем использовании if (!(obj instanceof this)). Это будет справедливо даже для подклассов.

Итак, единственное, что вам нужно сравнить, это на самом деле X и Y. Итак, лучшая equals()реализация в этом случае будет:

public boolean equals (final Object O) {
  if (!(O instanceof Key)) return false;
  if (((Key) O).X != X) return false;
  if (((Key) O).Y != Y) return false;
  return true;
}

Итак, в итоге ваш ключевой класс выглядит так:

public class Key {

  public final int X;
  public final int Y;

  public Key(final int X, final int Y) {
    this.X = X;
    this.Y = Y;
  }

  public boolean equals (final Object O) {
    if (!(O instanceof Key)) return false;
    if (((Key) O).X != X) return false;
    if (((Key) O).Y != Y) return false;
    return true;
  }

  public int hashCode() {
    return (X << 16) + Y;
  }

}

Вы можете указать свои индексы измерения Xи Yуровень публичного доступа, поскольку они являются окончательными и не содержат конфиденциальной информации. Я не уверен на 100%, правильно ли privateработает уровень доступа в любом случае при приведении Objectк файлу Key.

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


7

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

Создайте объект с именем Index, который принимает значения x и y.

public class Index {

    private int x;
    private int y;

    public Index(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode() {
        return this.x ^ this.y;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Index other = (Index) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }
}

Тогда HashMap<Index, Value>получите результат. :)


4
Вам нужно переопределить hashCodeи equals.
Tom Hawtin - tackline

4
реализация hashCode не различает (2,1) и (1,2)
user1947415

1
Это столкновение. Хэш-код не обязательно должен гарантировать разные значения для каждого объекта. @ user1947415
Ajak6

6

Реализовано в общих коллекциях MultiKeyMap


@Wilson Я исправил ссылку сейчас, жду экспертной оценки
computingfreak

@computingfreak, похоже, получил положительный отзыв. Ура! NB это лучший ответ ИМХО. Если только вы не хотите часами соревноваться с опытными инженерами Apache за некоторые очень полезные (как всегда), но в конечном итоге приземленные функциональные возможности.
mike rodent

4

Две возможности. Либо используйте комбинированный ключ:

class MyKey {
    int firstIndex;
    int secondIndex;
    // important: override hashCode() and equals()
}

Или карту карты:

Map<Integer, Map<Integer, Integer>> myMap;

2
Используйте карту карт только в том случае, если вам не важны производительность или использование памяти (т. Е. Карта мала) или имеется много ключей с одним и тем же первым индексом - поскольку это решение означает оплату накладных расходов объекта HashMap для каждый уникальный первый индекс.
BeeOnRope 03

1
Чтобы улучшить этот ответ, вот информация о переопределении hashCodeи equalsметодах.
Pshemo 03

2

Используйте в Pairкачестве ключей для HashMap. JDK не имеет пары, но вы можете использовать стороннюю библиотеку, такую ​​как http://commons.apache.org/lang, или написать свой собственный тайпе пары.


1

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

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

заботясь об отмене equals()и hashCode()правильно. Если это кажется большой работой, вы можете рассмотреть несколько готовых универсальных контейнеров, например, Pairпредоставляемых Apache Commons среди других.

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


1

Если это два целых числа, вы можете попробовать быстрый и грязный трюк: Map<String, ?>использовать ключ as i+"#"+j.

Если ключ i+"#"+jтакой же, как j+"#"+iпопробуйте min(i,j)+"#"+max(i,j).


2
Действительно плохая идея. Во-первых, это просто плохо. Во-вторых, этот метод будет скопирован для других типов, где другой ключ может быть сопоставлен с одним и тем же Stringс забавными последствиями.
Tom Hawtin - tackline

Мне кажется, что i#j = j#iпри i == jтаком использовании min/maxтрюка не пойдет.
Matthieu

1
@Matthieu какая разница между 5#5и 5#5поменять местами?
Энрей

@enrey Нет. Я на это указывал. Это действительно зависит от ваших знаний о ключах.
Матье

@Matthieu, ага, я понимаю, о чем ты. Я думаю, что @arutaku имел в виду, когда вы хотите 5#3иметь такой же хэш, как 3#5, тогда вы используете min / max для обеспечения соблюдения 3#5в этом порядке.
Энри

1

Вы также можете использовать для этого реализацию таблицы гуавы .

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

//create a table
  Table<String, String, String> employeeTable = HashBasedTable.create();

  //initialize the table with employee details
  employeeTable.put("IBM", "101","Mahesh");
  employeeTable.put("IBM", "102","Ramesh");
  employeeTable.put("IBM", "103","Suresh");

  employeeTable.put("Microsoft", "111","Sohan");
  employeeTable.put("Microsoft", "112","Mohan");
  employeeTable.put("Microsoft", "113","Rohan");

  employeeTable.put("TCS", "121","Ram");
  employeeTable.put("TCS", "122","Shyam");
  employeeTable.put("TCS", "123","Sunil");

  //get Map corresponding to IBM
  Map<String,String> ibmEmployees =  employeeTable.row("IBM");

1

Вы можете создать свой ключевой объект примерно так:

public class MapKey {

public  Object key1;
public Object key2;

public Object getKey1() {
    return key1;
}

public void setKey1(Object key1) {
    this.key1 = key1;
}

public Object getKey2() {
    return key2;
}

public void setKey2(Object key2) {
    this.key2 = key2;
}

public boolean equals(Object keyObject){

    if(keyObject==null)
        return false;

    if (keyObject.getClass()!= MapKey.class)
        return false;

    MapKey key = (MapKey)keyObject;

    if(key.key1!=null && this.key1==null)
        return false;

    if(key.key2 !=null && this.key2==null)
        return false;

    if(this.key1==null && key.key1 !=null)
        return false;

    if(this.key2==null && key.key2 !=null)
        return false;

    if(this.key1==null && key.key1==null && this.key2 !=null && key.key2 !=null)
        return this.key2.equals(key.key2);

    if(this.key2==null && key.key2==null && this.key1 !=null && key.key1 !=null)
        return this.key1.equals(key.key1);

    return (this.key1.equals(key.key1) && this.key2.equals(key2));
}

public int hashCode(){
    int key1HashCode=key1.hashCode();
    int key2HashCode=key2.hashCode();
    return key1HashCode >> 3 + key2HashCode << 5;
}

}

Преимущество этого: он всегда будет следить за тем, чтобы вы также охватывали все сценарии Equals.

ПРИМЕЧАНИЕ . Ваши ключи key1 и key2 должны быть неизменными. Только тогда вы сможете построить стабильный ключевой Объект.


1

мы можем создать класс для передачи более одного ключа или значения, и объект этого класса можно использовать в качестве параметра в карте.

import java.io.BufferedReader; 
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

 public class key1 {
    String b;
    String a;
    key1(String a,String b)
    {
        this.a=a;
        this.b=b;
    }
  }

public class read2 {

private static final String FILENAME = "E:/studies/JAVA/ReadFile_Project/nn.txt";

public static void main(String[] args) {

    BufferedReader br = null;
    FileReader fr = null;
    Map<key1,String> map=new HashMap<key1,String>();
    try {

        fr = new FileReader(FILENAME);
        br = new BufferedReader(fr);

        String sCurrentLine;

        br = new BufferedReader(new FileReader(FILENAME));

        while ((sCurrentLine = br.readLine()) != null) {
            String[] s1 = sCurrentLine.split(",");
            key1 k1 = new key1(s1[0],s1[2]);
            map.put(k1,s1[2]);
        }
        for(Map.Entry<key1,String> m:map.entrySet()){  
            key1 key = m.getKey();
            String s3 = m.getValue();
               System.out.println(key.a+","+key.b+" : "+s3);  
              }  
  //            }   
        } catch (IOException e) {

        e.printStackTrace();

    } finally {

        try {

            if (br != null)
                br.close();

            if (fr != null)
                fr.close();

        } catch (IOException ex) {

            ex.printStackTrace();

        }

    }

    }

 }

0

Вы можете скачать его по ссылке ниже: https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java

https://github.com/VVS279/DoubleKeyHashMap

Вы можете использовать двойной ключ: значение hashmap,

   DoubleKeyHashMap<Integer, Integer, String> doubleKeyHashMap1 = new 
   DoubleKeyHashMap<Integer, Integer, String>();

   DoubleKeyHashMap<String, String, String> doubleKeyHashMap2 = new 
   DoubleKeyHashMap<String, String, String>();
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.