Apakah Java memiliki HashMap dengan pencarian terbalik?

98

Saya memiliki data yang diatur dalam format "kunci-kunci", bukan "nilai-kunci". Ini seperti HashMap, tetapi saya membutuhkan pencarian O (1) di kedua arah. Apakah ada nama untuk tipe struktur data ini, dan apakah yang seperti ini termasuk dalam pustaka standar Java? (atau mungkin Apache Commons?)

Saya bisa menulis kelas saya sendiri yang pada dasarnya menggunakan dua Peta yang dicerminkan, tetapi saya lebih suka tidak menemukan kembali roda (jika ini sudah ada tetapi saya tidak mencari istilah yang tepat).

Tidur
sumber

Jawaban:

106

Tidak ada kelas seperti itu di Java API. Kelas Apache Commons yang Anda inginkan akan menjadi salah satu implementasi BidiMap .

Sebagai seorang ahli matematika, saya akan menyebut struktur semacam ini sebagai kebijaksanaan.

uckelman
sumber
83
sebagai non-matematikawan, saya akan menyebut jenis struktur ini "peta yang memungkinkan Anda mencari nilai dengan kunci atau sebaliknya"
Dónal
4
Sayang sekali tidak ada dukungan untuk obat generik, tampaknya Jambu.
Eran Medan
2
github.com/megamattron/collections-generic memiliki BidiMap dengan dukungan Generik
Kenston Choi
1
@Don "Bidi" -> "Bi-Directional"
ryvantage
3
@ Dónal Ya, tetapi seluruh TI didasarkan pada matematika
Alex
75

Selain Apache Commons, Guava juga memiliki BiMap .

ColinD
sumber
terimakasih atas infonya! Saya tetap menggunakan apache untuk saat ini (kecuali jika ada alasan bagus untuk tidak melakukannya?)
Kip
Saya tidak dapat menawarkan perbandingan yang baik dengan koleksi apache, tetapi koleksi google memang memiliki banyak hal bagus yang menurut saya akan membuatnya layak untuk dilihat.
ColinD
16
Satu keuntungan dari Koleksi Google adalah ia memiliki obat generik sedangkan Koleksi Commons tidak.
Tandai
3
Untuk perbandingan dari dua lib, lihat kutipan dalam jawaban ini: stackoverflow.com/questions/787446/… (dan wawancara asli). Itu bias terhadap Google, untuk alasan yang jelas, tetapi meskipun demikian menurut saya aman untuk mengatakan bahwa Anda lebih baik dengan Koleksi Google saat ini.
Jonik
1
Tautan BiMap rusak. Silakan gunakan yang ini .
Mahsa2
20

Ini adalah kelas sederhana yang saya gunakan untuk menyelesaikan ini (saya tidak ingin memiliki ketergantungan pihak ketiga lagi). Itu tidak menawarkan semua fitur yang tersedia di Maps tetapi ini adalah awal yang baik.

    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);
        }
    }
GETah
sumber
5
Anda akan secara signifikan meningkatkan kinerja containsValue () dengan mengubahnya menjadi mengembalikan valueToKeyMap.containsKey (value)
JT.
Saya tidak akan menggunakan peta ini karena saat ini karena bi-directionality rusak jika kunci (atau nilai) ditambahkan kembali dengan nilai yang berbeda (atau kunci), yang akan menjadi penggunaan yang valid untuk memperbarui IMO kunci.
Qw3ry
11

Jika tidak ada tabrakan, Anda selalu dapat menambahkan kedua arah ke HashMap yang sama :-)

rsp
sumber
6
@Kip: Mengapa? Dalam beberapa konteks, ini adalah solusi yang sangat sah. Jadi akan memiliki dua peta hash.
Lawrence Dol
7
tidak, ini adalah peretasan yang jelek dan rapuh. itu membutuhkan pemeliharaan properti dua arah pada setiap get () dan put (), dan itu bisa diteruskan ke metode lain yang memodifikasi peta bahkan tanpa mengetahui tentang properti dua arah. mungkin tidak masalah sebagai variabel lokal di dalam metode yang tidak diteruskan ke mana pun, atau jika dibuat tidak dapat dimodifikasi segera setelah dibuat. tetapi bahkan kemudian, itu rapuh (seseorang datang dan mengubah fungsi itu dan mematahkan dua arah dengan cara yang tidak akan selalu segera menunjukkan dirinya menjadi masalah)
Kip
1
@Kip, saya setuju bahwa penggunaan seperti itu harus disimpan secara internal untuk kelas menggunakan peta itu, tetapi komentar terakhir Anda hanya berlaku jika tes JUnit yang sesuai tidak rapi :-)
rsp
Jika saya dapat mengajukan penggunaan yang sangat valid dari implementasi seperti itu, bayangkan membutuhkan peta untuk decoding / encoding opcodes dari instruksi bahasa assembly di sini Anda tidak akan pernah mengubah status peta juga, dalam satu arah kunci adalah instruksi string nilai biner lainnya. Jadi, Anda seharusnya tidak pernah mengalami konflik.
MK
Untuk tujuan pencarian skala kecil, peretasan itu memecahkan masalah saya.
milkersarac
5

