Làm cách nào để tạo bản đồ với các giá trị riêng biệt từ bản đồ (và sử dụng khóa bên phải bằng BinaryOperator)?


13

Tôi có một bản đồ Map<K, V>và mục tiêu của tôi là loại bỏ các giá trị trùng lặp và xuất Map<K, V>lại cấu trúc rất giống nhau . Trong trường hợp giá trị trùng lặp được tìm thấy, có phải được lựa chọn một chìa khóa ( k) từ hai phím ( k1k1) mà giữ những giá trị này, vì lý do này, giả định BinaryOperator<K>đưa ra ktừ k1k2có sẵn.

Ví dụ đầu vào và đầu ra:

// 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

Nỗ lực của tôi sử dụng Stream::collect(Supplier, BiConsumer, BiConsumer)một chút rất vụng về và chứa các hoạt động có thể thay đổi như Map::putMap::removetôi muốn tránh:

// // 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
);

Có giải pháp nào sử dụng kết hợp thích hợp Collectorstrong một Stream::collectcuộc gọi (ví dụ: không có hoạt động có thể thay đổi) không?


2
Số liệu của bạn cho " tốt hơn " hay " tốt nhất " là gì? Tôi phải được thực hiện thông qua Streams?
Turing85

Nếu cùng một giá trị được liên kết với 2 khóa, làm thế nào để bạn chọn khóa nào được giữ lại?
Michael

Sản lượng dự kiến ​​trong trường hợp của bạn là gì?
YCF_L

1
@ Turing85: Như tôi đã nói. Các tốt hơn hoặc tốt nhất sẽ không có sử dụng rõ ràng của phương pháp bản đồ có thể thay đổi như Map::puthay Map::removetrong Collector.
Nikolas

1
Thật đáng để xem qua BiMap. Có thể là một bản sao của Xóa các giá trị trùng lặp khỏi HashMap trong Java
Naman

Câu trả lời:


12

Bạn có thể sử dụng 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

Hãy thử điều này: Cách đơn giản là nghịch đảo khóa và giá trị sau đó sử dụng toMap()collector với chức năng hợp nhất.

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
Tôi không thấy những gì maphoạt động trung gian mua. Bạn dường như trao đổi các khóa và giá trị, điều đó rất rõ ràng, nhưng vấn đề là gì, bạn có thể làm điều đó ở bước thu thập giống nhau không?
GPI

3
@GPI và Michael, điều này là do anh ta phải hợp nhất các khóa, vì vậy đảo ngược các cặp sẽ hợp nhất các khóa. Điều còn thiếu là đảo ngược thứ hai rồi.
Jean-Baptiste Yunès

2
@HadiJ Không! Nghịch đảo đã đúng! nhưng cái thứ hai là cần thiết để lấy lại. Hợp nhất được sử dụng để hợp nhất các khóa, nhưng việc hợp nhất chỉ có thể cho các giá trị ...
Jean-Baptiste Yunès

@ Jean-BaptisteYunès Tôi hiểu sự cần thiết phải hợp nhất, nhưng tại sao tôi không nhận được ngay lập tức là lý do tại sao bạn viết mã swap(); collect(key, value, binOp);thay vì collect(value, key, binOp). Có lẽ tôi cần phải thử điều này trong một jshell cho thật?
GPI

2
Lấy tự do để sử dụng biến cục bộ được giới thiệu trong câu hỏi trong mã được chia sẻ bởi bạn. Đừng hoàn nguyên trong trường hợp nó mâu thuẫn với ý định trong khi bạn đang đưa ra câu trả lời.
Naman

4

Tôi thấy giải pháp non-stream biểu cảm hơn:

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

Điều này sử dụng Map.mergevới chức năng bi giảm của bạn và sử dụng LinkedHashMapđể duy trì thứ tự mục gốc.


2
Vâng, tôi đã kết luận giải pháp (tương tự) này. Tuy nhiên, tôi đang tìm kiếm cách tiếp cận java-stream , vì đó là cách khai báo nhiều hơn. Có +1 của tôi
Nikolas

1

Tôi tìm thấy một cách chỉ sử dụng Collectors không cần thu thập và xử lý thêm Bản đồ được trả lại. Ý tưởng là:

  1. Tập đoàn Map<K, V>đểMap<V, List<K> .

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

    {táo = [1, 5, 3], cam = [4, 2]}

  2. Giảm các phím mới ( List<K>) để Ksử dụng BinaryOperator<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
        )
    );

    {táo = 5, cam = 4}

  3. Đảo ngược Map<V, K>lại Map<K, V>cấu trúc một lần nữa - an toàn vì cả khóa và giá trị đều được đảm bảo là khác biệt.

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

    {5 = táo, 4 = cam}

Mã cuối cùng:

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

Một sự chấp thuận khác để có được kết quả mong muốn với "Stream và 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));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.