Java มี HashMap พร้อมการค้นหาแบบย้อนกลับหรือไม่


98

ฉันมีข้อมูลที่จัดระเบียบในรูปแบบ "คีย์ - คีย์" แทนที่จะเป็น "คีย์ - ค่า" มันเหมือน HashMap แต่ฉันจะต้องค้นหา O (1) ทั้งสองทิศทาง มีชื่อโครงสร้างข้อมูลประเภทนี้หรือไม่และสิ่งนี้รวมอยู่ในไลบรารีมาตรฐานของ Java หรือไม่ (หรืออาจจะเป็น Apache Commons?)

ฉันสามารถเขียนคลาสของตัวเองที่โดยทั่วไปใช้แผนที่มิเรอร์สองแผนที่ แต่ฉันไม่อยากสร้างวงล้อใหม่ (ถ้ามีอยู่แล้ว แต่ฉันไม่ได้ค้นหาคำที่ถูกต้อง)

คำตอบ:


106

ไม่มีคลาสดังกล่าวใน Java API ชั้น Apache Commons คุณต้องการจะเป็นหนึ่งของการใช้งานของBidiMap

ในฐานะนักคณิตศาสตร์ฉันจะเรียกโครงสร้างแบบนี้ว่า bijection


83
ในฐานะที่ไม่ใช่นักคณิตศาสตร์ฉันจะเรียกโครงสร้างแบบนี้ว่า "แผนที่ที่ให้คุณค้นหาค่าด้วยคีย์หรือวิธีอื่น ๆ "
Dónal

4
น่าเสียดายที่ไม่มีการสนับสนุนยาชื่อสามัญเหมือนที่ฝรั่งทำ
Eran Medan

2
github.com/megamattron/collections-genericมี BidiMap พร้อมการสนับสนุน Generics
Kenston Choi

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

3
@ Dónalใช่ แต่ไอทีทั้งหมดขึ้นอยู่กับคณิตศาสตร์
Alex

77

นอกเหนือไปจาก Apache คอมมอนส์ฝรั่งยังมีBiMap


ขอบคุณสำหรับข้อมูล! ฉันกำลังติดกับ apache ในขณะนี้ (เว้นแต่จะมีเหตุผลที่ดีที่จะไม่ทำ)
Kip

ฉันไม่สามารถเปรียบเทียบกับคอลเลกชัน apache ได้ดี แต่คอลเล็กชันของ Google มีสิ่งดีๆมากมายที่ฉันคิดว่ามันคุ้มค่าที่จะมองหา
ColinD

16
ข้อดีอย่างหนึ่งของ Google Collections คือมีข้อมูลทั่วไปในขณะที่ Commons Collections ไม่มี
มาร์ค

3
สำหรับการเปรียบเทียบทั้งสอง libs โปรดดูคำพูดในคำตอบนี้: stackoverflow.com/questions/787446/… (และบทสัมภาษณ์ต้นฉบับ) นั่นมีความลำเอียงต่อ Google ด้วยเหตุผลที่ชัดเจน แต่ถึงอย่างนั้นฉันคิดว่ามันปลอดภัยที่จะบอกว่าคุณดีกว่ากับ Google Collections ในปัจจุบัน
Jonik

1
ลิงก์ BiMap เสีย โดยใช้คนนี้
Mahsa2

20

นี่คือคลาสง่ายๆที่ฉันใช้ในการทำสิ่งนี้ (ฉันไม่ต้องการพึ่งพาบุคคลที่สามอีก) ไม่มีคุณลักษณะทั้งหมดที่มีอยู่ใน Maps แต่เป็นการเริ่มต้นที่ดี

    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
@ คิป: ทำไม? ในบางบริบทนี่เป็นวิธีแก้ปัญหาที่ถูกต้องตามกฎหมายอย่างสมบูรณ์ ดังนั้นจะมีสองแผนที่แฮช
Lawrence Dol

