Bagaimana cara membuat HashMap dengan dua kunci (Key-Pair, Value)?

118

Saya memiliki array 2D dari Integer. Saya ingin mereka dimasukkan ke dalam HashMap. Tapi saya ingin mengakses elemen dari HashMap berdasarkan Array Index. Sesuatu seperti:

Untuk A [2] [5], map.get(2,5)yang mengembalikan nilai yang terkait dengan kunci itu. Tapi bagaimana cara membuat hashMap dengan sepasang kunci? Atau secara umum, beberapa kunci: Map<((key1, key2,..,keyN), Value)dengan cara yang saya dapat mengakses elemen dengan menggunakan get (key1, key2, ... keyN).

EDIT: 3 tahun setelah memposting pertanyaan, saya ingin menambahkan sedikit lebih banyak

Saya menemukan cara lain untuk NxN matrix.

Indeks array, idan jdapat direpresentasikan sebagai satu keycara berikut:

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

Dan indeks dapat ditarik kembali dengan keycara ini:

int i = key / N;
int j = key % N;
Crocode
sumber
Solusi sederhana adalah memetakan satu kunci di hashmap lainnya.
Mihai8
1
Mohon jangan menjawab pertanyaan di pertanyaan. Hasil edit Anda menarik, jadi silakan posting sebagai jawaban.
Ole VV
@Cowok! matematika di balik jawaban di Edit sangat gemilang. Hanya ingin tahu apakah ini bekerja secara umum untuk dua bilangan bulat i dan j.
likejudo
@ Crocode akan saya dan j siklus jika mereka adalah kelipatan N?
likejudo

Jawaban:

190

Ada beberapa pilihan:

2 dimensi

Peta peta

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

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

Objek kunci pembungkus

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

}

Menerapkan equals()dan hashCode()sangat penting di sini. Kemudian Anda cukup menggunakan:

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

dan:

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

Table dari jambu biji

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

table.get(2, 5);

Tablemenggunakan peta peta di bawahnya.

Dimensi N

Perhatikan bahwa Keykelas khusus adalah satu-satunya pendekatan yang diskalakan ke dimensi-n. Anda juga dapat mempertimbangkan:

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

tapi itu mengerikan dari perspektif kinerja, serta keterbacaan dan ketepatan (bukan cara mudah untuk menerapkan ukuran daftar).

Mungkin lihat Scala di mana Anda memiliki tupel dan casekelas (mengganti seluruh Keykelas dengan satu baris).

Tomasz Nurkiewicz
sumber
3
Hai, yang lain memiliki x'atau dua nilai saat melakukan kode hash. Mengapa Anda menggunakan 31? Saya pikir itu ada hubungannya dengan integer 32-bit, tetapi ketika saya memikirkannya, itu tidak masuk akal, karena x = 1 dan y = 0 masih memetakan ke kode hash yang sama seperti x = 0 dan y = 31
pete
1
@pete Joshua Bloch, dalam Effective Java bab 3. s 9., merekomendasikan, "1. Simpan beberapa nilai konstan bukan nol, katakanlah, 17, dalam variabel int yang disebut result ...", yang bekerja lebih baik dari segi tabrakan jika itu sebuah bilangan prima. Lihat juga: stackoverflow.com/questions/3613102/…
fncomp
2
Alih-alih objek kunci Wrapper mengapa tidak digunakan Map.Entry<K, V>sebagai kunci?
Roland
1
Tentang apa Map<Pair<Key1, Key2>, Value>?
Joaquin Iurchuk
1
catatan yang hashCode()juga dapat diimplementasikan dengan satu baris sebagaiObjects.hash(x,y)
xdavidliu
23

Saat Anda membuat objek pasangan kunci Anda sendiri, Anda harus menghadapi beberapa hal.

Pertama, Anda harus menyadari penerapan hashCode()dan equals(). Anda harus melakukan ini.

Kedua, saat menerapkan hashCode(), pastikan Anda memahami cara kerjanya. Contoh pengguna yang diberikan

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

