지도에서 고유 한 값으로지도를 생성하고 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::removeCollector.
니콜라스

1
살펴볼 가치가 BiMap있습니다. 아마도 Java의 HashMap에서 중복 값 제거
Naman

답변:


12

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

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은 키를 병합해야하기 때문에 쌍을 반전하면 키가 병합됩니다. 누락 된 것은 두 번째 반전입니다.
장 밥 티스트 Yunès

2
@HadiJ 아니! 반전이 맞았다! 그러나 다시 돌아가려면 두 번째 것이 필요했습니다. 병합은 키를 병합하는 데 사용되지만 병합은 값에만 가능합니다.
Jean-Baptiste Yunès

@ Jean-BaptisteYunès 나는 병합의 필요성을 이해하지만, 즉시 얻을 수없는 이유는 swap(); collect(key, value, binOp);대신에 코드를 작성 하는 이유 입니다 collect(value, key, binOp). 어쩌면 진짜 jshell에서 이것을 시도해야합니까?
GPI

2
코드 공유에서 질문에 소개 된 로컬 변수를 자유롭게 사용할 수있었습니다. 답변을하는 동안 의도와 상충되는 경우 되 돌리십시오.
나만

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
예,이 (유사한) 해결책을 결론지었습니다. 그러나 자바 스트림 방식은 더 선언적인 방식이므로 찾고 있습니다. 내 +1
Nikolas

1

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

1

"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));
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.