Java'da ters aramalı bir HashMap var mı?


98

"Anahtar-değer" yerine "anahtar-anahtar" biçiminde düzenlenmiş verilerim var. Bu bir HashMap gibi, ancak her iki yönde de O (1) aramasına ihtiyacım olacak. Bu tür bir veri yapısı için bir isim var mı ve buna benzer herhangi bir şey Java'nın standart kitaplıklarına dahil mi? (veya belki Apache Commons?)

Temelde iki yansıtılmış Harita kullanan kendi sınıfımı yazabilirim, ancak tekerleği yeniden icat etmemeyi tercih ederim (eğer bu zaten mevcutsa ama sadece doğru terimi aramıyorum).

Yanıtlar:


106

Java API'de böyle bir sınıf yoktur. İstediğiniz Apache Commons sınıfı, BidiMap'in uygulamalarından biri olacak .

Bir matematikçi olarak, bu tür bir yapıya bir eşleştirme derdim.


83
matematikçi olmayan biri olarak bu tür bir yapıya "anahtar veya başka bir yolla değerleri aramanıza izin veren bir harita"
derdim

4
Guava'nın jenerik ilaç desteğinin olmaması çok kötü.
Eran Medan


1
@Don "Bidi" -> "Çift Yönlü"
ryvantage

3
@ Dónal Evet, ancak BT'nin tamamı matematiğe dayanıyor
Alex

76

Apache Commons ek olarak, Guava de vardır BiMap .


bilgi için teşekkürler! Şimdilik apache ile uğraşıyorum (yapmamak için bazı iyi nedenler yoksa?)
Kip

Apache koleksiyonlarıyla iyi bir karşılaştırma yapamam, ancak google collections'ın bakmaya değer olacağını düşündüğüm pek çok güzel içeriği var.
ColinD

16
Google Koleksiyonlarının bir avantajı, jeneriklere sahip olması, ancak Commons Collections'ın olmamasıdır.
Mark

3
İki kitabın karşılaştırması için, bu cevaptaki alıntılara bakın: stackoverflow.com/questions/787446/… (ve orijinal röportaj). Bu, bariz nedenlerden ötürü Google'a karşı önyargılı, ancak yine de bugünlerde Google Koleksiyonları ile daha iyi durumda olduğunuzu söylemenin güvenli olduğunu düşünüyorum.
Jonik

1
BiMap bağlantısı koptu. Kullanın bu bir .
Mahsa2

20

İşte bunu yapmak için kullandığım basit bir sınıf (henüz başka bir üçüncü taraf bağımlılığına sahip olmak istemedim). Haritalar'da bulunan tüm özellikleri sunmaz, ancak iyi bir başlangıçtır.

    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
Bunu valueToKeyMap.containsKey (value)
JT

Bu haritayı şu anda olduğu gibi kullanmam çünkü bir anahtar (veya değer) farklı bir değerle (veya anahtarla) yeniden eklenirse iki yönlülük bozulur, bu da bir anahtar IMO'yu güncellemek için geçerli bir kullanım olacaktır.
Qw3ry

11

Çarpışma meydana gelmezse, her zaman aynı HashMap'e her iki yönü de ekleyebilirsiniz :-)


6
@Kip: Neden? Bazı bağlamlarda bu tamamen meşru bir çözümdür. Yani iki hash haritasına sahip olmak.
Lawrence Dol

7
hayır, bu çirkin, kırılgan bir hack. her get () ve put () üzerinde iki yönlü özelliğin bakımını gerektirir ve iki yönlü özelliği bilmeden haritayı değiştiren diğer yöntemlere aktarılabilir. belki de herhangi bir yere aktarılmayan bir yöntem içinde yerel bir değişken olarak veya oluşturulduktan hemen sonra değiştirilemez hale getirilmişse sorun olmazdı. ama o zaman bile kırılgandır (birisi gelir ve bu işlevi değiştirir ve her zaman kendini hemen bir sorun olarak göstermeyecek şekilde çift
Kip

1
@Kip, bu tür bir kullanımın bu haritayı kullanarak sınıfın içinde tutulması gerektiğini kabul ediyorum, ancak son açıklamanız yalnızca ilgili JUnit testleri özensizse geçerlidir :-)
rsp

