두 개의 키 (Key-Pair, Value)로 HashMap을 만드는 방법은 무엇입니까?


118

정수의 2D 배열이 있습니다. 나는 그것들을 HashMap에 넣기를 원합니다. 하지만 Array Index를 기반으로 HashMap의 요소에 액세스하고 싶습니다. 다음과 같은 것 :

A [2] [5]의 경우 map.get(2,5)해당 키와 관련된 값을 반환합니다. 하지만 키 쌍으로 hashMap을 어떻게 생성합니까? 또는 일반적으로 여러 키 : Map<((key1, key2,..,keyN), Value)get (key1, key2, ... keyN)을 사용하여 요소에 액세스 할 수있는 방식으로.

편집 : 질문을 게시 한 후 3 년, 나는 그것에 조금 더 추가하고 싶습니다

나는 NxN matrix.

배열 인덱스는, ij단일로 표현 될 수있다 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

@Crocode 와우! 편집에서 대답 뒤에 숨겨진 수학은 놀랍습니다. 두 정수 i와 j에 대해 일반적으로 작동하는지 궁금합니다.
likejudo

@Crocode는 i와 j가 N의 배수이면 순환합니까?
likejudo

답변:


190

몇 가지 옵션이 있습니다.

2 차원

지도지도

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

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

래퍼 키 개체

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 차원으로 확장되는 유일한 접근 방식입니다. 다음을 고려할 수도 있습니다.

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

그러나 그것은 성능 측면에서 끔찍할뿐만 아니라 가독성과 정확성 (목록 크기를 적용하는 쉬운 방법이 없음)도 마찬가지입니다.

튜플과 case클래스 가있는 Scala를 살펴볼 수도 있습니다 (전체 Key클래스를 한 줄로 대체 ).


3
안녕하세요, 다른 사람들은 hashCode를 할 때 x '또는 두 개의 값을 가지고 있습니다. 31을 사용하는 이유는 무엇입니까? 32 비트 정수와 관련이 있다고 생각했지만 x = 1 및 y = 0이 여전히 x = 0 및 y = 31과 동일한 해시 코드에 매핑되기 때문에 이해가되지 않습니다
pete

1
@pete Joshua Bloch (Effective Java ch 3. s 9.)는 "1. 0이 아닌 상수 값, 예를 들어 17을 result ...라는 int 변수에 저장"이라고 권장합니다. 프라임. 참조 : stackoverflow.com/questions/3613102/...
fncomp

2
래퍼 키 개체 대신 키로 사용하지 않는 이유 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);의 참조를 비교하지 않습니다. 지도에 액세스하고 싶을 때마다 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;
  }

}

차원 인덱스 XY공개 액세스 수준은 최종 항목이고 민감한 정보를 포함하지 않기 때문에 제공 할 수 있습니다 . 나는 확실히 여부를 100 % 아니에요 private액세스 수준이 제대로 작동 어떤 을 캐스팅 할 때 경우 ObjectA를 Key.

파이널에 대해 궁금하다면 인스턴스에 설정되고 절대 변경되지 않는 값을 final로 선언하므로 객체 상수입니다.


7

여러 키가있는 해시 맵을 가질 수 없지만 여러 매개 변수를 키로 사용하는 객체를 가질 수 있습니다.

x 및 y 값을 사용하는 Index라는 개체를 만듭니다.

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

4
hashCode 구현은 (2,1)과 (1,2)를 구분하지 않았습니다
user1947415 2014-08-05

1
그것은 충돌입니다. 해시 코드는 모든 객체에 대해 다른 값을 보장 할 필요가 없습니다. @ user1947415
Ajak6

6

공통 컬렉션 MultiKeyMap 에서 구현


@Wilson 나는 동료 평가를 기다리고, 지금 링크를 고정
computingfreak

@computingfreak은 호의적 인 견해가 나온 것 같습니다. 만세! NB 이것은 최고의 대답 IMHO입니다. 매우 유용하지만 궁극적으로 평범한 기능을 놓고 Apache 전문 엔지니어와 경쟁하는 데 시간을 보내는 것을 좋아하지 않는 한.
마이크 설치류

4

두 가지 가능성. 결합 된 키를 사용하십시오.

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

또는지도지도 :

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

2
여기에서 성능이나 메모리 사용에 신경 쓰지 않거나 (즉, 맵이 작음) 맵 맵을 사용하거나 첫 번째 인덱스가 동일한 키가 많은 경우-이 솔루션은 HashMap 객체의 오버 헤드를 모든 단일 고유 첫 번째 색인.
BeeOnRope 2013

1
이 답변을 개선하기 위해 여기 에 재정의 hashCodeequals메서드 에 대한 정보가 있습니다.
Pshemo

2

Pair대한 키로 사용하십시오 HashMap. JDK에는 Pair가 없지만 http://commons.apache.org/lang 과 같은 타사 라이브러리를 사용하거나 직접 Pair taype를 작성할 수 있습니다.


1

다음과 같이 복합 키를 나타내는 값 클래스를 만듭니다.

class Index2D {
  int first, second;

  // overrides equals and hashCode properly here
}

재정의 equals()하고 hashCode()올바르게. 이것이 많은 작업처럼 보인다면 Pair아파치 커먼즈에서 제공하는 것과 같은 기성품 일반 컨테이너를 고려할 수 있습니다.

Guava의 Table 사용과 같은 다른 아이디어와 함께 유사한 질문이 많이 있지만 키가 다른 유형을 가질 수 있습니다.


1

두 개의 정수라면 빠르고 더러운 트릭을 시도해 볼 수 있습니다 : Map<String, ?>키를 i+"#"+j.

i+"#"+jj+"#"+itry 와 같은 경우 min(i,j)+"#"+max(i,j).


2
정말 나쁜 생각입니다. 첫째, 그것은 단지 나쁘다. 둘째,이 기술은 다른 키가 동일한 String결과로 매핑 될 수있는 다른 유형에 대해 복사됩니다 .
Tom Hawtin-tackline

그것은 나에게 보인다 i#j = j#ii == j사용 있도록 min/max트릭 것은하지 않을 것이다.
Matthieu

1
@Matthieu의 차이 무엇 5#55#5주변의 교환은?
enrey 2015-04-29

@enrey 없음. 그것이 내가 지적한 것입니다. 키에 대한 지식에 따라 다릅니다.
Matthieu 2015

@Matthieu 아하 무슨 뜻인지 알겠습니다. @arutaku가 의미 5#3하는 바는 같은 해시 를 원할 때라고 생각한 3#5다음 최소 / 최대를 사용 3#5하여이 순서 로 시행 합니다.
enrey

1

이를 위해 구아바 테이블 구현을 사용할 수도 있습니다 .

테이블은 단일 값을 참조하기 위해 결합 된 방식으로 두 개의 키를 지정할 수있는 특수 맵을 나타냅니다. 지도지도를 만드는 것과 비슷합니다.

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

다음과 같이 키 개체를 만들 수 있습니다.

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

}

이것의 장점은 다음과 같습니다. 항상 Equals의 모든 시나리오를 다루고 있는지 확인합니다.

참고 : key1 및 key2는 변경할 수 없습니다. 그래야만 안정적인 키 개체를 구성 할 수 있습니다.


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

        }

    }

    }

 }

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.