Làm cách nào để tạo HashMap có hai khóa (Key-Pair, Value)?


118

Tôi có một mảng số nguyên 2D. Tôi muốn chúng được đưa vào HashMap. Nhưng tôi muốn truy cập các phần tử từ HashMap dựa trên Chỉ mục mảng. Cái gì đó như:

Đối với A [2] [5], map.get(2,5)trả về một giá trị được liên kết với khóa đó. Nhưng làm cách nào để tạo một hashMap với một cặp khóa? Hoặc nói chung, nhiều khóa: Map<((key1, key2,..,keyN), Value)theo cách mà tôi có thể truy cập phần tử bằng cách sử dụng get (key1, key2, ... keyN).

CHỈNH SỬA: 3 năm sau khi đăng câu hỏi, tôi muốn thêm một chút vào nó

Tôi đã đi qua một cách khác cho NxN matrix.

Chỉ số mảng ijcó thể được biểu diễn thành một chỉ keysố theo cách sau:

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

Và các chỉ số có thể được thu keyhồi theo cách này:

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

Một giải pháp đơn giản là ánh xạ một khóa trong bản đồ băm khác.
Mihai8

1
Vui lòng không trả lời câu hỏi trong câu hỏi. Chỉnh sửa của bạn rất thú vị, vì vậy hãy đăng nó như một câu trả lời.
Ole VV

@Crocode wow! toán học đằng sau câu trả lời trong Chỉnh sửa đang lướt qua. Chỉ cần tự hỏi liệu nó có hoạt động chung cho bất kỳ hai số nguyên i và j hay không.
likejudo

@Crocode liệu tôi và j có thay đổi chu kỳ nếu chúng là bội số của N không?
likejudo

Câu trả lời:


190

Có một số tùy chọn:

2 chiều

Bản đồ của bản đồ

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

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

Đối tượng chính của gói

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

}

Thực hiện equals()hashCode()là rất quan trọng ở đây. Sau đó, bạn chỉ cần sử dụng:

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

và:

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

Table từ Guava

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

table.get(2, 5);

Tablesử dụng bản đồ của bản đồ bên dưới.

N thứ nguyên

Lưu ý rằng Keylớp đặc biệt là cách tiếp cận duy nhất mở rộng đến n-chiều. Bạn cũng có thể cân nhắc:

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

nhưng điều đó thật tồi tệ từ góc độ hiệu suất, cũng như khả năng đọc và tính đúng đắn (không có cách nào dễ dàng để thực thi kích thước danh sách).

Có thể hãy xem Scala nơi bạn có các bộ giá trị và casecác lớp (thay thế toàn bộ Keylớp bằng một lớp lót).


3
Xin chào, những người khác có x'hoặc hai giá trị khi thực hiện mã băm. Tại sao bạn sử dụng 31? Tôi nghĩ nó có liên quan gì đó với số nguyên 32 bit, nhưng khi tôi nghĩ lại thì nó không có ý nghĩa gì, bởi vì x = 1 và y = 0 vẫn ánh xạ đến cùng một mã băm như x = 0 và y = 31
pete

1
@pete Joshua Bloch, trong Java hiệu quả ch 3. s 9., khuyến nghị, "1. Lưu trữ một số giá trị khác không hằng số, chẳng hạn, 17, trong một biến int có tên là result ...", điều này sẽ hoạt động tốt hơn nếu có một số nguyên tố. Xem thêm: stackoverflow.com/questions/3613102/…
fncomp

2
Thay vì đối tượng khóa Wrapper tại sao không sử dụng Map.Entry<K, V>làm khóa?
Roland

1
Về Map<Pair<Key1, Key2>, Value>thì sao?
Joaquin Iurchuk

1
lưu ý rằng hashCode()cũng có thể được thực hiện với một dòng duy nhất làObjects.hash(x,y)
xdavidliu

23

Khi bạn tạo đối tượng cặp khóa của riêng mình, bạn phải đối mặt với một số điều.

Đầu tiên, bạn nên biết về việc thực hiện hashCode()equals(). Bạn sẽ cần phải làm điều này.