sebenarnya adalah salah satu penerapan terburuk yang dapat Anda lakukan. Alasannya sederhana: Anda memiliki banyak hash yang sama! Dan hashCode()harus mengembalikan nilai int yang cenderung langka, unik yang terbaik. Gunakan sesuatu seperti ini:

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

Ini cepat dan mengembalikan hash unik untuk kunci antara -2 ^ 16 dan 2 ^ 16-1 (-65536 hingga 65535). Ini cocok di hampir semua kasus. Sangat jarang Anda berada di luar batasan ini.

Ketiga, ketika mengimplementasikan equals()juga tahu untuk apa itu digunakan dan menyadari bagaimana Anda membuat kunci Anda, karena mereka adalah objek. Seringkali Anda melakukan yang tidak perlu jika pernyataan menyebabkan Anda akan selalu mendapatkan hasil yang sama.

Jika Anda membuat kunci seperti ini: map.put(new Key(x,y),V);Anda tidak akan pernah membandingkan referensi kunci Anda. Sebab setiap kali Anda ingin mengakses peta, Anda akan melakukan sesuatu seperti map.get(new Key(x,y));. Oleh karena itu Anda equals()tidak membutuhkan pernyataan seperti if (this == obj). Itu tidak akan pernah terjadi.

Daripada if (getClass() != obj.getClass())Anda equals()gunakan dengan lebih baik if (!(obj instanceof this)). Ini akan berlaku bahkan untuk subclass.

Jadi satu-satunya hal yang perlu Anda bandingkan sebenarnya adalah X dan Y. Jadi equals()penerapan terbaik dalam hal ini adalah:

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

Jadi pada akhirnya kelas kunci Anda adalah seperti ini:

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

}

Anda dapat memberikan indeks dimensi Xdan Ytingkat akses publik, karena faktanya indeks tersebut final dan tidak berisi informasi sensitif. Saya tidak 100% yakin apakah privatetingkat akses berfungsi dengan benar dalam hal apa pun saat mentransmisikan Objectke Key.

Jika Anda bertanya-tanya tentang final, saya menyatakan apa pun sebagai final yang nilainya ditetapkan pada instancing dan tidak pernah berubah - dan oleh karena itu merupakan objek konstan.

chrixle
sumber
7

Anda tidak dapat memiliki peta hash dengan banyak kunci, tetapi Anda dapat memiliki objek yang menggunakan banyak parameter sebagai kuncinya.

Buat sebuah objek bernama Indeks yang memiliki nilai x dan 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;
    }
}

Kemudian mintalah Anda HashMap<Index, Value>untuk mendapatkan hasil Anda. :)

Brad
sumber
4
Anda perlu mengganti hashCodedan equals.
Tom Hawtin - tackline
4
implementasi hashCode tidak membedakan antara (2,1) dan (1,2)
pengguna1947415
1
Itu adalah tabrakan. Hashcode tidak perlu menjamin nilai yang berbeda untuk setiap objek yang berbeda. @ user1947415
Ajak6
6

Diimplementasikan dalam koleksi umum MultiKeyMap

Mike
sumber
@Wilson Saya memperbaiki tautannya sekarang, menunggu tinjauan sejawat
computingfreak
@computingfreak sepertinya ada pandangan yang menguntungkan. Hore! NB ini adalah jawaban terbaik IMHO. Kecuali jika Anda suka menghabiskan waktu berjam-jam untuk berkompetisi dengan insinyur ahli Apache untuk beberapa fungsi yang sangat berguna (seperti biasa) tetapi pada akhirnya biasa saja.
mike rodent
4

Dua kemungkinan. Gunakan salah satu kunci gabungan:

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

Atau Peta Peta:

Map<Integer, Map<Integer, Integer>> myMap;
Cyrille Ka
sumber
2
Hanya gunakan peta peta jika Anda tidak peduli dengan kinerja atau penggunaan memori di sini (mis., Petanya kecil), atau ada banyak kunci dengan indeks pertama yang sama - karena solusi ini berarti membayar biaya overhead objek HashMap untuk setiap indeks pertama yang unik.
BeeOnRope
1
Untuk meningkatkan jawaban ini, berikut adalah info tentang penggantian hashCodedan equalsmetode.
Pshemo
2

Gunakan a Pairsebagai kunci untuk HashMap. JDK tidak memiliki Pair, tetapi Anda dapat menggunakan libraray pihak ketiga seperti http://commons.apache.org/lang atau menulis Pair taype Anda sendiri.

MrSmith42
sumber
1

Buat kelas nilai yang akan mewakili kunci majemuk Anda, seperti:

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

berhati-hati untuk mengganti equals()dan hashCode()dengan benar. Jika itu tampak seperti banyak pekerjaan, Anda dapat mempertimbangkan beberapa wadah generik siap pakai, seperti yang Pairdisediakan oleh apache commons antara lain.

Ada juga banyak pertanyaan serupa di sini, dengan ide lain, seperti menggunakan Tabel Jambu , meskipun memungkinkan kunci memiliki jenis yang berbeda, yang mungkin berlebihan (dalam penggunaan memori dan kompleksitas) dalam kasus Anda karena saya mengerti bahwa kunci Anda adalah bilangan bulat.

BeeOnRope
sumber
1

Jika keduanya adalah bilangan bulat, Anda dapat mencoba trik cepat dan kotor: Map<String, ?>menggunakan kunci sebagai i+"#"+j.

Jika kuncinya i+"#"+jsama dengan j+"#"+icoba min(i,j)+"#"+max(i,j).

arutaku
sumber
2
Ide yang sangat buruk. Pertama, itu buruk. Kedua, teknik ini akan disalin untuk jenis lain di mana kunci yang berbeda dapat dipetakan ke yang sama Stringdengan konsekuensi yang lucu.
Tom Hawtin - tackline
Menurut saya, i#j = j#ijika i == jmenggunakan min/maxtrik tidak akan berhasil.
Matthieu
1
@ Matthieu apa perbedaan antara 5#5dan 5#5bertukar sekitar?
enrey
@enrey Tidak ada. Itulah yang saya tunjukkan. Itu sangat tergantung pada pengetahuan yang Anda miliki tentang kunci Anda.
Matthieu
@ Matthieu aha Saya mengerti apa yang Anda maksud. Menurut saya yang dimaksud @arutaku adalah ketika Anda ingin 5#3memiliki hash yang sama 3#5, maka Anda menggunakan min / max untuk menegakkan 3#5urutan ini.
enrey
1

Anda juga dapat menggunakan implementasi Tabel jambu biji untuk ini.

Tabel mewakili peta khusus di mana dua kunci dapat ditentukan secara gabungan untuk merujuk ke satu nilai. Ini mirip dengan membuat peta peta.

//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");
slisnychyi
sumber
1

Anda dapat membuat objek utama Anda seperti ini:

MapKey kelas publik {

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

}

Keuntungan dari ini adalah: Ini akan selalu memastikan Anda mencakup semua skenario Equals juga.

CATATAN : Key1 dan key2 Anda harus tetap. Hanya dengan demikian Anda dapat membangun Objek kunci stabil.

Andy
sumber
1

kita dapat membuat kelas untuk melewatkan lebih dari satu kunci atau nilai dan objek kelas ini dapat digunakan sebagai parameter di peta.

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

        }

    }

    }

 }
Vikas Pal
sumber
0

Anda dapat mengunduhnya dari tautan di bawah ini: https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java

https://github.com/VVS279/DoubleKeyHashMap

Anda dapat menggunakan kunci ganda: nilai hashmap,

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

   DoubleKeyHashMap<String, String, String> doubleKeyHashMap2 = new 
   DoubleKeyHashMap<String, String, String>();
VISHAL SINGH
sumber