การใช้งานแผนที่ด้วยคีย์ที่ซ้ำกัน


116

ฉันต้องการมีแผนที่ที่มีคีย์ซ้ำกัน

ฉันรู้ว่ามีการใช้งานแผนที่มากมาย (Eclipse แสดงให้ฉันเห็นประมาณ 50) ดังนั้นฉันพนันได้เลยว่าจะต้องมีอย่างใดอย่างหนึ่งที่อนุญาตสิ่งนี้ ฉันรู้ว่ามันง่ายมากที่จะเขียนแผนที่ของคุณเองที่ทำสิ่งนี้ แต่ฉันอยากจะใช้วิธีแก้ปัญหาที่มีอยู่

อาจจะมีบางอย่างในคอลเลกชันทั่วไปหรือ Google คอลเลคชัน?


4
วิธีนี้ควรทำงานอย่างไร? หากคุณขอค่าที่เชื่อมโยงกับคีย์และคีย์นี้มีอยู่หลายครั้งในแผนที่ควรส่งคืนค่าใด
Mnementh

ได้รับสามารถโยนข้อยกเว้นฉันต้องการแผนที่นี้สำหรับการทำซ้ำเท่านั้น
IAdapter

6
หากคุณต้องการเพียงแค่การทำซ้ำทำไมคุณถึงต้องมีแผนที่ตั้งแต่แรก? ใช้รายชื่อคู่หรืออะไร ...
Tal Pressman

2
เนื่องจากโปรแกรมทั้งหมดของฉันทำงานร่วมกับแผนที่แล้วและตอนนี้ฉันค้นพบว่าข้อมูลมีคีย์ซ้ำกันได้ หากใช้แผนที่วิธีอื่นจะผิดมากเราจะมีการใช้งานแผนที่เพียง 5 ครั้งเท่านั้นไม่ใช่ 50+
IAdapter

คำตอบ:


90

คุณกำลังค้นหามัลติแมปและแท้จริงแล้วทั้งคอลเลกชันคอมมอนส์และ Guava มีการใช้งานหลายอย่าง มัลติแมปอนุญาตให้มีหลายคีย์โดยการรักษาคอลเลคชันของค่าต่อคีย์กล่าวคือคุณสามารถใส่อ็อบเจ็กต์เดียวลงในแผนที่ แต่คุณจะเรียกคอลเล็กชัน

ถ้าคุณสามารถใช้ Java 5 ได้ฉันจะชอบของ Guava มากกว่าMultimapเพราะมันเป็นเรื่องทั่วไป


3
นอกจากนี้ Multimap นี้ไม่ได้แสร้งทำเป็นแผนที่แบบที่ apache ทำ
Kevin Bourrillion

7
โปรดทราบว่า Google Collections ถูกแทนที่โดย Guava ดังนั้นนี่คือลิงก์ไปยัง MultiMap เวอร์ชัน Guava: code.google.com/p/guava-libraries/wiki/…
Josh Glover

อย่างไรก็ตาม Multimap ไม่สามารถทำให้เป็นอนุกรมได้อย่างสมบูรณ์มีสมาชิกชั่วคราวซึ่งทำให้อินสแตนซ์ deserialized ไร้ประโยชน์
dschulten

@dschulten Multimap เป็นอินเทอร์เฟซและคุณไม่ได้ระบุว่าคุณหมายถึงการนำไปใช้งานใด com.google.common.collect.HashMultimapมีreadObject/ writeObjectวิธีการเช่นเดียวกับ ArrayListMultimap และไม่เปลี่ยนรูป {List, Set} Multimap ฉันจะพิจารณาว่าอินสแตนซ์ deserialized ที่ไร้ประโยชน์เป็นข้อผิดพลาดที่ควรค่าแก่การรายงาน
.

1
Apache Collections 4.0 รองรับ generics commons.apache.org/proper/commons-collections/javadocs/…
kervin

35

เราไม่จำเป็นต้องพึ่งพาไลบรารีภายนอกของ Google Collections คุณสามารถใช้แผนที่ต่อไปนี้:

Map<String, ArrayList<String>> hashMap = new HashMap<String, ArrayList>();

