카테고리 보관물: Java

Java

값으로 맵 <키, 값> 정렬 처음 접했고 종종 Map<Key, Value>값

나는 Java를 처음 접했고 종종 Map<Key, Value>값 을 정렬해야한다는 것을 알았습니다 .

값이 고유하지 때문에, 나 자신이 변환 찾을 keySetarray, 그리고 통해 해당 배열 정렬 배열 정렬 A를 사용자 정의 비교 값의 종류가 키와 연관된 것으로합니다.

더 쉬운 방법이 있습니까?



답변

일반적인 버전은 다음과 같습니다.

public class MapUtil {
    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
        List<Entry<K, V>> list = new ArrayList<>(map.entrySet());
        list.sort(Entry.comparingByValue());

        Map<K, V> result = new LinkedHashMap<>();
        for (Entry<K, V> entry : list) {
            result.put(entry.getKey(), entry.getValue());
        }

        return result;
    }
}

답변

중요 사항:

이 코드는 여러 가지 방법으로 중단 될 수 있습니다. 제공된 코드를 사용하려면 주석을 읽고 그 의미를 알고 있어야합니다. 예를 들어 키로 더 이상 값을 검색 할 수 없습니다. ( get항상을 반환합니다 null.)


앞서 말한 것보다 훨씬 쉬운 것 같습니다. 다음과 같이 TreeMap을 사용하십시오.

public class Testing {
    public static void main(String[] args) {
        HashMap<String, Double> map = new HashMap<String, Double>();
        ValueComparator bvc = new ValueComparator(map);
        TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc);

        map.put("A", 99.5);
        map.put("B", 67.4);
        map.put("C", 67.4);
        map.put("D", 67.3);

        System.out.println("unsorted map: " + map);
        sorted_map.putAll(map);
        System.out.println("results: " + sorted_map);
    }
}

class ValueComparator implements Comparator<String> {
    Map<String, Double> base;

    public ValueComparator(Map<String, Double> base) {
        this.base = base;
    }

    // Note: this comparator imposes orderings that are inconsistent with
    // equals.
    public int compare(String a, String b) {
        if (base.get(a) >= base.get(b)) {
            return -1;
        } else {
            return 1;
        } // returning 0 would merge keys
    }
}

산출:

unsorted map: {D=67.3, A=99.5, B=67.4, C=67.4}
results: {D=67.3, B=67.4, C=67.4, A=99.5}

답변

Java 8은 새로운 답변을 제공합니다. 항목을 스트림으로 변환하고 Map.Entry의 비교기 결합기를 사용하십시오.

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue());

이를 통해 오름차순으로 정렬 된 항목을 사용할 수 있습니다. 내림차순 값을 원하면 비교기를 반대로 바꾸십시오.

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));

값을 비교할 수없는 경우 명시 적 비교기를 전달할 수 있습니다.

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue(comparator));

그런 다음 다른 스트림 작업을 사용하여 데이터를 소비 할 수 있습니다. 예를 들어, 새지도에서 상위 10 개를 원할 경우 :

Map<K,V> topTen =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
       .limit(10)
       .collect(Collectors.toMap(
          Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

또는 다음으로 인쇄하십시오 System.out.

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue())
   .forEach(System.out::println);

답변

세 줄로 된 답변 …

내가 사용하는 것이 구글 컬렉션 구아바을 이렇게 – 당신의 가치가있는 경우 Comparable다음 사용할 수 있습니다

valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))

그러면지도에 대한 함수 (객체)를 생성하고 [키를 입력으로 가져 와서 각각의 값을 반환] 자연적인 (비교 가능한) 순서를 [값]에 적용합니다.

그들이 비교할 수 없다면, 당신은의 라인을 따라 무언가를해야합니다

valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map)) 

이것들은 Ordering확장 된 TreeMap Comparator이나 정렬 후 LinkedHashMap에 적용될 수 있습니다.

