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.