public static void main(String... arg) {
   // Add data with duplicate keys
   addValues("A", "a1");
   addValues("A", "a2");
   addValues("B", "b");
   // View data.
   Iterator it = hashMap.keySet().iterator();
   ArrayList tempList = null;

   while (it.hasNext()) {
      String key = it.next().toString();             
      tempList = hashMap.get(key);
      if (tempList != null) {
         for (String value: tempList) {
            System.out.println("Key : "+key+ " , Value : "+value);
         }
      }
   }
}

private void addValues(String key, String value) {
   ArrayList tempList = null;
   if (hashMap.containsKey(key)) {
      tempList = hashMap.get(key);
      if(tempList == null)
         tempList = new ArrayList();
      tempList.add(value);  
   } else {
      tempList = new ArrayList();
      tempList.add(value);               
   }
   hashMap.put(key,tempList);
}

โปรดตรวจสอบให้แน่ใจว่าได้ปรับแต่งโค้ดแล้ว


14
คุณไม่จำเป็นต้องพึ่งพา Multimap ของ Guava แน่นอน มันทำให้ชีวิตของคุณง่ายขึ้นโดยที่คุณไม่ต้องปรับใช้ใหม่ทดสอบพวกมัน ฯลฯ
ฟีลโฮ

สิ่งนี้ไม่อนุญาตให้ทำซ้ำได้อย่างราบรื่นในทุกคู่ ย่อมมีข้อบกพร่องมากขึ้น ฉันกำลังจะแนะนำวิธีแก้ปัญหาของฉันซึ่งต้องใช้คลาสพิเศษหนึ่งคลาสจากนั้นเห็นคำตอบของ @Mnementh เป็นเพียงนั้น
Mark Jeronimus

2
การเขียนโค้ดพื้นฐานไม่ใช่ว่าจะฉลาดเสมอไป Google มีแนวโน้มที่จะมีการทดสอบที่ดีขึ้น
senseiwu

27
Multimap<Integer, String> multimap = ArrayListMultimap.create();

multimap.put(1, "A");
multimap.put(1, "B");
multimap.put(1, "C");
multimap.put(1, "A");

multimap.put(2, "A");
multimap.put(2, "B");
multimap.put(2, "C");

multimap.put(3, "A");

System.out.println(multimap.get(1));
System.out.println(multimap.get(2));       
System.out.println(multimap.get(3));

ผลลัพธ์คือ:

[A,B,C,A]
[A,B,C]
[A]

หมายเหตุ:เราจำเป็นต้องนำเข้าไฟล์ไลบรารี

http://www.java2s.com/Code/Jar/g/Downloadgooglecollectionsjar.htm

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

หรือhttps://commons.apache.org/proper/commons-collections/download_collections.cgi

import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;

2
ข้อเสนอแนะที่ดีเนื่องจากฉันใช้ Spring ในโครงการของฉันฉันจึงได้ใช้ MultiValueMap รสชาติฤดูใบไม้ผลิตามที่กล่าวไว้ในเอกสารhttp://docs.spring.io/spring-framework/docs/current/javadoc-api/org/ springframework / util / MultiValueMap.html
ajup

18

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

คุณสามารถใช้MultiMapได้แม้ว่าฉันจะไม่ชอบความคิดที่จะทำคีย์ซ้ำด้วยตัวเองก็ตาม


ขอบคุณ! การใช้TreeMap<String, ArrayList<MyClass>>แก้ไขความต้องการคีย์ที่ซ้ำกันของฉัน
โจ

10

หากคุณต้องการวนซ้ำเกี่ยวกับรายการคู่คีย์ - ค่า - (ตามที่คุณเขียนไว้ในความคิดเห็น) รายการหรืออาร์เรย์ควรจะดีกว่า ขั้นแรกรวมคีย์และค่าของคุณ:

public class Pair
{
   public Class1 key;
   public Class2 value;

   public Pair(Class1 key, Class2 value)
   {
      this.key = key;
      this.value = value;
   }

}

แทนที่ Class1 และ Class2 ด้วยประเภทที่คุณต้องการใช้สำหรับคีย์และค่า

ตอนนี้คุณสามารถใส่ไว้ในอาร์เรย์หรือรายการและทำซ้ำได้:

Pair[] pairs = new Pair[10];
...
for (Pair pair : pairs)
{
   ...
}

ฉันจะใช้ add () หรือใส่ () ได้อย่างไร ฉันไม่ต้องการฮาร์ดคอร์จำนวนมิติ
Amit Kumar Gupta