NB : TreeMap을 사용하려는 경우 비교 == 0 인 경우 항목이 이미 목록에 있습니다 (동일하게 비교되는 여러 값이있는 경우 발생 함). 이를 완화하기 위해 키와 값이 같다고 가정하여 키를 비교기에 추가 할 수 있습니다 Comparable.

valueComparator = Ordering.natural().onResultOf(Functions.forMap(map)).compound(Ordering.natural())

= 키로 매핑 된 값에 자연 순서를 적용하고 자연 순서로 키를 조합합니다.

이 여전히 키를 0으로 비교, 그러나 이것은 대부분 충분합니다 경우 작동하지 않습니다 참고 comparable항목 (로 hashCode, equals그리고 compareTo동기화 종종 …)

Ordering.onResultOf ()Functions.forMap ()을 참조하십시오 .

이행

이제 원하는 것을 수행하는 비교기를 얻었으므로 결과를 얻어야합니다.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

이제 이것은 대부분 작동하지만 다음과 같이 작동합니다.

  1. 완성 된지도를 완성해야합니다
  2. 위의 비교기를 시도하지 마십시오 TreeMap. 퍼팅 이후까지 값이 없을 때 삽입 된 키를 비교하려고하는 것은 아무 의미가 없습니다.

포인트 1은 저에게 약간의 거래 차단기입니다. 구글 컬렉션은 엄청나게 게으르다 (좋은 : 즉석에서 거의 모든 작업을 수행 할 수있다; 실제 작업은 결과를 사용하기 시작할 때 수행된다). 그러면 전체 지도를 복사해야한다 !

값으로 “전체”답변 / 실시간 정렬 맵

그래도 걱정하지 마십시오. 이런 방식으로 “라이브”맵을 정렬하는 것에 충분히 집착했다면, 위와 같은 문제 중 하나만 해결할 수 있고 다음과 같은 미쳤던 문제를 해결할 수 있습니다.

참고 : 이것은 2012 년 6 월에 크게 변경되었습니다. 이전 코드는 작동하지 않습니다 TreeMap.get().-> compare()compare()-> 사이에 무한 루프를 만들지 않고 값을 조회하려면 내부 HashMap이 필요합니다.get()

import static org.junit.Assert.assertEquals;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import com.google.common.base.Functions;
import com.google.common.collect.Ordering;

class ValueComparableMap<K extends Comparable<K>,V> extends TreeMap<K,V> {
    //A map for doing lookups on the keys for comparison so we don't get infinite loops
    private final Map<K, V> valueMap;

    ValueComparableMap(final Ordering<? super V> partialValueOrdering) {
        this(partialValueOrdering, new HashMap<K,V>());
    }

    private ValueComparableMap(Ordering<? super V> partialValueOrdering,
            HashMap<K, V> valueMap) {
        super(partialValueOrdering //Apply the value ordering
                .onResultOf(Functions.forMap(valueMap)) //On the result of getting the value for the key from the map
                .compound(Ordering.natural())); //as well as ensuring that the keys don't get clobbered
        this.valueMap = valueMap;
    }

    public V put(K k, V v) {
        if (valueMap.containsKey(k)){
            //remove the key in the sorted set before adding the key again
            remove(k);
        }
        valueMap.put(k,v); //To get "real" unsorted values for the comparator
        return super.put(k, v); //Put it in value order
    }

    public static void main(String[] args){
        TreeMap<String, Integer> map = new ValueComparableMap<String, Integer>(Ordering.natural());
        map.put("a", 5);
        map.put("b", 1);
        map.put("c", 3);
        assertEquals("b",map.firstKey());
        assertEquals("a",map.lastKey());
        map.put("d",0);
        assertEquals("d",map.firstKey());
        //ensure it's still a map (by overwriting a key, but with a new value) 
        map.put("d", 2);
        assertEquals("b", map.firstKey());
        //Ensure multiple values do not clobber keys
        map.put("e", 2);
        assertEquals(5, map.size());
        assertEquals(2, (int) map.get("e"));
        assertEquals(2, (int) map.get("d"));
    }
 }

