나는 Java를 처음 접했고 종종 Map<Key, Value>
값 을 정렬해야한다는 것을 알았습니다 .
값이 고유하지 때문에, 나 자신이 변환 찾을 keySet
로 array
, 그리고 통해 해당 배열 정렬 배열 정렬 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);
이제 이것은 대부분 작동하지만 다음과 같이 작동합니다.
- 완성 된지도를 완성해야합니다
- 위의 비교기를 시도하지 마십시오
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();
마지막으로 한 번에 한 번만 정렬하지 않고 정렬 된 정보에 계속 액세스해야하는 경우 추가 다중 맵을 사용할 수 있습니다. 자세한 내용이 필요하면 알려주세요 …