Come creare una HashMap con due chiavi (Key-Pair, Value)?


118

Ho una matrice 2D di numeri interi. Voglio che vengano inseriti in una HashMap. Ma voglio accedere agli elementi da HashMap in base all'indice di array. Qualcosa di simile a:

Per A [2] [5], map.get(2,5)che restituisce un valore associato a quella chiave. Ma come creo una hashMap con una coppia di chiavi? O in generale, più chiavi: Map<((key1, key2,..,keyN), Value)in modo da poter accedere all'elemento utilizzando get (key1, key2, ... keyN).

EDIT: 3 anni dopo aver pubblicato la domanda, voglio aggiungerne un po 'di più

Mi sono imbattuto in un altro modo per NxN matrix.

Indici di matrice ie jpossono essere rappresentati come un singolo keynel modo seguente:

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

E gli indici possono essere ritrovati keyin questo modo:

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

Una soluzione semplice è mappare una chiave in un'altra hashmap.
Mihai8

1
Per favore, non rispondere alla domanda nella domanda. La tua modifica è interessante, quindi sentiti libero di pubblicarla come risposta.
Ole VV

@Crocode wow! la matematica alla base della risposta nell'Edit è scintillante. Mi chiedo solo se funziona in generale per due interi i e j.
likejudo

@Crocode i e j verranno ripetuti se sono multipli di N?
likejudo

Risposte:


190

Ci sono diverse opzioni:

2 dimensioni

Mappa delle mappe

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

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

Oggetto chiave wrapper

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

}

L'attuazione equals()ed hashCode()è fondamentale qui. Quindi usi semplicemente:

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

e:

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

Table di Guava

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

table.get(2, 5);

Tableusa la mappa delle mappe sottostanti.

Dimensioni N.

Si noti che la Keyclasse speciale è l'unico approccio che scala alle n dimensioni. Potresti anche considerare:

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

ma è terribile dal punto di vista delle prestazioni, così come la leggibilità e la correttezza (non è un modo semplice per applicare la dimensione dell'elenco).

Magari dai un'occhiata a Scala dove hai tuple e caseclassi (sostituendo l'intera Keyclasse con una riga).


3
Ciao, altri hanno x'o i due valori quando si fa hashCode. Perché usi 31? Ho pensato che avesse qualcosa a che fare con il numero intero a 32 bit, ma quando ci penso non ha senso, perché x = 1 ey = 0 è ancora associato allo stesso codice hash di x = 0 e y = 31
pete

1
@pete Joshua Bloch, in Effective Java ch 3. s 9., consiglia: "1. Memorizza un valore costante diverso da zero, diciamo 17, in una variabile int chiamata risultato ...", che funziona meglio in termini di collisione se è un primo. Vedi anche: stackoverflow.com/questions/3613102/...
fncomp

2
Invece dell'oggetto chiave wrapper, perché non usarlo Map.Entry<K, V>come chiave?
Roland

1
Di cosa Map<Pair<Key1, Key2>, Value>?
Joaquin Iurchuk

1
nota che hashCode()può anche essere implementato con una singola riga comeObjects.hash(x,y)
xdavidliu

23

Quando crei il tuo oggetto coppia di chiavi, dovresti affrontare alcune cose.

Innanzitutto, dovresti essere consapevole dell'implementazione di hashCode()eequals() . Avrai bisogno di farlo.

Secondo, durante l'implementazione hashCode(), assicurati di capire come funziona. L'esempio utente fornito

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

è in realtà una delle peggiori implementazioni che puoi fare. Il motivo è semplice: hai molti hash uguali! E hashCode()dovrebbero restituire valori int che tendono ad essere rari, unici al meglio. Usa qualcosa del genere:

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

È veloce e restituisce hash univoci per chiavi comprese tra -2 ^ 16 e 2 ^ 16-1 (da -65536 a 65535). Questo si adatta a quasi tutti i casi. Molto raramente sei fuori da questo limite.

Terzo, durante l'implementazione equals() sappi anche a cosa serve e sii consapevole di come crei le tue chiavi, dato che sono oggetti. Spesso non è necessario fare dichiarazioni if ​​perché avrai sempre lo stesso risultato.

Se crei chiavi in ​​questo modo: map.put(new Key(x,y),V);non confronterai mai i riferimenti delle tue chiavi. Perché ogni volta che vuoi accedere alla mappa, farai qualcosa di simile map.get(new Key(x,y));. Quindi il tuo equals()non ha bisogno di una dichiarazione come if (this == obj). Non accadrà mai .

Invece che if (getClass() != obj.getClass())in un tuo equals()uso migliore if (!(obj instanceof this)). Sarà valido anche per le sottoclassi.

