web-dev-qa-db-fra.com

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

J'ai un tableau 2D de nombres 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 touche. Mais comment créer une hashMap avec une paire de clés? Ou en général, plusieurs clés: Map<((key1, key2,..,keyN), Value) de manière à ce que je puisse accéder à l'élément en utilisant get (key1, key2, ... keyN).

EDIT: 3 ans après la publication de la question, je souhaite en ajouter un peu plus

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

Les indices de tableau, i et j peuvent être représentés sous la forme d'un seul key de la manière suivante:

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

Et les indices peuvent être extraits du key de cette manière:

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

Il y a plusieurs options:

2 dimensions

Carte des cartes

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

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

Wrapper key object

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

}

Implémenter equals() et hashCode() est crucial ici. Ensuite, vous utilisez simplement:

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

et:

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

Table de Guava

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

table.get(2, 5);

Table utilise la carte des cartes dessous.

N dimensions

Notez que la classe spéciale Key est la seule approche qui évolue vers n-dimensions. Vous pourriez aussi considérer:

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

mais c’est terrible du point de vue des performances, de la lisibilité et de la correction (pas de moyen facile d’appliquer la taille de la liste).

Jetez peut-être un coup d’œil à Scala où vous avez des tuples et des classes case (en remplaçant la classe entière Key par une ligne).

180
Tomasz Nurkiewicz

Lorsque vous créez votre propre objet de paire de clés, vous devez faire face à quelques problèmes.

Tout d'abord, vous devez être conscient de l'implémentation de hashCode() et equals(). Vous devrez faire ceci.

Deuxièmement, lorsque vous implémentez hashCode(), assurez-vous de comprendre son fonctionnement. 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 la hashCode() devrait renvoyer des valeurs int qui tendent à ê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 comprises entre -2 ^ 16 et 2 ^ 16-1 (-65536 à 65535). Cela convient dans presque tous les cas. Très rarement, vous êtes en dehors de ces limites.

Troisièmement, lorsque vous implémentez equals(), vous devez également savoir à quoi il sert et comment vous créez vos clés, car ce sont des objets. Souvent, vous faites des efforts inutiles si les déclarations vous donnent toujours le même résultat.

Si vous créez des clés comme celle-ci: map.put(new Key(x,y),V);, vous ne comparerez jamais les références de vos clés. Parce que chaque fois que vous voulez accéder à la carte, vous ferez quelque chose comme map.get(new Key(x,y));. Par conséquent, votre equals() n'a pas besoin d'instructions telles que if (this == obj). Cela se produira jamais.

Au lieu de if (getClass() != obj.getClass()) dans votre equals(), utilisez plutôt if (!(obj instanceof this)). Il sera valable même pour les sous-classes.

Donc, la seule chose que vous devez comparer est réellement X et Y. Ainsi, la meilleure implémentation de equals() dans ce cas serait:

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

En fin de compte, votre classe de clés ressemble à 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 attribuer à vos index de dimension X et Y un accès public, car ils sont définitifs et ne contiennent pas d'informations sensibles. Je ne suis pas sûr à 100% que _ le niveau d'accès private fonctionne correctement dans le cas aucun lors de la conversion de Object en Key.

Si vous vous interrogez sur les finales, je déclare comme finale la valeur qui est définie sur instancing et qui ne change jamais - et est donc une constante d'objet.

21
chrixle

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

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

6
Brad

Implémenté dans common-collections MultiKeyMap

5
Mykhaylo Adamovych

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

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

Ou une carte de carte:

Map<Integer, Map<Integer, Integer>> myMap;
3
Cyrille Ka

Si ce sont deux entiers, vous pouvez essayer une astuce rapide et compliquée: Map<String, ?> en utilisant la clé comme i+"#"+j.

Si la touche i+"#"+j est identique à j+"#"+i essayez min(i,j)+"#"+max(i,j).

2
arutaku

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 remplacer equals() et hashCode() correctement. Si cela semble vous demander beaucoup de travail, vous pouvez envisager certains conteneurs génériques prêts à l'emploi, tels que Pair fourni par Apache commons, entre autres.

Il y a aussi beaucoup de questions similaires ici, avec d’autres idées, comme celle de Guava Table , bien que les clés puissent avoir différents types, ce qui peut être excessif (en mémoire complexité) dans votre cas puisque je comprends que vos clés sont des entiers.

1
BeeOnRope

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 qu'il s'assurera toujours que vous couvrez également tous les scénarios d'égaux.

NOTE: Votre clé1 et votre clé2 doivent être immuables. Ce n’est qu’alors que vous pourrez construire un objet clé stable.

1
Andy

nous pouvons créer une classe pour transmettre plus d'une clé ou valeur et l'objet de cette classe peut être utilisé comme paramètre dans 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();

        }

    }

    }

 }
1
Vikas Pal

Utilisez un Pair comme clé pour le HashMap. JDK n'a pas de paire, mais vous pouvez utiliser une bibliothèque tierce telle que http://commons.Apache.org/lang ou écrire votre propre paire.

0
MrSmith42

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 double clé: valeur hashmap,

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

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

Vous pouvez également utiliser table goyave pour cette implémentation.

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 valeur unique. 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");
0
slisnychyi