Thứ hai, khi thực hiện hashCode(), hãy chắc chắn rằng bạn hiểu nó hoạt động như thế nào. Ví dụ người dùng nhất định

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

thực sự là một trong những cách triển khai tồi tệ nhất mà bạn có thể làm. Lý do rất đơn giản: bạn có rất nhiều băm bằng nhau! Và hashCode()nên trả về các giá trị int có xu hướng hiếm, tốt nhất là duy nhất. Sử dụng một cái gì đó như thế này:

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

Điều này nhanh chóng và trả về số băm duy nhất cho các khóa từ -2 ^ 16 đến 2 ^ 16-1 (-65536 đến 65535). Điều này phù hợp với hầu hết mọi trường hợp. Rất hiếm khi bạn nằm ngoài giới hạn này.

Thứ ba, khi triển khai equals()cũng phải biết nó được sử dụng để làm gì và nhận thức được cách bạn tạo khóa của mình, vì chúng là các đối tượng. Thường thì bạn làm không cần thiết nếu các câu lệnh gây ra bạn sẽ luôn có cùng một kết quả.

Nếu bạn tạo các khóa như thế này: map.put(new Key(x,y),V);bạn sẽ không bao giờ so sánh các tham chiếu của các khóa của mình. Bởi vì mỗi khi bạn muốn gia nhập bản đồ, bạn sẽ làm điều gì đó như thế nào map.get(new Key(x,y));. Do đó của bạn equals()không cần một tuyên bố như thế nào if (this == obj). Nó sẽ không bao giờ xảy ra .

Thay vì sử dụng tốt hơn if (getClass() != obj.getClass())của bạn . Nó sẽ có giá trị ngay cả đối với các lớp con.equals()if (!(obj instanceof this))

Vì vậy, điều duy nhất bạn cần so sánh thực sự là X và Y. Vì vậy, cách equals()triển khai tốt nhất trong trường hợp này sẽ là:

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

Vì vậy, cuối cùng lớp khóa của bạn là như thế này:

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

}

Bạn có thể cung cấp các chỉ số thứ nguyên XYcấp độ truy cập công khai, do chúng là chỉ số cuối cùng và không chứa thông tin nhạy cảm. Tôi không chắc 100% liệu privatecấp truy cập có hoạt động chính xác trong mọi trường hợp khi truyền Objecttới a hay không Key.

Nếu bạn thắc mắc về kết quả cuối cùng, tôi tuyên bố bất kỳ điều gì là giá trị cuối cùng được đặt trên cá nhân và không bao giờ thay đổi - và do đó là một hằng số đối tượng.


7

Bạn không thể có một bản đồ băm với nhiều khóa, nhưng bạn có thể có một đối tượng lấy nhiều tham số làm khóa.

Tạo một đối tượng có tên là Index nhận giá trị x và 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;
    }
}

Sau đó, có của bạn HashMap<Index, Value>để có được kết quả của bạn. :)


4
Bạn cần ghi đè hashCodeequals.
Tom Hawtin - tackline

4
việc thực hiện hashCode không phân biệt giữa (2,1) và (1,2)
user1947415

1
Đó là sự va chạm. Hashcode không cần đảm bảo giá trị khác nhau cho mọi đối tượng khác nhau. @ user1947415
Ajak6

6

Được triển khai trong bộ sưu tập chung MultiKeyMap


@Wilson Tôi cố định liên kết với doanh nghiệp, chờ đợi đánh giá ngang hàng
computingfreak

@computingfreak có vẻ như đã có một cái nhìn thuận lợi. Tiếng hoan hô! NB đây là câu trả lời tốt nhất IMHO. Trừ khi bạn thích dành hàng giờ đồng hồ để cạnh tranh với các kỹ sư Apache chuyên gia về một số chức năng rất hữu ích (hơn bao giờ hết) nhưng cuối cùng lại rất nhàm chán.
loài gặm nhấm mike

4

Hai khả năng. Sử dụng một khóa kết hợp:

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

Hoặc Bản đồ Bản đồ:

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

