Java에 역방향 조회 기능이있는 HashMap이 있습니까?


98

"키-값"이 아닌 "키-키"형식으로 구성된 데이터가 있습니다. HashMap과 비슷하지만 양방향으로 O (1) 조회가 필요합니다. 이러한 유형의 데이터 구조에 대한 이름이 있습니까? 그리고 이와 비슷한 것이 Java의 표준 라이브러리에 포함되어 있습니까? (또는 Apache Commons?)

기본적으로 두 개의 미러링 된 맵을 사용하는 내 클래스를 작성할 수 있지만 휠을 재발 명하지는 않습니다 (이미 존재하지만 올바른 용어를 검색하지 않는 경우).

답변:


106

Java API에는 이러한 클래스가 없습니다. 원하는 Apache Commons 클래스는 BidiMap 구현 중 하나가 될 것입니다 .

수학자로서 저는 이런 종류의 구조를 bijection이라고 부를 것입니다.


83
비 수학자로서 저는 이런 종류의 구조를 "키 또는 다른 방법으로 값을 조회 할 수있는 맵"이라고 부를 것입니다
Dónal

4
제네릭을 지원하지 않는 것이 안타깝습니다. Guava가 지원하는 것 같습니다.
Eran Medan


1
@Don "Bidi"-> "Bi-Directional"
ryvantage

3
DONAL 네,하지만 전체 @ IT는 수학을 기반으로한다
알렉스

75

Apache Commons 외에도 Guava 에는 BiMap이 있습니다.


정보 주셔서 감사합니다! 나는 당분간 아파치를 고수하고 있습니다 (그렇지 않은 좋은 이유가 없다면?)
Kip

아파치 컬렉션과 좋은 비교를 제공 할 수는 없지만 Google 컬렉션에는 살펴볼 가치가 있다고 생각하는 멋진 기능이 많이 있습니다.
ColinD 2009

16
Google 컬렉션의 한 가지 장점은 일반 컬렉션이 있지만 Commons 컬렉션에는 없다는 것입니다.
Mark

3
두 라이브러리를 비교하려면이 답변의 따옴표를 참조하십시오. stackoverflow.com/questions/787446/… (및 원래 인터뷰). 그것은 명백한 이유로 Google에 편향되어 있지만 요즘에는 Google 컬렉션을 사용하는 것이 더 낫다고 말하는 것이 안전하다고 생각합니다.
Jonik

1
BiMap 링크가 끊어졌습니다. 이것을 사용 하십시오 .
Mahsa2

20

다음은이 작업을 수행하는 데 사용한 간단한 클래스입니다 (아직 다른 타사 종속성을 갖고 싶지 않았습니다). 지도에서 사용할 수있는 모든 기능을 제공하지는 않지만 좋은 시작입니다.

    public class BidirectionalMap<KeyType, ValueType>{
        private Map<KeyType, ValueType> keyToValueMap = new ConcurrentHashMap<KeyType, ValueType>();
        private Map<ValueType, KeyType> valueToKeyMap = new ConcurrentHashMap<ValueType, KeyType>();

        synchronized public void put(KeyType key, ValueType value){
            keyToValueMap.put(key, value);
            valueToKeyMap.put(value, key);
        }

        synchronized public ValueType removeByKey(KeyType key){
            ValueType removedValue = keyToValueMap.remove(key);
            valueToKeyMap.remove(removedValue);
            return removedValue;
        }

        synchronized public KeyType removeByValue(ValueType value){
            KeyType removedKey = valueToKeyMap.remove(value);
            keyToValueMap.remove(removedKey);
            return removedKey;
        }

        public boolean containsKey(KeyType key){
            return keyToValueMap.containsKey(key);
        }

        public boolean containsValue(ValueType value){
            return keyToValueMap.containsValue(value);
        }

        public KeyType getKey(ValueType value){
            return valueToKeyMap.get(value);
        }

        public ValueType get(KeyType key){
            return keyToValueMap.get(key);
        }
    }

