วิธีสร้างแผนที่ด้วยค่าที่แตกต่างจากแผนที่ (และใช้ปุ่มขวาโดยใช้ BinaryOperator)


13

ฉันมีแผนที่Map<K, V>และเป้าหมายของฉันคือการลบค่าที่ซ้ำกันและเอาท์พุทโครงสร้างเดียวกันMap<K, V>อีกครั้ง ในกรณีที่พบค่าที่ซ้ำกันจะต้องมีการเลือกหนึ่งคีย์ ( k) จากสองคีย์ ( k1และk1) ที่เก็บค่าเหล่านี้ด้วยเหตุผลนี้ให้ถือว่ามีการBinaryOperator<K>ให้kจากk1และk2พร้อมใช้งาน

ตัวอย่างอินพุตและเอาต์พุต:

// Input
Map<Integer, String> map = new HashMap<>();
map.put(1, "apple");
map.put(5, "apple");
map.put(4, "orange");
map.put(3, "apple");
map.put(2, "orange");

// Output: {5=apple, 4=orange} // the key is the largest possible

ความพยายามของฉันใช้Stream::collect(Supplier, BiConsumer, BiConsumer)เป็นบิตเงอะงะมากและมีการดำเนินงานที่ไม่แน่นอนเช่นMap::putและMap::removeที่ฉันต้องการจะหลีกเลี่ยง:

// // the key is the largest integer possible (following the example above)
final BinaryOperator<K> reducingKeysBinaryOperator = (k1, k2) -> k1 > k2 ? k1 : k2;

Map<K, V> distinctValuesMap = map.entrySet().stream().collect(
    HashMap::new,                                                              // A new map to return (supplier)
    (map, entry) -> {                                                          // Accumulator
        final K key = entry.getKey();
        final V value = entry.getValue();
        final Entry<K, V> editedEntry = Optional.of(map)                       // New edited Value
            .filter(HashMap::isEmpty)
            .map(m -> new SimpleEntry<>(key, value))                           // If a first entry, use it
            .orElseGet(() -> map.entrySet()                                    // otherwise check for a duplicate
                    .stream() 
                    .filter(e -> value.equals(e.getValue()))
                    .findFirst()
                    .map(e -> new SimpleEntry<>(                               // .. if found, replace
                            reducingKeysBinaryOperator.apply(e.getKey(), key), 
                            map.remove(e.getKey())))
                    .orElse(new SimpleEntry<>(key, value)));                   // .. or else leave
        map.put(editedEntry.getKey(), editedEntry.getValue());                 // put it to the map
    },
    (m1, m2) -> {}                                                             // Combiner
);

มีวิธีการแก้ปัญหาโดยใช้ชุดค่าผสมที่เหมาะสมของการโทรCollectorsภายในครั้งเดียวStream::collect(เช่นไม่มีการดำเนินการที่ไม่แน่นอน)?


2
การวัดของคุณสำหรับ " ดีกว่า " หรือ " ดีที่สุด " คืออะไร? ต้องทำผ่านStreams หรือไม่?
Turing85

หากค่าเดียวกันเชื่อมโยงกับ 2 คีย์คุณจะเลือกคีย์ที่ถูกเก็บรักษาไว้อย่างไร
Michael

ผลลัพธ์ที่คาดหวังในกรณีของคุณคืออะไร?
YCF_L

1
@ Turing85: ตามที่ฉันพูด ดีกว่าหรือดีที่สุดจะไม่มีการใช้งานที่ชัดเจนของวิธีการแผนที่ไม่แน่นอนเช่นMap::putหรือภายในMap::remove Collector
นิโคลัส

1
BiMapมันคุ้มค่าการดูที่ อาจซ้ำซ้อนกับลบค่าที่ซ้ำกันออกจาก HashMap ใน Java
Naman

คำตอบ:


12

คุณสามารถใช้Collector.toMap

private Map<Integer, String> deduplicateValues(Map<Integer, String> map) {
    Map<String, Integer> inverse = map.entrySet().stream().collect(toMap(
            Map.Entry::getValue,
            Map.Entry::getKey,
            Math::max) // take the highest key on duplicate values
    );

    return inverse.entrySet().stream().collect(toMap(Map.Entry::getValue, Map.Entry::getKey));
}

9

ลองสิ่งนี้: วิธีที่ง่ายคือการสลับคีย์และค่าจากนั้นใช้ตัวtoMap()สะสมที่มีฟังก์ชันผสาน

map.entrySet().stream()
        .map(entry -> new AbstractMap.SimpleEntry<>(entry.getValue(), entry.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, reducingKeysBinaryOperator));

Map<K, V> output = map.entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, reducingKeysBinaryOperator))
        .entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));

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

3
@GPI และ Michael นี่เป็นเพราะเขาต้องรวมกุญแจดังนั้นการกลับคู่จะรวมกุญแจ สิ่งที่ขาดหายไปคือการผกผันครั้งที่สอง
Jean-Baptiste Yunès

