Comment créer un HashMap avec deux clés (Key-Pair, Value)?

118

J'ai un tableau 2D d'entiers. Je veux qu'ils soient mis dans un HashMap. Mais je veux accéder aux éléments du HashMap basé sur Array Index. Quelque chose comme:

Pour A [2] [5], map.get(2,5)qui renvoie une valeur associée à cette clé. Mais comment créer un hashMap avec une paire de clés? Ou en général, plusieurs clés: Map<((key1, key2,..,keyN), Value)de manière à pouvoir accéder à l'élément avec get (key1, key2, ... keyN).

EDIT: 3 ans après avoir posté la question, je veux y ajouter un peu plus

Je suis tombé sur un autre moyen pour NxN matrix.

Les indices de tableau iet jpeuvent être représentés comme un seul de keyla manière suivante:

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

Et les indices peuvent être récupérés à partir du keyde cette manière:

int i = key / N;
int j = key % N;
Crocode
la source
Une solution simple consiste à mapper une clé dans une autre hashmap.
Mihai8
1
Veuillez ne pas répondre à la question de la question. Votre modification est intéressante, alors n'hésitez pas à la publier comme réponse.
Ole VV
@Crocode wow! les mathématiques derrière la réponse dans la modification sont scintillantes. Je me demande simplement si cela fonctionne en général pour deux entiers i et j.
likejudo
@Crocode est-ce que i et j reviendront s'ils sont des multiples de N?
likejudo

Réponses:

190

Il existe plusieurs options:

2 dimensions

Carte des cartes

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

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

Objet clé 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;
    }

}

La mise en œuvre equals()et hashCode()est cruciale ici. Ensuite, vous utilisez simplement:

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

et:

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

Table depuis Guava

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

table.get(2, 5);

Tableutilise la carte des cartes ci- dessous.

N dimensions

Notez que la Keyclasse spéciale est la seule approche qui s'adapte à n dimensions. Vous pourriez également envisager:

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

mais c'est terrible du point de vue des performances, ainsi que de la lisibilité et de l'exactitude (pas de moyen facile d'appliquer la taille de la liste).

Jetez peut-être un œil à Scala où vous avez des tuples et des caseclasses (en remplaçant toute la Keyclasse par une seule ligne).

Tomasz Nurkiewicz
la source
3
Salut, d'autres ont x'ou les deux valeurs lors de l'utilisation de hashCode. Pourquoi utilisez-vous 31? Je pensais que cela avait quelque chose à voir avec un entier 32 bits, mais quand j'y pense, cela n'a pas de sens, car x = 1 et y = 0 correspondent toujours au même hashcode que x = 0 et y = 31
pete
1
@pete Joshua Bloch, dans Effective Java ch 3. s 9., recommande: "1. Stockez une valeur constante différente de zéro, disons 17, dans une variable int appelée result ...", qui fonctionne mieux en cas de collision si c'est un prime. Voir aussi: stackoverflow.com/questions/3613102/…
fncomp
2
Au lieu de l'objet clé Wrapper, pourquoi ne pas l'utiliser Map.Entry<K, V>comme clé?
Roland
1
Et quoi Map<Pair<Key1, Key2>, Value>?
Joaquin Iurchuk
1
notez que hashCode()peut également être implémenté avec une seule ligne commeObjects.hash(x,y)
xdavidliu
23

Lorsque vous créez votre propre objet de paire de clés, vous devez faire face à certaines choses.

Tout d'abord, vous devez être conscient de la mise en œuvre hashCode()et equals(). Vous devrez le faire.

Deuxièmement, lors de la mise en œuvre hashCode(), assurez-vous de comprendre comment cela fonctionne. L'exemple d'utilisateur donné

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

est en fait l'une des pires implémentations que vous puissiez faire. La raison est simple: vous avez beaucoup de hachages égaux! Et le hashCode()devrait renvoyer des valeurs int qui ont tendance à être rares, uniques au mieux. Utilisez quelque chose comme ceci:

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

Ceci est rapide et renvoie des hachages uniques pour les clés entre -2 ^ 16 et 2 ^ 16-1 (-65536 à 65535). Cela convient à presque tous les cas. Très rarement, vous êtes hors de ces limites.

Troisièmement, lors de la mise en œuvre, equals()sachez également à quoi il sert et soyez conscient de la façon dont vous créez vos clés, car ce sont des objets. Souvent, vous ne faites pas inutile si les déclarations provoquent vous aurez toujours le même résultat.

Si vous créez des clés comme ceci: map.put(new Key(x,y),V);vous ne comparerez jamais les références de vos clés. Parce qu'à chaque fois que vous voudrez accéder à la carte, vous ferez quelque chose comme map.get(new Key(x,y));. Par conséquent, vous equals()n'avez pas besoin d'une déclaration comme if (this == obj). Cela n'arrivera jamais .

Au lieu de if (getClass() != obj.getClass())votre equals()meilleure utilisation if (!(obj instanceof this)). Il sera valable même pour les sous-classes.

Donc, la seule chose que vous devez comparer est en fait X et Y. La meilleure equals()implémentation dans ce cas serait donc:

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

Donc, à la fin, votre classe de clé est comme ceci:

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

}