5
containsValue ()를 valueToKeyMap.containsKey (value)
JT

키 (또는 값)가 다른 값 (또는 키)으로 다시 추가되면 양방향성이 깨지기 때문에 현재이 맵을 사용하지 않을 것입니다. 이는 키 IMO를 업데이트하는 데 유효한 사용입니다.
Qw3ry

11

충돌이 발생하지 않으면 항상 동일한 HashMap에 양방향을 추가 할 수 있습니다. :-)


6
@Kip : 왜? 일부 상황에서 이것은 완벽하게 합법적 인 솔루션입니다. 따라서 두 개의 해시 맵이 있습니다.
Lawrence Dol

7
아니, 추악하고 깨지기 쉬운 해킹입니다. 모든 get () 및 put ()에서 양방향 속성을 유지 관리해야하며 양방향 속성에 대해 알지 못해도지도를 수정하는 다른 메서드에 전달할 수 있습니다. 아무데도 전달되지 않는 메서드 내부의 로컬 변수로 괜찮을 수도 있고 생성 직후 수정할 수 없게 된 경우에도 괜찮을 것입니다. 하지만 그래도 깨지기 쉽습니다 (누군가가 와서 그 기능을 조정하고 항상 문제가되는 것을 즉시 보여주지는 않는 방식으로 양방향성을 깨뜨립니다)
Kip

1
@Kip, 나는 그러한 사용법이 해당 맵을 사용하여 클래스 내부에 유지되어야한다는 데 동의하지만, 마지막 언급은 해당 JUnit 테스트가 엉성한 경우에만 적용됩니다. :-)
rsp

이러한 구현의 매우 유효한 사용을 제안 할 수 있다면 여기서 어셈블리 언어 명령의 opcode를 디코딩 / 인코딩하기위한 맵이 필요하다고 상상하면 맵의 상태도 변경하지 않을 것입니다. 한 방향에서 키는 다른 이진 값의 명령 문자열입니다. 따라서 충돌이 있어서는 안됩니다.
MK

소규모 조회 목적으로 그 해킹이 내 문제를 해결합니다.
milkersarac

5

여기 내 2 센트.

또는 제네릭과 함께 간단한 방법을 사용할 수 있습니다. 케이크 조각.

public static <K,V> Map<V, K> invertMap(Map<K, V> toInvert) {
    Map<V, K> result = new HashMap<V, K>();
    for(K k: toInvert.keySet()){
        result.put(toInvert.get(k), k);
    }
    return result;
}

물론 고유 한 값이있는 맵이 있어야합니다. 그렇지 않으면 그중 하나가 교체됩니다.


1

GETah의 답변에 영감을 받아 몇 가지 개선 사항을 통해 비슷한 것을 작성하기로 결정했습니다.

  • 클래스는 Map<K,V>-Interface를 구현하고 있습니다.
  • 양방향성은 값을 a로 변경할 때 처리함으로써 실제로 보장됩니다 put(적어도 이로써 보증하고 싶습니다).

사용법은 매핑 호출에 대한 역보기를 얻기 위해 노멀 맵과 같습니다 getReverseView(). 콘텐츠는 복사되지 않고보기 만 반환됩니다.

이것이 완전히 어리석은 것인지 확신 할 수 없습니다 (실제로는 그렇지 않을 것입니다). 결함이 발견되면 댓글을 남겨 주시면 답변을 업데이트하겠습니다.

public class BidirectionalMap<Key, Value> implements Map<Key, Value> {

    private final Map<Key, Value> map;
    private final Map<Value, Key> revMap;

    public BidirectionalMap() {
        this(16, 0.75f);
    }

    public BidirectionalMap(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    public BidirectionalMap(int initialCapacity, float loadFactor) {
        this.map = new HashMap<>(initialCapacity, loadFactor);
        this.revMap = new HashMap<>(initialCapacity, loadFactor);
    }

    private BidirectionalMap(Map<Key, Value> map, Map<Value, Key> reverseMap) {
        this.map = map;
        this.revMap = reverseMap;
    }

    @Override
    public void clear() {
        map.clear();
        revMap.clear();
    }

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return revMap.containsKey(value);
    }