2
Chỉ sử dụng bản đồ các bản đồ nếu bạn không quan tâm đến hiệu suất hoặc sử dụng bộ nhớ ở đây (tức là bản đồ nhỏ) hoặc có nhiều khóa có cùng chỉ mục đầu tiên - vì giải pháp này có nghĩa là trả chi phí của một đối tượng HashMap cho mọi chỉ mục đầu tiên duy nhất.
BeeOnRope

1
Để cải thiện câu trả lời này, đây là thông tin về ghi đè hashCodeequalsphương pháp.
Pshemo

2

Sử dụng một Pairlàm phím cho HashMap. JDK không có Pair, nhưng bạn có thể sử dụng một băng thư viện của bên thứ 3 như http://commons.apache.org/lang hoặc viết một taype Pair của riêng bạn.


1

Tạo một lớp giá trị sẽ đại diện cho khóa ghép của bạn, chẳng hạn như:

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

cẩn thận để ghi đè equals()hashCode()chính xác. Nếu điều đó có vẻ như nhiều công việc, bạn có thể cân nhắc một số vùng chứa chung được tạo sẵn, chẳng hạn như Pairđược cung cấp bởi apache commons trong số những người khác.

Cũng có nhiều câu hỏi tương tự ở đây, với các ý tưởng khác, chẳng hạn như sử dụng Guava's Table , mặc dù cho phép các khóa có nhiều loại khác nhau, điều này có thể quá mức cần thiết (trong sử dụng bộ nhớ và độ phức tạp) trong trường hợp của bạn vì tôi hiểu các khóa của bạn đều là số nguyên.


1

Nếu chúng là hai số nguyên, bạn có thể thử một thủ thuật nhanh và dễ hiểu: Map<String, ?>sử dụng phím as i+"#"+j.

Nếu khóa i+"#"+jgiống như j+"#"+ithử min(i,j)+"#"+max(i,j).


2
Ý tưởng thực sự tồi tệ. Thứ nhất, nó chỉ là xấu. Thứ hai, kỹ thuật này sẽ được sao chép cho các kiểu khác, trong đó các khóa khác nhau có thể được ánh xạ vào cùng một Stringhệ quả vui nhộn.
Tom Hawtin - tackline

Dường như với tôi rằng i#j = j#ikhi i == jđể sử dụng các min/maxthủ thuật sẽ không làm.
Matthieu

1
@Matthieu sự khác biệt giữa 5#5và được 5#5hoán đổi là gì?
enrey

@enrey Không có. Đó là những gì tôi đã chỉ ra. Nó thực sự phụ thuộc vào kiến ​​thức bạn có về chìa khóa của mình.
Matthieu

@Matthieu aha Tôi hiểu ý bạn. Tôi nghĩ rằng ý nghĩa của @arutaku là khi bạn muốn 5#3có cùng một hàm băm 3#5, sau đó bạn sử dụng min / max để thực thi 3#5theo thứ tự này.
enrey

1

Bạn cũng có thể sử dụng cách thực hiện Bảng ổi cho việc này.

Bảng đại diện cho một bản đồ đặc biệt trong đó hai khóa có thể được chỉ định theo kiểu kết hợp để tham chiếu đến một giá trị duy nhất. Nó tương tự như việc tạo bản đồ bản đồ.

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

Bạn có thể tạo đối tượng chính của mình giống như sau:

lớp công khai 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;
}

}

Ưu điểm của điều này là: Nó sẽ luôn đảm bảo rằng bạn cũng đang bao gồm tất cả các kịch bản của Equals.

LƯU Ý : Key1 và key2 của bạn phải là bất biến. Chỉ khi đó, bạn mới có thể tạo một Đối tượng khóa ổn định.


1

chúng ta có thể tạo một lớp để truyền nhiều hơn một khóa hoặc giá trị và đối tượng của lớp này có thể được dùng làm tham số trong bản đồ.

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

Bạn có thể tải xuống từ liên kết dưới đây: https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java

https://github.com/VVS279/DoubleKeyHashMap

Bạn có thể sử dụng khóa kép: giá trị băm,

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

   DoubleKeyHashMap<String, String, String> doubleKeyHashMap2 = new 
   DoubleKeyHashMap<String, String, String>();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.