우리가 넣을 때, 우리는 해시 맵이 비교기에 대한 값을 가지고 있는지 확인한 다음 정렬을 위해 TreeSet에 넣습니다. 그러나 그 전에 해시 맵을 검사하여 키가 실제로 복제본이 아님을 확인합니다. 또한 우리가 만든 비교기에는 키가 포함되므로 중복 값이 ​​중복되지 않은 키를 삭제하지 않습니다 (== 비교로 인해). 이 두 항목은 지도 계약을 유지하는 데 필수적 입니다. 당신이 그것을 원하지 않는다고 생각한다면, 당신은 거의지도를 완전히 뒤집는 지점에 Map<V,K>있습니다.

생성자를 다음과 같이 호출해야합니다.

 new ValueComparableMap(Ordering.natural());
 //or
 new ValueComparableMap(Ordering.from(comparator));

답변

에서 http://www.programmersheaven.com/download/49349/download.aspx

private static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
    List<Entry<K, V>> list = new LinkedList<>(map.entrySet());
    Collections.sort(list, new Comparator<Object>() {
        @SuppressWarnings("unchecked")
        public int compare(Object o1, Object o2) {
            return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
        }
    });

    Map<K, V> result = new LinkedHashMap<>();
    for (Iterator<Entry<K, V>> it = list.iterator(); it.hasNext();) {
        Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
        result.put(entry.getKey(), entry.getValue());
    }

    return result;
}

답변

Java 8에서는 스트림 API 를 사용하여 덜 장황한 방식으로 수행 할 수 있습니다 .

Map<K, V> sortedMap = map.entrySet().stream()
                         .sorted(Entry.comparingByValue())
                         .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

답변

키를 정렬하려면 비교기가 각 비교에 대한 각 값을 찾아야합니다. 더 확장 가능한 솔루션은 entrySet을 직접 사용하므로 각 비교에 대해 값을 즉시 사용할 수 있기 때문에 (숫자별로 백업하지는 않았지만).

다음은 그러한 것들의 일반적인 버전입니다.

public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue(Map<K, V> map) {
    final int size = map.size();
    final List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>(size);
    list.addAll(map.entrySet());
    final ValueComparator<V> cmp = new ValueComparator<V>();
    Collections.sort(list, cmp);
    final List<K> keys = new ArrayList<K>(size);
    for (int i = 0; i < size; i++) {
        keys.set(i, list.get(i).getKey());
    }
    return keys;
}

private static final class ValueComparator<V extends Comparable<? super V>>
                                     implements Comparator<Map.Entry<?, V>> {
    public int compare(Map.Entry<?, V> o1, Map.Entry<?, V> o2) {
        return o1.getValue().compareTo(o2.getValue());
    }
}

위의 솔루션에 대한 메모리 회전을 줄이는 방법이 있습니다. 생성 된 첫 번째 ArrayList는 예를 들어 반환 값으로 재사용 될 수 있습니다. 이것은 일부 제네릭 경고를 억제해야하지만 재사용 가능한 라이브러리 코드에는 가치가 있습니다. 또한 모든 호출에서 비교기를 다시 할당 할 필요는 없습니다.

덜 매력적인 버전이지만 더 효율적입니다.

public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue2(Map<K, V> map) {
    final int size = map.size();
    final List reusedList = new ArrayList(size);
    final List<Map.Entry<K, V>> meView = reusedList;
    meView.addAll(map.entrySet());
    Collections.sort(meView, SINGLE);
    final List<K> keyView = reusedList;
    for (int i = 0; i < size; i++) {
        keyView.set(i, meView.get(i).getKey());
    }
    return keyView;
}

private static final Comparator SINGLE = new ValueComparator();

마지막으로 한 번에 한 번만 정렬하지 않고 정렬 된 정보에 계속 액세스해야하는 경우 추가 다중 맵을 사용할 수 있습니다. 자세한 내용이 필요하면 알려주세요 …