จะสร้าง HashMap ด้วยปุ่มสองปุ่ม (Key-Pair, Value) ได้อย่างไร?


118

ฉันมีอาร์เรย์ 2 มิติของจำนวนเต็ม ฉันต้องการให้ใส่ลงใน HashMap แต่ฉันต้องการเข้าถึงองค์ประกอบจาก HashMap ตามดัชนีอาร์เรย์ สิ่งที่ต้องการ:

สำหรับ A [2] [5] map.get(2,5)ซึ่งส่งคืนค่าที่เกี่ยวข้องกับคีย์นั้น แต่ฉันจะสร้างแฮชแมปด้วยคีย์คู่ได้อย่างไร หรือโดยทั่วไปหลายคีย์: Map<((key1, key2,..,keyN), Value)ในวิธีที่ฉันสามารถเข้าถึงองค์ประกอบโดยใช้ get (key1, key2, ... keyN)

แก้ไข: 3 ปีหลังจากโพสต์คำถามฉันต้องการเพิ่มอีกเล็กน้อย

ฉันข้ามมาอีกทางสำหรับNxN matrix.

ดัชนีอาร์เรย์iและjสามารถแสดงเป็นวิธีเดียวkeyดังต่อไปนี้:

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

และสามารถเรียกดูดัชนีได้keyด้วยวิธีนี้:

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

วิธีง่ายๆคือการแมปคีย์หนึ่งในแฮชแมปอื่น
Mihai8

1
กรุณาอย่าตอบคำถามในคำถาม การแก้ไขของคุณน่าสนใจดังนั้นอย่าลังเลที่จะโพสต์เป็นคำตอบ
Ole VV

@ โครโคดว้าว! คณิตศาสตร์ที่อยู่เบื้องหลังคำตอบใน Edit นั้นเป็นประกาย แค่สงสัยว่ามันทำงานโดยทั่วไปสำหรับสองจำนวนเต็ม i และ j หรือไม่
likejudo

@Crocode ฉันและ j จะวนซ้ำถ้าเป็นทวีคูณของ N?
likejudo

คำตอบ:


190

มีหลายตัวเลือก:

2 มิติ

แผนที่ของแผนที่

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

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

วัตถุสำคัญของ 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;
    }

}

การดำเนินการequals()และhashCode()เป็นสิ่งสำคัญที่นี่ จากนั้นคุณก็ใช้:

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

และ:

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

Table จากฝรั่ง

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

table.get(2, 5);

Tableใช้แผนที่ของแผนที่ด้านล่าง

N ขนาด

สังเกตว่าKeyคลาสพิเศษเป็นแนวทางเดียวที่ปรับขนาดเป็น n-Dimension คุณอาจพิจารณา:

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

แต่นั่นแย่มากจากมุมมองด้านประสิทธิภาพรวมถึงความสามารถในการอ่านและความถูกต้อง (ไม่มีวิธีง่ายๆในการบังคับใช้ขนาดรายการ)

อาจลองดูที่ Scala ที่ซึ่งคุณมีทูเพิลและcaseคลาส (แทนที่ทั้งKeyคลาสด้วยซับเดียว)


3
สวัสดีคนอื่น ๆ มีค่า x'or สองค่าเมื่อทำ hashCode ทำไมคุณถึงใช้ 31? ฉันคิดว่ามันเกี่ยวข้องกับจำนวนเต็ม 32 บิต แต่เมื่อฉันคิดถึงมันก็ไม่สมเหตุสมผลเพราะ x = 1 และ y = 0 ยังแมปกับแฮชโค้ดเดียวกันกับ x = 0 และ y = 31
pete

1
@pete Joshua Bloch ใน Effective Java ch 3. s 9. แนะนำว่า "1. จัดเก็บค่าคงที่ที่ไม่ใช่ศูนย์กล่าวว่า 17 ในตัวแปร int ที่เรียกว่าผลลัพธ์ ... " ซึ่งจะทำงานได้ดีกว่าในการชนกันหากเป็น นายก. ดูสิ่งนี้ด้วย: stackoverflow.com/questions/3613102/…
fncomp

2
แทนที่จะเป็นวัตถุสำคัญของ Wrapper ทำไมไม่ใช้Map.Entry<K, V>เป็นคีย์?
Roland

1
เกี่ยวกับอะไรMap<Pair<Key1, Key2>, Value>?
Joaquin Iurchuk

1
โปรดทราบว่าhashCode()สามารถใช้งานได้ด้วยบรรทัดเดียวเช่นObjects.hash(x,y)
xdavidliu

23

เมื่อคุณสร้างวัตถุคู่คีย์ของคุณเองคุณควรเผชิญหน้ากับบางสิ่ง

ครั้งแรกที่คุณควรจะตระหนักถึงการดำเนินการและhashCode() equals()คุณจะต้องดำเนินการนี้