Ini 2 sen saya.

Atau Anda dapat menggunakan metode sederhana dengan obat generik. Sepotong kue.

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

Tentunya Anda harus memiliki peta dengan nilai yang unik. Jika tidak, salah satunya akan diganti.

Fulvius
sumber
1

Terinspirasi oleh jawaban GETah, saya memutuskan untuk menulis sesuatu yang serupa sendiri dengan beberapa perbaikan:

  • Kelas mengimplementasikan Map<K,V>-Interface
  • Bidirectionality benar-benar dijamin dengan menjaganya saat mengubah nilai dengan put(setidaknya saya berharap dapat menjaminnya dengan ini)

Penggunaannya seperti peta biasa, untuk mendapatkan tampilan terbalik pada panggilan pemetaan getReverseView(). Konten tidak disalin, hanya tampilan yang dikembalikan.

Saya tidak yakin ini benar-benar bukti yang bodoh (sebenarnya, mungkin tidak), jadi silakan berkomentar jika Anda melihat ada kekurangan dan saya akan memperbarui jawabannya.

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

}
Qw3ry
sumber
perhatikan bahwa seperti BiMap dan BidiMap, ini adalah bijection yang tidak memungkinkan memiliki banyak kunci dengan nilai yang sama. (getReverseView (). get (v) akan selalu mengembalikan hanya satu kunci).
Donatello
Benar, tapi OTOH persis seperti yang diminta OP
Qw3ry
Saya tidak yakin dia menyatakan bahwa datanya sesuai dengan batasan ini, tetapi bagaimanapun, ini dapat membantu orang lain untuk lebih memahami!
Donatello
0

Pertanyaan yang cukup lama di sini, tetapi jika orang lain mengalami penyumbatan otak seperti yang baru saja saya lakukan dan tersandung pada hal ini, semoga ini dapat membantu.

Saya juga sedang mencari HashMap dua arah, terkadang itu adalah jawaban paling sederhana yang paling berguna.

Jika Anda tidak ingin menemukan kembali roda dan memilih untuk tidak menambahkan pustaka atau proyek lain ke proyek Anda, bagaimana dengan implementasi sederhana dari array paralel (atau ArrayLists jika desain Anda menuntutnya).

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

Segera setelah Anda mengetahui indeks salah satu dari dua kunci, Anda dapat dengan mudah meminta yang lain. Jadi metode pencarian Anda bisa terlihat seperti:

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

Ini mengasumsikan Anda menggunakan struktur berorientasi objek yang tepat, di mana hanya metode yang memodifikasi array / ArrayLists ini, akan sangat mudah untuk membuatnya tetap paralel. Bahkan lebih mudah untuk ArrayList karena Anda tidak perlu membangun kembali jika ukuran array berubah, selama Anda menambahkan / menghapus secara bersamaan.

ThatOneGuy
sumber
3
Anda kehilangan fitur penting dari HashMaps, yaitu pencarian O (1). Implementasi seperti ini akan memerlukan pemindaian melalui salah satu array sampai Anda menemukan indeks item yang Anda cari, yaitu O (n)
Kip
Ya, itu sangat benar dan merupakan salah satu kelemahan yang cukup besar. Namun dalam situasi pribadi saya, saya sebenarnya berurusan dengan kebutuhan untuk daftar kunci tiga arah dan saya selalu akan tahu setidaknya 1 dari kunci sebelumnya, jadi bagi saya pribadi itu bukan masalah. Terima kasih telah menunjukkan hal itu, saya tampaknya telah melewatkan fakta penting itu dalam posting asli saya.
ThatOneGuy