2
@HadiJ ไม่! การผกผันนั้นถูกต้อง! แต่สิ่งที่สองจำเป็นต้องได้รับกลับ การผสานถูกใช้เพื่อรวมกุญแจ แต่การรวมกันเป็นไปได้สำหรับค่าเท่านั้น ...
Jean-Baptiste Yunès

@ Jean-BaptisteYunèsฉันเข้าใจถึงความจำเป็นที่จะผสาน แต่ทำไมผมไม่ได้รับทันทีคือเหตุผลที่คุณรหัสแทนswap(); collect(key, value, binOp); collect(value, key, binOp)บางทีฉันอาจต้องการลองใช้ jshell ด้วยตัวเอง?
GPI

2
เอาเสรีภาพในการใช้ตัวแปรท้องถิ่นที่นำมาใช้ในคำถามในรหัสที่คุณแบ่งปัน อย่าเปลี่ยนกลับในกรณีที่ขัดแย้งกับความตั้งใจในขณะที่คุณตอบคำถาม
Naman

4

ฉันพบโซลูชันที่ไม่ใช่สตรีมที่แสดงออกได้มากกว่านี้:

BinaryOperator<K> reducingKeysBinaryOperator = (k1, k2) -> k1 > k2 ? k1 : k2;

Map<V, K> reverse = new LinkedHashMap<>(map.size());
map.forEach((k, v) -> reverse.merge(v, k, reducingKeysBinaryOperator));

Map<K, V> result = new LinkedHashMap<>(reverse.size());
reverse.forEach((v, k) -> result.put(k, v));

สิ่งนี้ใช้Map.mergeกับฟังก์ชั่นลดขนาดสองชั้นของคุณและใช้LinkedHashMapเพื่อรักษาลำดับรายการ


2
ใช่ฉันได้สรุปวิธีนี้ (คล้ายกัน) แล้ว อย่างไรก็ตามฉันกำลังมองหาวิธีjava-streamเนื่องจากเป็นวิธีที่เปิดเผยมากขึ้น รับ +1 ของฉัน
Nikolas

1

ฉันพบวิธีการใช้โดยCollectorsไม่จำเป็นต้องรวบรวมและประมวลผลแผนที่ที่ส่งคืนอีกต่อไป ความคิดคือ:

  1. กลุ่ม บริษัทจะMap<K, V>Map<V, List<K>

    Map<K, V> distinctValuesMap = this.stream.collect(
        Collectors.collectingAndThen(
            Collectors.groupingBy(Entry::getValue),
            groupingDownstream 
        )
    );

    {apple = [1, 5, 3], orange = [4, 2]}

  2. ลดคีย์ใหม่ ( List<K>) เพื่อใช้KBinaryOperator<K>

    Function<Entry<V, List<Entry<K, V>>>, K> keyMapFunction = e -> e.getValue().stream()
        .map(Entry::getKey)
        .collect(Collectors.collectingAndThen(
            Collectors.reducing(reducingKeysBinaryOperator),
            Optional::get
        )
    );

    {apple = 5, orange = 4}

  3. ผกผันMap<V, K>กลับไปที่Map<K, V>โครงสร้างอีกครั้ง - ซึ่งปลอดภัยเนื่องจากทั้งคีย์และค่าต่าง ๆ นั้นมีการรับประกันว่าแตกต่างกัน

    Function<Map<V, List<Entry<K,V>>>, Map<K, V>> groupingDownstream = m -> m.entrySet()
        .stream()
        .collect(Collectors.toMap(
            keyMapFunction,
            Entry::getKey
        )
    );

    {5 = apple, 4 = orange}

รหัสสุดท้าย:

final BinaryOperator<K> reducingKeysBinaryOperator = ...

final Map<K, V> distinctValuesMap = map.entrySet().stream().collect(
        Collectors.collectingAndThen(
            Collectors.groupingBy(Entry::getValue),
            m -> m.entrySet().stream().collect(
                Collectors.toMap(
                    e -> e.getValue().stream().map(Entry::getKey).collect(
                        Collectors.collectingAndThen(
                            Collectors.reducing(reducingKeysBinaryOperator),
                            Optional::get
                        )
                    ),
                    Entry::getKey
                )
            )
        )
    );

1

Approch อื่นเพื่อรับผลลัพธ์ที่ต้องการด้วย "สตรีมและ Collector.groupingBy"

    map = map.entrySet().stream()
    .collect(Collectors.groupingBy(
            Entry::getValue,
            Collectors.maxBy(Comparator.comparing(Entry::getKey))
            )
    )
    .entrySet().stream()
    .collect(Collectors.toMap(
            k -> {
                return k.getValue().get().getKey();
            }, 
            Entry::getKey));
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.