Quindi l'unica cosa che devi confrontare è in realtà X e Y. Quindi la migliore equals()implementazione in questo caso sarebbe:

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

Quindi alla fine la tua classe chiave è così:

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

}

Puoi fornire i tuoi indici di dimensione Xe Yun livello di accesso pubblico, poiché sono definitivi e non contengono informazioni sensibili. Non sono sicuro al 100% se il privatelivello di accesso funzioni correttamente in ogni caso quando si trasmette il file Objecta un fileKey .

Se ti chiedi quali sono le finali, dichiaro qualsiasi cosa come finale il cui valore è impostato sull'istanza e non cambia mai - e quindi è una costante dell'oggetto.


7

Non puoi avere una mappa hash con più chiavi, ma puoi avere un oggetto che accetta più parametri come chiave.

Crea un oggetto chiamato Index che accetta un valore xey.

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

Quindi HashMap<Index, Value>chiedi il tuo risultato. :)


4
È necessario sostituire hashCodee equals.
Tom Hawtin - tackline

4
l'implementazione hashCode non ha differenziato tra (2,1) e (1,2)
user1947415

1
Questa è una collisione. Hashcode non ha bisogno di garantire un valore diverso per ogni oggetto diverso. @ user1947415
Ajak6

6

Implementato nelle raccolte comuni MultiKeyMap


@Wilson Ho corretto il collegamento ora, in attesa di revisione tra pari
Computingfreak

@computingfreak sembra che sia arrivata una visione favorevole. Evviva! NB questa è la migliore risposta IMHO. A meno che tu non voglia passare ore a competere con gli esperti ingegneri di Apache su alcune funzionalità molto utili (come sempre) ma in definitiva banali.
mike rodent

4

Due possibilità. Utilizzare una chiave combinata:

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

O una mappa della mappa:

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

2
Usa una mappa di mappe solo se non ti interessano le prestazioni o l'uso della memoria qui (cioè, la mappa è piccola), o ci sono molte chiavi con lo stesso primo indice - poiché questa soluzione significa pagare l'overhead di un oggetto HashMap per ogni singolo primo indice univoco.
BeeOnRope

1
Per migliorare questa risposta, ecco le informazioni sull'override hashCodee sui equalsmetodi.
Pshemo


1

Crea una classe di valori che rappresenterà la tua chiave composta, ad esempio:

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

avendo cura di sovrascrivere equals()e hashCode()correttamente. Se ti sembra un sacco di lavoro, potresti prendere in considerazione alcuni contenitori generici già pronti, come quelli Pairforniti da apache commons tra gli altri.

Ci sono anche molte domande simili qui, con altre idee, come l'uso della tabella di Guava , sebbene consenta alle chiavi di avere tipi diversi, il che potrebbe essere eccessivo (nell'uso della memoria e nella complessità) nel tuo caso poiché capisco che le tue chiavi sono entrambi numeri interi.


1

Se sono due numeri interi puoi provare un trucco veloce e sporco: Map<String, ?>usando la chiave as i+"#"+j.

Se la chiave i+"#"+jè la stessa di j+"#"+itry min(i,j)+"#"+max(i,j).


2
Idea davvero pessima. In primo luogo, è solo brutto. In secondo luogo, questa tecnica verrà copiata per altri tipi in cui chiavi diverse potrebbero essere mappate sulla stessa Stringcon conseguenze esilaranti.
Tom Hawtin - tackline

Mi sembra che i#j = j#iquando è i == jcosì, usare il min/maxtrucco non va bene.
Matthieu

1
@ Matthieu qual è la differenza tra 5#5e 5#5scambiato?
enrey

@enrey Nessuno. Questo è quello che stavo sottolineando. Dipende davvero dalla conoscenza che hai sulle tue chiavi.
Matthieu

@ Matthieu aha capisco cosa intendi. Penso che ciò che significava @arutaku fosse quando vuoi 5#3avere lo stesso hash come 3#5, quindi usi min / max per applicare3#5 in questo ordine.
enrey

1

Puoi anche usare guava Table implementazione della per questo.

La tabella rappresenta una mappa speciale in cui è possibile specificare due chiavi in ​​modo combinato per fare riferimento a un singolo valore. È simile alla creazione di una mappa di mappe.

//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

Potresti creare il tuo oggetto chiave in questo modo:

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

}

Il vantaggio di questo è: assicurerà sempre di coprire anche tutti gli scenari di Equals.

NOTA : la chiave1 e la chiave2 dovrebbero essere immutabili. Solo allora sarai in grado di costruire un oggetto chiave stabile.


1

possiamo creare una classe per passare più di una chiave o valore e l'oggetto di questa classe può essere utilizzato come parametro in map.

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

        }

    }

    }

 }

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.