Як створити HashMap з двома клавішами (Key-Pair, Value)?


118

У мене є двовимірний масив цілих чисел. Я хочу, щоб вони були поміщені в HashMap. Але я хочу отримати доступ до елементів HashMap на основі Array Index. Щось на зразок:

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

EDIT: Через 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
Будь ласка, не відповідайте на запитання. Ваша редакція цікава, тому сміливо публікуйте її як відповідь.
Оле ВВ

@Crocode вау! математика, що стоїть за відповіддю у редагуванні, є сцинтиляльною. Цікаво, чи працює він взагалі для будь-яких двох цілих чисел 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 ch 3. s 9., рекомендує "1. Зберігати деяке постійне ненульове значення, скажімо, 17, в змінній int, що називається результатом ...", що краще спрацьовує зіткнення, якщо це прем'єр. Дивіться також: 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() також знайте, для чого це використовується, і будьте в курсі того, як ви створюєте свої ключі, оскільки вони є об'єктами. Часто ви робите непотрібне, якщо висловлювання викликають у вас завжди однаковий результат.

Якщо ви створюєте такі ключі: 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на aKey .

Якщо вам цікаво про фінал, я оголошую що-небудь як остаточне, значення якого встановлюється при інстанції і ніколи не змінюється - і тому є постійною об'єктом.


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.
Том Хотін - тайклін

4
реалізація хеш-коду не розмежувала (2,1) та (1,2)
user1947415,

1
Це зіткнення. Хеш-коду не потрібно гарантувати різне значення для кожного різного об'єкта. @ user1947415
Ajak6

6

Реалізовано в загальних колекціях MultiKeyMap


@Wilson Я виправив посилання зараз, чекаючи експертної оцінки
computingfreak

@computingfreak здається сприятливим видом. Ура! NB це найкраща відповідь ІМХО. Якщо ви не захочете витратити години, змагаючись з експертами Apache інженерів за деякі дуже корисні (як ніколи), але в кінцевому підсумку приземливі функції.
гризун

4

Дві можливості. Використовуйте комбінований ключ:

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

Або карта карти:

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

2
Використовуйте карту карт лише, якщо вам не важливо продуктивність або використання пам'яті (тобто карта невелика) або є багато клавіш з тим самим першим індексом - оскільки це рішення означає оплату накладних витрат об’єкта HashMap за кожен унікальний перший індекс.
BeeOnRope

1
Щоб покращити цю відповідь, тут інформація про переосмислення hashCodeта equalsметоди.
Пшемо

2

Використовуйте Pairяк клавіші для HashMap. У JDK немає пари, але ви можете використовувати бібліотеку сторонніх організацій, наприклад http://commons.apache.org/lang, або написати свій власний пара.


1

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

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

дбаючи про переосмислення equals()та hashCode()правильність. Якщо це здається великою роботою, ви можете розглянути кілька готових універсальних контейнерів, таких якPair наданих апашем спільнотами серед інших.

Тут також є багато подібних питань з іншими ідеями, такими як використання гуави Таблиця , хоча дозволяє клавішам мати різні типи, що може бути надмірним (у використанні пам'яті та складності) у вашому випадку, оскільки я розумію, що ваші ключі є обома цілими числами.


1

Якщо це два цілих числа, ви можете спробувати швидкий і брудний трюк: Map<String, ?>використовуючи ключ якi+"#"+j .

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


2
Дійсно погана ідея. По-перше, це просто погано. По-друге, ця техніка буде скопійована для інших типів, коли різний ключ може бути нанесений на той самий Stringіз веселими наслідками.
Том Хотін - тайклін

Мені здається, що i#j = j#iпри i == jвикористанні цього min/maxтрюку не вийде.
Матьє

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

Ви можете створити свій ключовий об’єкт приблизно так:

публічний клас 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;
}

}

Перевага цього полягає в тому, що ви завжди будете впевнені, що ви також охоплюєте всі сценарії рівних.

ПРИМІТКА . Ваші 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.