Vous pouvez donner vos indices de dimension Xet Yun niveau d'accès public, car ils sont définitifs et ne contiennent pas d'informations sensibles. Je ne suis pas sûr à 100% si le privateniveau d'accès fonctionne correctement dans tous les cas lors de la conversion du fichier Objecten a Key.

Si vous vous interrogez sur la finale, je déclare n'importe quoi comme final dont la valeur est fixée à l'instanciation et ne change jamais - et est donc une constante d'objet.

chrixle
la source
7

Vous ne pouvez pas avoir une carte de hachage avec plusieurs clés, mais vous pouvez avoir un objet qui prend plusieurs paramètres comme clé.

Créez un objet appelé Index qui prend une valeur x et 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;
    }
}

Alors ayez votre HashMap<Index, Value>pour obtenir votre résultat. :)

Brad
la source
4
Vous devez remplacer hashCodeet equals.
Tom Hawtin - tackline
4
l'implémentation de hashCode ne faisait pas la différence entre (2,1) et (1,2)
user1947415
1
C'est une collision. Le Hashcode n'a pas besoin de garantir une valeur différente pour chaque objet différent. @ user1947415
Ajak6
6

Implémenté dans les collections communes MultiKeyMap

Mike
la source
@Wilson J'ai corrigé le lien maintenant, en attendant l'examen par les pairs
computingfreak
@computingfreak semble être une opinion favorable. Hourra! NB c'est la meilleure réponse à mon humble avis. À moins que vous n'ayez envie de passer des heures à rivaliser avec les ingénieurs Apache experts sur une fonctionnalité très utile (comme toujours) mais finalement banale.
mike rodent le
4

Deux possibilités. Soit utiliser une clé combinée:

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

Ou une carte de la carte:

Map<Integer, Map<Integer, Integer>> myMap;
Cyrille Ka
la source
2
N'utilisez une carte de cartes que si vous ne vous souciez pas des performances ou de l'utilisation de la mémoire ici (c'est-à-dire que la carte est petite), ou s'il existe de nombreuses clés avec le même premier index - puisque cette solution signifie payer la surcharge d'un objet HashMap pour chaque premier index unique.
BeeOnRope
1
Pour améliorer cette réponse, voici des informations sur le remplacement hashCodeet les equalsméthodes.
Pshemo
2

Utilisez a Paircomme touches pour le HashMap. JDK n'a pas de paire, mais vous pouvez soit utiliser une bibliothèque tierce telle que http://commons.apache.org/lang, soit écrire votre propre modèle de paire.

MrSmith42
la source
1

Créez une classe de valeur qui représentera votre clé composée, telle que:

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

en prenant soin de passer outre equals()et hashCode()correctement. Si cela semble demander beaucoup de travail, vous pouvez envisager des conteneurs génériques prêts à l'emploi, tels que ceux Pairfournis par apache commons entre autres.

Il y a aussi beaucoup de questions similaires ici, avec d'autres idées, telles que l'utilisation de la table de Guava , bien que cela permette aux clés d'avoir différents types, ce qui pourrait être excessif (en termes d'utilisation de la mémoire et de complexité) dans votre cas, car je comprends que vos clés sont toutes les deux des entiers.

BeeOnRope
la source
1

S'il s'agit de deux entiers, vous pouvez essayer une astuce rapide et sale: en Map<String, ?>utilisant la touche comme i+"#"+j.

Si la clé i+"#"+jest la même que j+"#"+iessayez min(i,j)+"#"+max(i,j).

arutaku
la source
2
Vraiment mauvaise idée. Premièrement, c'est tout simplement mauvais. Deuxièmement, cette technique sera copiée pour d'autres types où différentes clés peuvent être mappées sur la même Stringavec des conséquences hilarantes.
Tom Hawtin - tackline
Il me semble que i#j = j#iquand i == jdonc en utilisant l' min/maxastuce ne le fera pas.
Matthieu
1
@Matthieu quelle est la différence entre 5#5et 5#5échangés?
enrey
@enrey Aucun. C'est ce que je signalais. Cela dépend vraiment de la connaissance que vous avez de vos clés.
Matthieu
@Matthieu aha je vois ce que tu veux dire. Je pense que ce que @arutaku voulait dire, c'était quand vous voulez 5#3avoir le même hachage 3#5, alors vous utilisez min / max pour appliquer 3#5dans cet ordre.
enrey
1

Vous pouvez également utiliser l' implémentation de table goyave pour cela.

Table représente une carte spéciale dans laquelle deux clés peuvent être spécifiées de manière combinée pour faire référence à une seule valeur. Cela revient à créer une carte de cartes.

//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
la source
1

Vous pouvez créer votre objet clé comme ceci:

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

}

L'avantage de ceci est que cela garantira toujours que vous couvriez également tous les scénarios d'égal.

REMARQUE : vos key1 et key2 doivent être immuables. Ce n'est qu'alors que vous pourrez construire un objet clé stable.

Andy
la source
1

nous pouvons créer une classe pour passer plus d'une clé ou valeur et l'objet de cette classe peut être utilisé comme paramètre dans la carte.

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
la source
0

Vous pouvez le télécharger à partir du lien ci-dessous: https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java

https://github.com/VVS279/DoubleKeyHashMap

Vous pouvez utiliser une clé double: valeur hashmap,

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

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