ประการที่สองเมื่อใช้งานhashCode()โปรดตรวจสอบให้แน่ใจว่าคุณเข้าใจวิธีการทำงาน ตัวอย่างผู้ใช้ที่กำหนด

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

เป็นหนึ่งในการใช้งานที่แย่ที่สุดที่คุณสามารถทำได้ เหตุผลง่ายๆคือคุณมีแฮชที่เท่ากันเยอะมาก! และhashCode()ควรคืนค่า int ที่มักจะหายากไม่ซ้ำใครจะดีที่สุด ใช้สิ่งนี้:

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

รวดเร็วและส่งคืนแฮชเฉพาะสำหรับคีย์ระหว่าง -2 ^ 16 และ 2 ^ 16-1 (-65536 ถึง 65535) เหมาะกับเกือบทุกกรณี ไม่ค่อยมีคุณอยู่นอกขอบเขตนี้

ประการที่สามเมื่อนำไปใช้equals()ควรรู้ด้วยว่ามันถูกใช้เพื่ออะไรและตระหนักถึงวิธีที่คุณสร้างคีย์ของคุณเนื่องจากมันเป็นวัตถุ บ่อยครั้งที่คุณทำโดยไม่จำเป็นหากงบทำให้คุณจะได้ผลลัพธ์เดียวกันเสมอ

หากคุณสร้างคีย์เช่นนี้map.put(new Key(x,y),V);คุณจะไม่เปรียบเทียบการอ้างอิงคีย์ของคุณ ทุกสาเหตุที่คุณต้องการ acces map.get(new Key(x,y));แผนที่คุณจะทำสิ่งที่ชอบ ดังนั้นคุณไม่จำเป็นต้องมีคำสั่งเช่นequals() if (this == obj)มันจะไม่เกิดขึ้น

แทนที่จะif (getClass() != obj.getClass())ของคุณในการใช้งานที่ดีขึ้นequals() if (!(obj instanceof this))จะใช้ได้แม้กระทั่งสำหรับคลาสย่อย

ดังนั้นสิ่งเดียวที่คุณต้องเปรียบเทียบคือ X และ Y ดังนั้นequals()การนำไปใช้งานที่ดีที่สุดในกรณีนี้คือ:

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

}

คุณสามารถให้ดัชนีมิติข้อมูลXและYระดับการเข้าถึงสาธารณะได้เนื่องจากข้อมูลดังกล่าวถือเป็นที่สิ้นสุดและไม่มีข้อมูลที่ละเอียดอ่อน ฉันไม่แน่ใจ 100% ว่าprivateระดับการเข้าถึงทำงานอย่างถูกต้องหรือไม่ในกรณีใด ๆเมื่อส่งObjectไปยังไฟล์Key.

หากคุณสงสัยเกี่ยวกับรอบชิงชนะเลิศฉันขอประกาศว่าค่าใดเป็นค่าสุดท้ายที่กำหนดในอินสแตนซ์และไม่เคยเปลี่ยนแปลง - ดังนั้นจึงเป็นค่าคงที่ของวัตถุ


7

คุณไม่สามารถมีแผนที่แฮชที่มีหลายคีย์ได้ แต่คุณสามารถมีออบเจ็กต์ที่รับพารามิเตอร์หลายตัวเป็นคีย์ได้

สร้างออบเจ็กต์ชื่อ Index ที่รับค่า x และ 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;
    }
}

จากนั้นHashMap<Index, Value>ให้คุณได้รับผลลัพธ์ของคุณ :)


4
คุณต้องแทนที่และhashCode equals
Tom Hawtin - แทคไลน์

4
การใช้งาน hashCode ไม่ได้แยกความแตกต่างระหว่าง (2,1) และ (1,2)
user1947415

1
นั่นคือการปะทะกัน Hashcode ไม่จำเป็นต้องรับประกันมูลค่าที่แตกต่างกันสำหรับทุกวัตถุที่แตกต่างกัน @ user1947415
Ajak6

6

นำไปใช้ในMultiKeyMap ทั่วไป


@ Wilson ฉันแก้ไขลิงก์แล้วตอนนี้กำลังรอการตรวจสอบจากเพื่อน
computingfreak

@computingfreak ดูเหมือนว่าจะมีมุมมองที่ดีเข้ามา เย่! หมายเหตุนี่คือคำตอบที่ดีที่สุดของ IMHO เว้นแต่คุณจะชอบใช้เวลาหลายชั่วโมงในการแข่งขันกับวิศวกรผู้เชี่ยวชาญของ Apache เพื่อหาฟังก์ชั่นการใช้งานบางอย่างที่มีประโยชน์มาก (เท่าที่เคยมีมา) แต่ท้ายที่สุดแล้ว
หนูไมค์

4

ความเป็นไปได้สองประการ ใช้คีย์รวม:

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

หรือแผนที่แผนที่:

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