2
ในกรณีนี้ให้ใช้รายการ ตัวอย่างที่สองเปลี่ยนเป็น List <Pair> pair = new List <Pair> (); for-loop ยังคงเหมือนเดิม คุณสามารถเพิ่มคู่ด้วยคำสั่งนี้: pairs.add (คู่);
Mnementh

นี่อาจเป็นคำตอบที่ดีที่สุดสำหรับความซื่อสัตย์
PaulBGD

6

List<Map.Entry<K,V>>ปัญหานี้สามารถแก้ไขได้ด้วยรายชื่อของรายการแผนที่ เราไม่จำเป็นต้องใช้ทั้งไลบรารีภายนอกหรือการใช้งานแผนที่ใหม่ รายการแผนที่สามารถสร้างได้ดังนี้: Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);



3

เรียนรู้จากข้อผิดพลาดของฉัน ... โปรดอย่าใช้สิ่งนี้ด้วยตัวคุณเอง Guava multimap เป็นวิธีที่จะไป

การปรับปรุงทั่วไปที่จำเป็นในมัลติแมปคือการไม่อนุญาตให้มีคู่คีย์ - ค่าที่ซ้ำกัน

การปรับใช้ / เปลี่ยนแปลงสิ่งนี้ในการใช้งานของคุณอาจเป็นเรื่องที่น่ารำคาญ

ในฝรั่งมันง่ายเหมือน:

HashMultimap<String, Integer> no_dupe_key_plus_val = HashMultimap.create();

ArrayListMultimap<String, Integer> allow_dupe_key_plus_val = ArrayListMultimap.create();

2

ฉันมีปัญหาที่แตกต่างกันเล็กน้อย: จำเป็นต้องเชื่อมโยงค่าที่แตกต่างกันสองค่ากับคีย์เดียวกัน เพียงโพสต์ไว้ที่นี่เผื่อว่าจะช่วยผู้อื่นได้ฉันได้แนะนำ HashMap เป็นค่า:

/* @param frameTypeHash: Key -> Integer (frameID), Value -> HashMap (innerMap)
   @param innerMap: Key -> String (extIP), Value -> String
   If the key exists, retrieve the stored HashMap innerMap 
   and put the constructed key, value pair
*/
  if (frameTypeHash.containsKey(frameID)){
            //Key exists, add the key/value to innerHashMap
            HashMap innerMap = (HashMap)frameTypeHash.get(frameID);
            innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);

        } else {
            HashMap<String, String> innerMap = new HashMap<String, String>();
            innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);
            // This means the key doesn't exists, adding it for the first time
            frameTypeHash.put(frameID, innerMap );
        }
}

ในโค้ดด้านบนคีย์ frameID ถูกอ่านจากสตริงแรกของไฟล์อินพุตในแต่ละบรรทัดค่าสำหรับ frameTypeHash ถูกสร้างขึ้นโดยการแยกบรรทัดที่เหลือและถูกจัดเก็บเป็นอ็อบเจกต์ String ในช่วงระยะเวลาหนึ่งไฟล์เริ่มมีหลายบรรทัด ( ที่มีค่าต่างกัน) ที่เชื่อมโยงกับคีย์ frameID เดียวกันดังนั้น frameTypeHash จึงถูกเขียนทับด้วยบรรทัดสุดท้ายเป็นค่า ฉันแทนที่วัตถุ String ด้วยวัตถุ HashMap อื่นเป็นช่องค่าซึ่งช่วยในการรักษาคีย์เดียวในการแมปค่าที่แตกต่างกัน


2

ไม่จำเป็นต้องมี libs แฟนซี แผนที่ถูกกำหนดโดยคีย์ที่ไม่ซ้ำกันดังนั้นอย่าโค้งงอให้ใช้รายการ สตรีมมีพลังมาก

import java.util.AbstractMap.SimpleImmutableEntry;

List<SimpleImmutableEntry<String, String>> nameToLocationMap = Arrays.asList(
    new SimpleImmutableEntry<>("A", "A1"),
    new SimpleImmutableEntry<>("A", "A2"),
    new SimpleImmutableEntry<>("B", "B1"),
    new SimpleImmutableEntry<>("B", "B1"),
);

และนั่นก็คือ ตัวอย่างการใช้งาน:

List<String> allBsLocations = nameToLocationMap.stream()
        .filter(x -> x.getKey().equals("B"))
        .map(x -> x.getValue())
        .collect(Collectors.toList());

nameToLocationMap.stream().forEach(x -> 
do stuff with: x.getKey()...x.getValue()...

1
class  DuplicateMap<K, V> 
{
    enum MapType
    {
        Hash,LinkedHash
    }

    int HashCode = 0;
    Map<Key<K>,V> map = null;

    DuplicateMap()
    {
        map = new HashMap<Key<K>,V>();
    }

    DuplicateMap( MapType maptype )
    {
        if ( maptype == MapType.Hash ) {
            map = new HashMap<Key<K>,V>();
        }
        else if ( maptype == MapType.LinkedHash ) {
            map = new LinkedHashMap<Key<K>,V>();
        }
        else
            map = new HashMap<Key<K>,V>();
    }

    V put( K key, V value  )
    {

        return map.put( new Key<K>( key , HashCode++ ), value );
    }

    void putAll( Map<K, V> map1 )
    {
        Map<Key<K>,V> map2 = new LinkedHashMap<Key<K>,V>();

        for ( Entry<K, V> entry : map1.entrySet() ) {
            map2.put( new Key<K>( entry.getKey() , HashCode++ ), entry.getValue());
        }
        map.putAll(map2);
    }

    Set<Entry<K, V>> entrySet()
    {
        Set<Entry<K, V>> entry = new LinkedHashSet<Map.Entry<K,V>>();
        for ( final Entry<Key<K>, V> entry1 : map.entrySet() ) {
            entry.add( new Entry<K, V>(){
                private K Key = entry1.getKey().Key();
                private V Value = entry1.getValue();

                @Override
                public K getKey() {
                    return Key;
                }

                @Override
                public V getValue() {
                    return Value;
                }

                @Override
                public V setValue(V value) {
                    return null;
                }});
        }

        return entry;
    }

    @Override
    public String toString() {
        StringBuilder builder = new  StringBuilder();
        builder.append("{");
        boolean FirstIteration = true;
        for ( Entry<K, V> entry : entrySet() ) {
            builder.append( ( (FirstIteration)? "" : "," ) + ((entry.getKey()==null) ? null :entry.getKey().toString() ) + "=" + ((entry.getValue()==null) ? null :entry.getValue().toString() )  );
            FirstIteration = false;
        }
        builder.append("}");
        return builder.toString();
    }

    class Key<K1>
    {
        K1 Key;
        int HashCode;

        public Key(K1 key, int hashCode) {
            super();
            Key = key;
            HashCode = hashCode;
        }

        public K1 Key() {
            return Key;
        }

        @Override
        public String toString() {
            return  Key.toString() ;
        }

        @Override
        public int hashCode() {

            return HashCode;
        }
    }

ขอบคุณ @daspilker. ตอนนี้ฉันเห็นการแก้ไขของคุณเท่านั้น อยากเห็นใครบางคนพบว่าตัวอย่างข้อมูลของฉันมีค่าหากมีการแก้ไข
Gabrial David

1
 1, Map<String, List<String>> map = new HashMap<>();

โซลูชัน verbose นี้มีข้อบกพร่องหลายประการและมีแนวโน้มที่จะเกิดข้อผิดพลาด หมายความว่าเราต้องสร้างอินสแตนซ์คอลเลคชันสำหรับทุกค่าตรวจสอบการมีอยู่ก่อนเพิ่มหรือลบค่าลบด้วยตนเองเมื่อไม่มีค่าเหลืออยู่ ฯลฯ

2, org.apache.commons.collections4.MultiMap interface
3, com.google.common.collect.Multimap interface 

Java แผนที่ที่ซ้ำกันคีย์


1

แล้ว MultiMap จะเป็นอย่างไร?

public class MultiMap<K, V> extends HashMap<K, Set<V>> {
  private static final long serialVersionUID = 1L;
  private Map<K, Set<V>> innerMap = new HashMap<>();

  public Set<V> put(K key, V value) {
    Set<V> valuesOld = this.innerMap.get(key);
    HashSet<V> valuesNewTotal = new HashSet<>();
    if (valuesOld != null) {
      valuesNewTotal.addAll(valuesOld);
    }
    valuesNewTotal.add(value);
    this.innerMap.put(key, valuesNewTotal);
    return valuesOld;
  }

  public void putAll(K key, Set<V> values) {
    for (V value : values) {
      put(key, value);
    }
  }

  @Override
  public Set<V> put(K key, Set<V> value) {
    Set<V> valuesOld = this.innerMap.get(key);
    putAll(key, value);
    return valuesOld;
  }

  @Override
  public void putAll(Map<? extends K, ? extends Set<V>> mapOfValues) {
    for (Map.Entry<? extends K, ? extends Set<V>> valueEntry : mapOfValues.entrySet()) {
      K key = valueEntry.getKey();
      Set<V> value = valueEntry.getValue();
      putAll(key, value);
    }
  }

  @Override
  public Set<V> putIfAbsent(K key, Set<V> value) {
    Set<V> valueOld = this.innerMap.get(key);
    if (valueOld == null) {
      putAll(key, value);
    }
    return valueOld;
  }

  @Override
  public Set<V> get(Object key) {
    return this.innerMap.get(key);
  }

  @Override
  etc. etc. override all public methods size(), clear() .....

}

0

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

หมายเหตุ: คุณต้องใช้ฟังก์ชันลดการชนกันดังนั้นคีย์ที่ชนกันจะถูกแปลงเป็นชุดเฉพาะ "always" อะไรง่ายๆเช่นการต่อท้ายคีย์ด้วยแฮชโค้ดของวัตถุหรืออะไร?


1
บริบทคือฉันคิดว่าคีย์จะไม่ซ้ำกัน แต่ปรากฎว่าอาจไม่ใช่ ฉันไม่ต้องการ refactor ทุกอย่างเพราะมันจะไม่ถูกใช้บ่อย
IAdapter

ฉันต้องการแปลงไฟล์ XML ขนาดเล็กเป็นแฮชแมปเช่นประเภทข้อมูล เฉพาะปัญหาคือโครงสร้างของไฟล์ XML ไม่ได้รับการแก้ไข
Amit Kumar Gupta

0

เพียงเพื่อให้เสร็จสมบูรณ์ Apache คอมมอนส์คอลเลกชันนี้ยังมีMultimap ข้อเสียของหลักสูตรคือ Apache Commons ไม่ใช้ Generics


1
โปรดทราบว่า MultiMap ของพวกเขาใช้แผนที่ แต่ผิดสัญญาของวิธีการแผนที่ นั่นมันโรคจิตฉัน
Kevin Bourrillion

0

ด้วยการแฮ็กเล็กน้อยคุณสามารถใช้ HashSet กับคีย์ที่ซ้ำกันได้ คำเตือน: นี่ขึ้นอยู่กับการใช้งาน HashSet เป็นอย่างมาก

class MultiKeyPair {
    Object key;
    Object value;

    public MultiKeyPair(Object key, Object value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public int hashCode() {
        return key.hashCode();
    }
}

class MultiKeyList extends MultiKeyPair {
    ArrayList<MultiKeyPair> list = new ArrayList<MultiKeyPair>();

    public MultiKeyList(Object key) {
        super(key, null);
    }

    @Override
    public boolean equals(Object obj) {
        list.add((MultiKeyPair) obj);
        return false;
    }
}

public static void main(String[] args) {
    HashSet<MultiKeyPair> set = new HashSet<MultiKeyPair>();
    set.add(new MultiKeyPair("A","a1"));
    set.add(new MultiKeyPair("A","a2"));
    set.add(new MultiKeyPair("B","b1"));
    set.add(new MultiKeyPair("A","a3"));

    MultiKeyList o = new MultiKeyList("A");
    set.contains(o);

    for (MultiKeyPair pair : o.list) {
        System.out.println(pair.value);
    }
}

0

หากมีคีย์ที่ซ้ำกันคีย์อาจสอดคล้องกับมากกว่าหนึ่งค่า วิธีแก้ปัญหาที่ชัดเจนคือการแมปคีย์กับรายการค่าเหล่านี้

ตัวอย่างเช่นใน Python:

map = dict()
map["driver"] = list()
map["driver"].append("john")
map["driver"].append("mike")
print map["driver"]          # It shows john and mike
print map["driver"][0]       # It shows john
print map["driver"][1]       # It shows mike

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