[java] 지도에서 고유 한 값으로지도를 생성하고 BinaryOperator를 사용하여 올바른 키를 사용하는 방법은 무엇입니까?

나는지도를 가지고 있으며 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통화 내에서 적절한 조합을 사용하는 솔루션이 있습니까 (예 : 변경 가능한 작업 없음)?



답변

Collectors.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));
}


답변

이것을보십시오 : 간단한 방법은 키와 값의 역수 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));


답변

비 스트림 솔루션이 더 표현력이 좋습니다.

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되며 원래 입력 순서를 유지하는 데 사용 됩니다.


답변

Collectors반환 된 Map을 다시 수집하고 추가 처리 할 필요없이 사용하는 방법을 찾았 습니다. 아이디어는 다음과 같습니다.

  1. 그룹 Map<K, V>Map<V, List<K>.

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

    {사과 = [1, 5, 3], 주황색 = [4, 2]}

  2. 새 키 ( List<K>)를 K사용으로 줄 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
        )
    );

    {사과 = 5, 주황색 = 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 = 사과, 4 = 오렌지}

최종 코드 :

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


답변

“Stream and Collectors.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));


답변