2
ใช้แผนที่ของแผนที่เท่านั้นหากคุณไม่สนใจเกี่ยวกับประสิทธิภาพหรือการใช้หน่วยความจำที่นี่ (เช่นแผนที่มีขนาดเล็ก) หรือมีหลายคีย์ที่มีดัชนีแรกเหมือนกัน - เนื่องจากโซลูชันนี้หมายถึงการจ่ายค่าใช้จ่ายของวัตถุ HashMap สำหรับ ดัชนีแรกที่ไม่ซ้ำกันทุกรายการ
BeeOnRope

1
เพื่อปรับปรุงคำตอบนี้นี่คือข้อมูลเกี่ยวกับการลบล้างhashCodeและequalsวิธีการ
Pshemo

2

ใช้ a Pairเป็นคีย์สำหรับไฟล์HashMap. JDK ไม่มีคู่ แต่คุณสามารถใช้บรรณารักษ์ของบุคคลที่สามเช่นhttp://commons.apache.org/langหรือเขียน Pair taype ของคุณเอง


1

สร้างคลาสค่าที่จะแทนคีย์ผสมของคุณเช่น:

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

ดูแลที่จะลบล้างequals()และhashCode()ถูกต้อง หากดูเหมือนว่าจะใช้งานได้มากคุณอาจพิจารณาคอนเทนเนอร์ทั่วไปที่พร้อมใช้งานเช่นPairapache commons จัดเตรียมไว้ให้

นอกจากนี้ยังมีคำถามที่คล้ายกันอีกมากมายพร้อมแนวคิดอื่น ๆ เช่นการใช้ Guava's Tableแม้ว่าจะอนุญาตให้คีย์มีประเภทต่างๆซึ่งอาจใช้งานมากเกินไป (ในการใช้หน่วยความจำและความซับซ้อน) ในกรณีของคุณเนื่องจากฉันเข้าใจว่าคีย์ของคุณเป็นจำนวนเต็มทั้งคู่


1

หากพวกเขาเป็นสองจำนวนเต็มคุณสามารถลองเคล็ดลับที่รวดเร็วและสกปรก: ใช้คีย์เป็นMap<String, ?>i+"#"+j

ถ้าคีย์i+"#"+jเหมือนj+"#"+iลองmin(i,j)+"#"+max(i,j).


2
ความคิดที่ไม่ดีจริงๆ ประการแรกมันแย่เพียง ประการที่สองเทคนิคนี้จะถูกคัดลอกสำหรับประเภทอื่น ๆ ซึ่งอาจมีการแมปคีย์ที่แตกต่างกันไปStringพร้อมกับผลลัพธ์ที่น่าขบขัน
Tom Hawtin - แท็

สำหรับฉันแล้วดูเหมือนว่าi#j = j#iเมื่อi == jใช้min/maxกลอุบายจะไม่เกิดขึ้น
Matthieu

1
@ Matthieu อะไรคือความแตกต่างระหว่าง5#5และ5#5สลับรอบ?
enrey

@enrey ไม่มี. นั่นคือสิ่งที่ฉันชี้ให้เห็น มันขึ้นอยู่กับความรู้ที่คุณมีเกี่ยวกับกุญแจของคุณจริงๆ
Matthieu

@Matthieu aha ฉันเข้าใจว่าคุณหมายถึงอะไร ฉันคิดว่าสิ่งที่ @arutaku หมายถึงคือเมื่อคุณต้องการ5#3มีแฮชแบบเดียวกัน3#5จากนั้นคุณใช้ min / max เพื่อบังคับใช้3#5ตามลำดับนี้
enrey

1

คุณยังสามารถใช้guava Tableสำหรับสิ่งนี้ได้

ตารางแสดงแผนที่พิเศษที่สามารถระบุสองคีย์ในรูปแบบรวมกันเพื่ออ้างถึงค่าเดียว มันคล้ายกับการสร้างแผนที่ของแผนที่

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

คุณสามารถสร้างวัตถุหลักของคุณได้ดังนี้:

ชั้นสาธารณะ 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;
}

}

ข้อดีของสิ่งนี้คือมันจะทำให้แน่ใจว่าคุณครอบคลุมสถานการณ์ทั้งหมดของ Equals ด้วยเช่นกัน

หมายเหตุ : คีย์ 1 และคีย์ 2 ของคุณควรไม่เปลี่ยนรูป จากนั้นคุณจะสามารถสร้างวัตถุหลักที่มั่นคงได้


1

เราสามารถสร้างคลาสเพื่อส่งผ่านคีย์หรือค่ามากกว่าหนึ่งคีย์และอ็อบเจ็กต์ของคลาสนี้สามารถใช้เป็นพารามิเตอร์ในแผนที่

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

คุณสามารถดาวน์โหลดได้จากลิงค์ด้านล่าง: https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java

https://github.com/VVS279/DoubleKeyHashMap

คุณสามารถใช้คีย์คู่: แฮชแมปค่า

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

   DoubleKeyHashMap<String, String, String> doubleKeyHashMap2 = new 
   DoubleKeyHashMap<String, String, String>();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.