    @Override
    public Set<java.util.Map.Entry<Key, Value>> entrySet() {
        return Collections.unmodifiableSet(map.entrySet());
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public Set<Key> keySet() {
        return Collections.unmodifiableSet(map.keySet());
    }

    @Override
    public void putAll(Map<? extends Key, ? extends Value> m) {
        m.entrySet().forEach(e -> put(e.getKey(), e.getValue()));
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public Collection<Value> values() {
        return Collections.unmodifiableCollection(map.values());
    }

    @Override
    public Value get(Object key) {
        return map.get(key);
    }

    @Override
    public Value put(Key key, Value value) {
        Value v = remove(key);
        getReverseView().remove(value);
        map.put(key, value);
        revMap.put(value, key);
        return v;
    }

    public Map<Value, Key> getReverseView() {
        return new BidirectionalMap<>(revMap, map);
    }

    @Override
    public Value remove(Object key) {
        if (containsKey(key)) {
            Value v = map.remove(key);
            revMap.remove(v);
            return v;
        } else {
            return null;
        }
    }

}

BiMap 및 BidiMap과 마찬가지로 동일한 값을 가진 여러 키를 가질 수없는 bijection입니다. (getReverseView (). get (v)는 항상 하나의 키만 반환합니다).
Donatello

사실이지만 OTOH는 OP가 요청한 것입니다
Qw3ry

그가 데이터가이 제약 조건과 일치한다고 표현했는지는 모르겠지만 어쨌든 다른 사람이 더 잘 이해하는 데 도움이 될 수 있습니다!
Donatello

0

여기에는 꽤 오래된 질문이 있지만, 제가 방금했던 것처럼 다른 사람이 뇌 차단을해서 우연히 발견하면 도움이 될 것입니다.

나도 양방향 HashMap을 찾고 있었는데, 때로는 가장 유용한 답변 중 가장 간단한 답변입니다.

바퀴를 재발 명하고 싶지 않고 다른 라이브러리 나 프로젝트를 프로젝트에 추가하지 않으려면 병렬 배열 (또는 디자인에서 요구하는 경우 ArrayLists)을 간단하게 구현하는 것이 좋습니다.

SomeType[] keys1 = new SomeType[NUM_PAIRS];
OtherType[] keys2 = new OtherType[NUM_PAIRS];

두 키 중 하나의 색인을 알게되면 다른 키를 쉽게 요청할 수 있습니다. 따라서 조회 방법은 다음과 같습니다.

SomeType getKey1(OtherType ot);
SomeType getKey1ByIndex(int key2Idx);
OtherType getKey2(SomeType st); 
OtherType getKey2ByIndex(int key2Idx);

이것은 당신이 적절한 객체 지향 구조를 사용하고 있다고 가정하고, 메소드 만이 이러한 배열 / ArrayList를 수정하는 경우, 그것들을 병렬로 유지하는 것은 매우 간단합니다. 동시에 추가 / 제거하는 한 배열의 크기가 변경되면 다시 빌드 할 필요가 없기 때문에 ArrayList의 경우 더 쉽습니다.


3
HashMaps의 중요한 기능, 즉 O (1) 조회를 잃고 있습니다. 이와 같은 구현에서는 찾고있는 항목의 인덱스를 찾을 때까지 배열 중 하나를 스캔해야합니다. 즉, O (n)
Kip

네 그것은 매우 사실이며 다소 큰 단점 중 하나입니다. 그러나 내 개인적인 상황에서 나는 실제로 3 방향 키 목록에 대한 필요성을 다루고 있었고 항상 적어도 하나의 키를 미리 알고 있었으므로 개인적으로 그것은 문제가되지 않았습니다. 지적 해주셔서 감사합니다. 원래 게시물에서 그 중요한 사실을 건너 뛴 것 같습니다.
ThatOneGuy 2015
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.