Böyle bir uygulamanın çok geçerli bir kullanımını ortaya koyabilirsem, burada birleştirme dili talimatlarının kodlarının çözülmesi / kodlanması için bir haritaya ihtiyaç duyduğunuzu hayal edin, haritanın durumunu da asla değiştirmeyeceksiniz, bir yönde anahtarlar, diğer ikili değerler komut dizeleridir. Bu yüzden asla çatışmamalısınız.
MK

Küçük ölçekli bir arama amacı için, bu hack sorunumu çözer.
milkersarac

5

İşte benim 2 sentim.

Veya jeneriklerle basit bir yöntem kullanabilirsiniz. Kekin parçası.

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

Elbette benzersiz değerlere sahip bir haritanız olmalıdır. Aksi takdirde biri değiştirilecektir.


1

GETah'ın cevabından esinlenerek , bazı iyileştirmelerle kendi başıma benzer bir şey yazmaya karar verdim:

  • Sınıf, Map<K,V>-Interface'i uyguluyor
  • Çift yönlülük, bir değeri a ile değiştirirken buna dikkat ederek gerçekten garanti edilir put(en azından burada garanti etmeyi umuyorum)

Haritalama çağrısında ters bir görünüm elde etmek için kullanım normal bir harita gibidir getReverseView(). İçerik kopyalanmaz, sadece bir görünüm döndürülür.

Bunun tamamen aptalca bir kanıt olduğundan emin değilim (aslında, muhtemelen değil), bu yüzden herhangi bir kusur fark ederseniz yorum yapmaktan çekinmeyin ve cevabı güncelleyeceğim.

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 ve BidiMap gibi, bunun aynı değere sahip birden fazla anahtara sahip olmaya izin vermeyen bir eşleştirme olduğunu unutmayın. (getReverseView (). get (v) her zaman yalnızca bir anahtar döndürür).
Donatello

Doğru, ancak OTOH tam olarak OP'nin istediği şey
Qw3ry

Verilerinin bu kısıtlamaya uyduğunu ifade ettiğinden emin değilim, ancak yine de, başka birinin daha iyi anlamasına yardımcı olabilir!
Donatello

0

Burada oldukça eski bir soru, ama benim yaptığım gibi başka biri beyin bloğuna sahipse ve bu konuda tökezlerse, umarım bu yardımcı olur.

Ben de iki yönlü bir HashMap arıyordum, bazen en kullanışlı olan en basit cevaplardır.

Tekerleği yeniden icat etmek istemiyor ve projenize başka kitaplıklar veya projeler eklememeyi tercih ediyorsanız, paralel dizilerin (veya tasarımınız gerektiriyorsa Dizi Listelerinin) basit bir şekilde uygulanmasına ne dersiniz?

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

İki anahtardan birinin indeksini öğrenir öğrenmez diğerini kolayca talep edebilirsiniz. Böylece arama yöntemleriniz şuna benzer olabilir:

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

Bu, uygun nesne yönelimli yapılar kullandığınızı varsayar, burada sadece yöntemler bu dizileri / Dizi Listelerini değiştirir, onları paralel tutmak çok basit olacaktır. Art arda ekleyip çıkardığınız sürece, dizilerin boyutu değişirse yeniden oluşturmak zorunda kalmayacağınızdan, bir ArrayList için daha da kolaydır.


3
HashMaps'in önemli bir özelliğini, yani O (1) aramasını kaybediyorsunuz. Bunun gibi bir uygulama, aradığınız öğenin dizinini bulana kadar dizilerden birinin taranmasını gerektirir, yani O (n)
Kip

Evet bu çok doğru ve oldukça büyük bir dezavantaj. Bununla birlikte, kişisel durumumda aslında üç yönlü bir anahtar listeleme ihtiyacıyla uğraşıyordum ve her zaman en az 1 anahtarı önceden biliyordum, bu yüzden şahsen benim için bu bir sorun değildi. Buna işaret ettiğiniz için teşekkür ederim, orijinal yazımda bu önemli gerçeği atlamış gibiyim.
ThatOneGuy 15
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.