7
ไม่มันเป็นการแฮ็กที่น่าเกลียดและเปราะบาง มันต้องการการบำรุงรักษาคุณสมบัติสองทิศทางในทุกๆ get () และ put () และสามารถส่งต่อไปยังวิธีการอื่น ๆ ที่ปรับเปลี่ยนแผนที่โดยไม่ได้รู้เกี่ยวกับคุณสมบัติสองทิศทาง บางทีมันอาจจะโอเคในฐานะตัวแปรโลคัลภายในเมธอดที่ไม่ได้ส่งผ่านไปที่ใดก็ได้หรือถ้ามันถูกทำให้ไม่สามารถแก้ไขได้ทันทีหลังจากสร้าง แต่ถึงอย่างนั้นก็เปราะบาง (มีคนเข้ามาปรับแต่งฟังก์ชันนั้นและแบ่งการทำงานแบบสองทิศทางในลักษณะที่จะไม่แสดงตัวว่าเป็นปัญหาในทันที)
กีบ

1
@Kip ฉันยอมรับว่าควรเก็บการใช้งานดังกล่าวไว้ภายในชั้นเรียนโดยใช้แผนที่นั้น แต่คำพูดสุดท้ายของคุณจะเป็นจริงก็ต่อเมื่อการทดสอบ JUnit ที่เกี่ยวข้องนั้นเลอะเทอะ :-)
rsp

หากฉันสามารถใช้การใช้งานดังกล่าวได้อย่างถูกต้องลองนึกดูว่าจำเป็นต้องมีแผนที่สำหรับการถอดรหัส / เข้ารหัส opcodes ของคำแนะนำภาษาแอสเซมบลีที่นี่คุณจะไม่มีวันเปลี่ยนสถานะของแผนที่เช่นกันในทิศทางเดียวคีย์คือสตริงคำแนะนำของค่าไบนารีอื่น ดังนั้นคุณไม่ควรมีความขัดแย้ง
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];

ทันทีที่คุณทราบดัชนีของ 1 ในสองคีย์คุณสามารถขออีกคีย์ได้อย่างง่ายดาย ดังนั้นวิธีการค้นหาของคุณอาจมีลักษณะดังนี้:

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

นี่คือสมมติว่าคุณกำลังใช้โครงสร้างเชิงวัตถุที่เหมาะสมซึ่งมีเพียงวิธีการเดียวเท่านั้นที่แก้ไขอาร์เรย์ / ArrayLists เหล่านี้มันจะง่ายมากที่จะทำให้มันขนานกัน ง่ายยิ่งขึ้นสำหรับ ArrayList เนื่องจากคุณไม่ต้องสร้างใหม่หากขนาดของอาร์เรย์เปลี่ยนไปตราบใดที่คุณเพิ่ม / ลบควบคู่


3
คุณกำลังสูญเสียคุณสมบัติที่สำคัญของ HashMaps นั่นคือการค้นหา O (1) การใช้งานเช่นนี้จะต้องมีการสแกนผ่านอาร์เรย์ใดอาร์เรย์จนกว่าคุณจะพบดัชนีของรายการที่คุณกำลังมองหาซึ่งก็คือ O (n)
Kip

ใช่นั่นเป็นเรื่องจริงและเป็นข้อเสียเปรียบที่ค่อนข้างใหญ่ อย่างไรก็ตามในสถานการณ์ส่วนตัวของฉันจริงๆแล้วฉันต้องรับมือกับความต้องการรายการคีย์แบบสามทิศทางและฉันมักจะรู้คีย์อย่างน้อย 1 รายการล่วงหน้าดังนั้นสำหรับฉันโดยส่วนตัวแล้วนั่นไม่ใช่ปัญหา ขอบคุณที่ชี้ให้เห็นว่าดูเหมือนว่าฉันจะข้ามข้อเท็จจริงสำคัญนั้นในโพสต์ต้นฉบับของฉันไป
ThatOneGuy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.