저는 Java를 처음 접했고 종종 Map<Key, Value>
를 정렬해야 한다는 사실을 알게 되었습니다.
값이 고유하지 않기 때문에 keySet
을 array
로 변환하고 키와 연결된 값을 정렬 하는 사용자 지정 비교기 를 사용하여 배열 정렬 을 통해 해당 배열을 정렬합니다.
더 쉬운 방법이 있습니까?
질문자 :Community Wiki
저는 Java를 처음 접했고 종종 Map<Key, Value>
를 정렬해야 한다는 사실을 알게 되었습니다.
값이 고유하지 않기 때문에 keySet
을 array
로 변환하고 키와 연결된 값을 정렬 하는 사용자 지정 비교기 를 사용하여 배열 정렬 을 통해 해당 배열을 정렬합니다.
더 쉬운 방법이 있습니까?
다음은 일반 친화적인 버전입니다.
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);
3개의 1줄 답변...
Google Collections Guava 를 사용하여 이 작업을 수행합니다. 값이 Comparable
이면 다음을 사용할 수 있습니다.
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))
이는 맵을 위한 함수(객체)를 생성하고 [키 중 하나를 입력으로 사용하여 해당 값을 반환하는] 그런 다음 [값]에 자연적인(비교 가능한) 순서를 적용합니다.
비교할 수 없는 경우 다음과 같은 조치를 취해야 합니다.
valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map))
이것들은 TreeMap( Ordering
Comparator
확장함에 따라) 또는 정렬 후 LinkedHashMap에 적용될 수 있습니다.
주의 : 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은 나에게 약간의 거래 차단기입니다. google 컬렉션은 엄청나게 게으릅니다(좋은 점: 거의 모든 작업을 즉시 수행할 수 있습니다. 실제 작업은 결과를 사용하기 시작할 때 완료됨). 이를 위해서는 전체 지도를 복사해야 합니다!
그래도 걱정하지 마십시오. 이러한 방식으로 "라이브" 지도를 정렬하는 데 집착했다면 다음과 같은 미친 것으로 위의 문제 중 하나가 아닌 둘 모두(!) 해결할 수 있습니다.
참고: 이것은 2012년 6월에 크게 변경되었습니다. 이전 코드는 절대 작동할 수 없습니다. TreeMap.get()
-> compare()
및 compare()
-> 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에 넣습니다. 그러나 그 전에 해시 맵을 확인하여 키가 실제로 중복되지 않았는지 확인합니다. 또한 우리가 만드는 비교기는 중복 값이 중복되지 않은 키를 삭제하지 않도록 키도 포함합니다(== 비교로 인해). 이 2가지 항목은 지도 계약이 유지되도록 하는 데 중요합니다. 당신이 그것을 원하지 않는다고 생각한다면, 당신은 거의 지도를 완전히 뒤집는 시점에 있습니다( 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));
키를 정렬하려면 Comparator가 각 비교에 대해 각 값을 조회해야 합니다. 더 확장 가능한 솔루션은 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는 예를 들어 반환 값으로 재사용될 수 있습니다. 이를 위해서는 일부 제네릭 경고를 억제해야 하지만 재사용 가능한 라이브러리 코드에는 그만한 가치가 있습니다. 또한 호출할 때마다 Comparator를 다시 할당할 필요가 없습니다.
덜 매력적이지만 더 효율적인 버전은 다음과 같습니다.
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();
마지막으로, 정렬된 정보에 지속적으로 액세스해야 하는 경우(간헐적으로 정렬하는 것이 아니라) 추가 멀티 맵을 사용할 수 있습니다. 자세한 내용이 필요하면 알려주세요...
commons-collections 라이브러리에는 TreeBidiMap 이라는 솔루션이 포함되어 있습니다. 또는 Google Collections API를 살펴볼 수 있습니다. 사용할 수있는 TreeMultimap이 있습니다.
그리고 이러한 프레임워크를 사용하지 않으려면... 소스 코드와 함께 제공됩니다.
주어진 답변을 살펴 보았지만 많은 키가 동일한 값을 가질 때 필요 이상으로 복잡하거나 맵 요소를 제거합니다.
다음은 더 적합하다고 생각되는 솔루션입니다.
public static <K, V extends Comparable<V>> Map<K, V> sortByValues(final Map<K, V> map) { Comparator<K> valueComparator = new Comparator<K>() { public int compare(K k1, K k2) { int compare = map.get(k2).compareTo(map.get(k1)); if (compare == 0) return 1; else return compare; } }; Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator); sortedByValues.putAll(map); return sortedByValues; }
지도는 가장 높은 값에서 가장 낮은 값으로 정렬됩니다.
Java 8의 새로운 기능으로 이를 수행하려면 다음을 수행하십시오.
import static java.util.Map.Entry.comparingByValue; import static java.util.stream.Collectors.toList; <K, V> List<Entry<K, V>> sort(Map<K, V> map, Comparator<? super V> comparator) { return map.entrySet().stream().sorted(comparingByValue(comparator)).collect(toList()); }
항목은 주어진 비교기를 사용하여 값에 따라 정렬됩니다. 또는 값이 서로 비교할 수 있는 경우 명시적 비교기가 필요하지 않습니다.
<K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) { return map.entrySet().stream().sorted(comparingByValue()).collect(toList()); }
반환된 목록은 이 메서드가 호출될 때 주어진 지도의 스냅샷이므로 어느 쪽도 다른 쪽의 후속 변경 사항을 반영하지 않습니다. 지도의 실시간 반복 가능한 보기의 경우:
<K, V extends Comparable<? super V>> Iterable<Entry<K, V>> sort(Map<K, V> map) { return () -> map.entrySet().stream().sorted(comparingByValue()).iterator(); }
반환된 iterable은 반복될 때마다 지정된 맵의 새로운 스냅샷을 생성하므로 동시 수정을 제외하고 항상 맵의 현재 상태를 반영합니다.
사용자 정의 비교기를 생성하고 새로운 TreeMap 객체를 생성할 때 사용하십시오.
class MyComparator implements Comparator<Object> { Map<String, Integer> map; public MyComparator(Map<String, Integer> map) { this.map = map; } public int compare(Object o1, Object o2) { if (map.get(o2) == map.get(o1)) return 1; else return ((Integer) map.get(o2)).compareTo((Integer) map.get(o1)); } }
기본 기능에서 아래 코드를 사용하십시오.
Map<String, Integer> lMap = new HashMap<String, Integer>(); lMap.put("A", 35); lMap.put("B", 75); lMap.put("C", 50); lMap.put("D", 50); MyComparator comparator = new MyComparator(lMap); Map<String, Integer> newMap = new TreeMap<String, Integer>(comparator); newMap.putAll(lMap); System.out.println(newMap);
산출:
{B=75, D=50, C=50, A=35}
주어진 지도
Map<String, Integer> wordCounts = new HashMap<>(); wordCounts.put("USA", 100); wordCounts.put("jobs", 200); wordCounts.put("software", 50); wordCounts.put("technology", 70); wordCounts.put("opportunity", 200);
값을 기준으로 지도를 오름차순으로 정렬
Map<String,Integer> sortedMap = wordCounts.entrySet(). stream(). sorted(Map.Entry.comparingByValue()). collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); System.out.println(sortedMap);
값을 기준으로 내림차순으로 맵 정렬
Map<String,Integer> sortedMapReverseOrder = wordCounts.entrySet(). stream(). sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())). collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); System.out.println(sortedMapReverseOrder);
산출:
{소프트웨어=50, 기술=70, 미국=100, 직업=200, 기회=200}
{작업=200, 기회=200, 미국=100, 기술=70, 소프트웨어=50}
지도를 정렬해야 하는 끊임없는 필요성은 아마도 냄새일 것이라는 점에는 동의하지만 다음 코드가 다른 데이터 구조를 사용하지 않고 이를 수행하는 가장 쉬운 방법이라고 생각합니다.
public class MapUtilities { public static <K, V extends Comparable<V>> List<Entry<K, V>> sortByValue(Map<K, V> map) { List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(map.entrySet()); Collections.sort(entries, new ByValue<K, V>()); return entries; } private static class ByValue<K, V extends Comparable<V>> implements Comparator<Entry<K, V>> { public int compare(Entry<K, V> o1, Entry<K, V> o2) { return o1.getValue().compareTo(o2.getValue()); } }
}
그리고 여기 당황스러울 정도로 불완전한 단위 테스트가 있습니다.
public class MapUtilitiesTest extends TestCase { public void testSorting() { HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("One", 1); map.put("Two", 2); map.put("Three", 3); List<Map.Entry<String, Integer>> sorted = MapUtilities.sortByValue(map); assertEquals("First", "One", sorted.get(0).getKey()); assertEquals("Second", "Two", sorted.get(1).getKey()); assertEquals("Third", "Three", sorted.get(2).getKey()); }
}
결과는 키와 값을 얻을 수 있는 Map.Entry 개체의 정렬된 목록입니다.
다음과 같은 일반 비교기를 사용하십시오.
final class MapValueComparator<K,V extends Comparable<V>> implements Comparator<K> { private final Map<K,V> map; private MapValueComparator() { super(); } public MapValueComparator(Map<K,V> map) { this(); this.map = map; } public int compare(K o1, K o2) { return map.get(o1).compareTo(map.get(o2)); } }
가장 많이 투표된 답변은 동일한 항목이 2개 있는 경우 작동하지 않습니다. TreeMap은 동일한 값을 남겨둡니다.
예: 정렬되지 않은 지도
키/값: D/67.3 키/값: A/99.5 키/값: B/67.4 키/값: C/67.5 키/값: E/99.5
결과
키/값: A/99.5 키/값: C/67.5 키/값: B/67.4 키/값: D/67.3
그래서 E는 생략!!
나를 위해 비교기를 조정하는 것이 잘 작동했습니다. 같으면 0이 아니라 -1을 반환합니다.
예에서:
클래스 ValueComparator 구현 Comparator {
지도 기반; public ValueComparator(지도 기반) { this.base = 기본; }
공개 int 비교(객체 a, 객체 b) {
if((Double)base.get(a) < (Double)base.get(b)) { return 1; } else if((Double)base.get(a) == (Double)base.get(b)) { return -1; } else { return -1; }
} }
이제 다음을 반환합니다.
정렬되지 않은 맵:
키/값: D/67.3 키/값: A/99.5 키/값: B/67.4 키/값: C/67.5 키/값: E/99.5
결과:
키/값: A/99.5 키/값: E/99.5 키/값: C/67.5 키/값: B/67.4 키/값: D/67.3
Aliens에 대한 응답으로(2011년 11월 22일): 정수 ID 및 이름의 맵에 이 솔루션을 사용하고 있지만 아이디어는 동일하므로 위의 코드가 올바르지 않을 수 있습니다(테스트에서 작성하겠습니다. 올바른 코드 제공) 위의 솔루션을 기반으로 한 지도 정렬 코드입니다.
package nl.iamit.util; import java.util.Comparator; import java.util.Map; public class Comparators { public static class MapIntegerStringComparator implements Comparator { Map<Integer, String> base; public MapIntegerStringComparator(Map<Integer, String> base) { this.base = base; } public int compare(Object a, Object b) { int compare = ((String) base.get(a)) .compareTo((String) base.get(b)); if (compare == 0) { return -1; } return compare; } } }
그리고 이것은 테스트 클래스입니다(방금 테스트했으며 정수, 문자열 맵에서 작동합니다.
package test.nl.iamit.util; import java.util.HashMap; import java.util.TreeMap; import nl.iamit.util.Comparators; import org.junit.Test; import static org.junit.Assert.assertArrayEquals; public class TestComparators { @Test public void testMapIntegerStringComparator(){ HashMap<Integer, String> unSoretedMap = new HashMap<Integer, String>(); Comparators.MapIntegerStringComparator bvc = new Comparators.MapIntegerStringComparator( unSoretedMap); TreeMap<Integer, String> sorted_map = new TreeMap<Integer, String>(bvc); //the testdata: unSoretedMap.put(new Integer(1), "E"); unSoretedMap.put(new Integer(2), "A"); unSoretedMap.put(new Integer(3), "E"); unSoretedMap.put(new Integer(4), "B"); unSoretedMap.put(new Integer(5), "F"); sorted_map.putAll(unSoretedMap); Object[] targetKeys={new Integer(2),new Integer(4),new Integer(3),new Integer(1),new Integer(5) }; Object[] currecntKeys=sorted_map.keySet().toArray(); assertArrayEquals(targetKeys,currecntKeys); } }
다음은 지도 비교기의 코드입니다.
public static class MapStringDoubleComparator implements Comparator { Map<String, Double> base; public MapStringDoubleComparator(Map<String, Double> base) { this.base = base; } //note if you want decending in stead of ascending, turn around 1 and -1 public int compare(Object a, Object b) { if ((Double) base.get(a) == (Double) base.get(b)) { return 0; } else if((Double) base.get(a) < (Double) base.get(b)) { return -1; }else{ return 1; } } }
그리고 이것은 이것에 대한 테스트 케이스입니다:
@Test public void testMapStringDoubleComparator(){ HashMap<String, Double> unSoretedMap = new HashMap<String, Double>(); Comparators.MapStringDoubleComparator bvc = new Comparators.MapStringDoubleComparator( unSoretedMap); TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc); //the testdata: unSoretedMap.put("D",new Double(67.3)); unSoretedMap.put("A",new Double(99.5)); unSoretedMap.put("B",new Double(67.4)); unSoretedMap.put("C",new Double(67.5)); unSoretedMap.put("E",new Double(99.5)); sorted_map.putAll(unSoretedMap); Object[] targetKeys={"D","B","C","E","A"}; Object[] currecntKeys=sorted_map.keySet().toArray(); assertArrayEquals(targetKeys,currecntKeys); }
물론 이것을 훨씬 더 일반적으로 만들 수 있지만 1개의 경우(지도)에 필요했습니다.
Collections.sort
를 사용하는 대신 Arrays.sort
사용하는 것이 좋습니다. 실제로 Collections.sort
가 하는 일은 다음과 같습니다.
public static <T extends Comparable<? super T>> void sort(List<T> list) { Object[] a = list.toArray(); Arrays.sort(a); ListIterator<T> i = list.listIterator(); for (int j=0; j<a.length; j++) { i.next(); i.set((T)a[j]); } }
toArray
를 호출 Arrays.sort
를 사용합니다. 이렇게 하면 모든 맵 항목이 세 번 복사됩니다. 한 번은 맵에서 임시 목록(LinkedList 또는 ArrayList)으로, 그런 다음 임시 배열로, 마지막으로 새 맵으로 복사됩니다.
내 솔루션은 불필요한 LinkedList를 생성하지 않기 때문에 이 한 단계를 생략합니다. 다음은 일반적이고 성능이 최적화된 코드입니다.
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) { @SuppressWarnings("unchecked") Map.Entry<K,V>[] array = map.entrySet().toArray(new Map.Entry[map.size()]); Arrays.sort(array, new Comparator<Map.Entry<K, V>>() { public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { return e1.getValue().compareTo(e2.getValue()); } }); Map<K, V> result = new LinkedHashMap<K, V>(); for (Map.Entry<K, V> entry : array) result.put(entry.getKey(), entry.getValue()); return result; }
이것은 중복 값이 있는 경우 작동하지 않는 Anthony의 답변의 변형입니다.
public static <K, V extends Comparable<V>> Map<K, V> sortMapByValues(final Map<K, V> map) { Comparator<K> valueComparator = new Comparator<K>() { public int compare(K k1, K k2) { final V v1 = map.get(k1); final V v2 = map.get(k2); /* Not sure how to handle nulls ... */ if (v1 == null) { return (v2 == null) ? 0 : 1; } int compare = v2.compareTo(v1); if (compare != 0) { return compare; } else { Integer h1 = k1.hashCode(); Integer h2 = k2.hashCode(); return h2.compareTo(h1); } } }; Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator); sortedByValues.putAll(map); return sortedByValues; }
null을 처리하는 방법은 아직 공개되지 않았습니다.
이 접근 방식의 중요한 이점 중 하나는 여기에 제공된 다른 솔루션과 달리 실제로 Map을 반환한다는 것입니다.
최선의 접근
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; public class OrderByValue { public static void main(String a[]){ Map<String, Integer> map = new HashMap<String, Integer>(); map.put("java", 20); map.put("C++", 45); map.put("Unix", 67); map.put("MAC", 26); map.put("Why this kolavari", 93); Set<Entry<String, Integer>> set = map.entrySet(); List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(set); Collections.sort( list, new Comparator<Map.Entry<String, Integer>>() { public int compare( Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2 ) { return (o1.getValue()).compareTo( o2.getValue() );//Ascending order //return (o2.getValue()).compareTo( o1.getValue() );//Descending order } } ); for(Map.Entry<String, Integer> entry:list){ System.out.println(entry.getKey()+" ==== "+entry.getValue()); } }}
산출
java ==== 20 MAC ==== 26 C++ ==== 45 Unix ==== 67 Why this kolavari ==== 93
늦은 입장.
Java-8의 출현으로 우리는 매우 쉽고 간결한 방식으로 데이터 조작을 위해 스트림을 사용할 수 있습니다. 스트림을 사용하여 값별로 맵 항목을 정렬하고 삽입 순서 반복을 유지 하는 LinkedHashMap을 만들 수 있습니다.
예:
LinkedHashMap sortedByValueMap = map.entrySet().stream() .sorted(comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)) //first sorting by Value, then sorting by Key(entries with same value) .collect(LinkedHashMap::new,(map,entry) -> map.put(entry.getKey(),entry.getValue()),LinkedHashMap::putAll);
역순의 경우 다음을 교체하십시오.
comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)
~와 함께
comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey).reversed()
주요 문제. 첫 번째 답변을 사용하는 경우(Google에서 여기로 안내) 비교기를 변경하여 equal 절을 추가하세요. 그렇지 않으면 키를 기준으로 sorted_map에서 값을 가져올 수 없습니다.
public int compare(String a, String b) { if (base.get(a) > base.get(b)) { return 1; } else if (base.get(a) < base.get(b)){ return -1; } return 0; // returning 0 would merge keys }
이 질문에 대한 답변은 이미 많이 있지만, 내가 찾던 것, 연결된 값으로 정렬된 키와 항목을 반환하고 지도에서 키와 값이 수정될 때 이 속성을 유지 관리하는 지도 구현을 제공한 것은 없습니다. 다른 두 가지 질문 이 이에 대해 구체적으로 묻습니다.
이 사용 사례를 해결하는 일반적인 친숙한 예제를 만들었습니다. 이 구현은 원래 객체의 keySet() 및 entrySet()에서 반환된 집합의 값 변경 및 제거를 반영하는 것과 같이 Map 인터페이스의 모든 계약을 준수하지 않습니다. 나는 그러한 솔루션이 스택 오버플로 답변에 포함하기에는 너무 클 것이라고 느꼈습니다. 더 완전한 구현을 만들 수 있다면 아마도 Github에 게시한 다음 이 답변의 업데이트된 버전에 링크할 것입니다.
import java.util.*; /** * A map where {@link #keySet()} and {@link #entrySet()} return sets ordered * by associated values based on the the comparator provided at construction * time. The order of two or more keys with identical values is not defined. * <p> * Several contracts of the Map interface are not satisfied by this minimal * implementation. */ public class ValueSortedMap<K, V> extends HashMap<K, V> { protected Map<V, Collection<K>> valueToKeysMap; // uses natural order of value object, if any public ValueSortedMap() { this((Comparator<? super V>) null); } public ValueSortedMap(Comparator<? super V> valueComparator) { this.valueToKeysMap = new TreeMap<V, Collection<K>>(valueComparator); } public boolean containsValue(Object o) { return valueToKeysMap.containsKey(o); } public V put(K k, V v) { V oldV = null; if (containsKey(k)) { oldV = get(k); valueToKeysMap.get(oldV).remove(k); } super.put(k, v); if (!valueToKeysMap.containsKey(v)) { Collection<K> keys = new ArrayList<K>(); keys.add(k); valueToKeysMap.put(v, keys); } else { valueToKeysMap.get(v).add(k); } return oldV; } public void putAll(Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); } public V remove(Object k) { V oldV = null; if (containsKey(k)) { oldV = get(k); super.remove(k); valueToKeysMap.get(oldV).remove(k); } return oldV; } public void clear() { super.clear(); valueToKeysMap.clear(); } public Set<K> keySet() { LinkedHashSet<K> ret = new LinkedHashSet<K>(size()); for (V v : valueToKeysMap.keySet()) { Collection<K> keys = valueToKeysMap.get(v); ret.addAll(keys); } return ret; } public Set<Map.Entry<K, V>> entrySet() { LinkedHashSet<Map.Entry<K, V>> ret = new LinkedHashSet<Map.Entry<K, V>>(size()); for (Collection<K> keys : valueToKeysMap.values()) { for (final K k : keys) { final V v = get(k); ret.add(new Map.Entry<K,V>() { public K getKey() { return k; } public V getValue() { return v; } public V setValue(V v) { throw new UnsupportedOperationException(); } }); } } return ret; } }
컨텍스트에 따라 항목이 맵에 배치되는 순서를 기억하는 java.util.LinkedHashMap<T>
Collections.sort()
를 통해 정렬할 수 있는 별도의 List를 유지 관리하는 것이 좋습니다.
Afaik에서 가장 깔끔한 방법은 컬렉션을 활용하여 값에 따라 맵을 정렬하는 것입니다.
Map<String, Long> map = new HashMap<String, Long>(); // populate with data to sort on Value // use datastructure designed for sorting Queue queue = new PriorityQueue( map.size(), new MapComparable() ); queue.addAll( map.entrySet() ); // get a sorted map LinkedHashMap<String, Long> linkedMap = new LinkedHashMap<String, Long>(); for (Map.Entry<String, Long> entry; (entry = queue.poll())!=null;) { linkedMap.put(entry.getKey(), entry.getValue()); } public static class MapComparable implements Comparator<Map.Entry<String, Long>>{ public int compare(Entry<String, Long> e1, Entry<String, Long> e2) { return e1.getValue().compareTo(e2.getValue()); } }
TreeMap<>은 같을 수 있는 값에 대해 작동하지 않으므로 다음을 사용했습니다.
private <K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) { List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<K, V>>() { public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) { return o1.getValue().compareTo(o2.getValue()); } }); return list; }
LinkedHashMap 에 목록 을 넣고 싶을 수도 있지만 바로 반복하려는 경우에는 불필요합니다...
이것은 너무 복잡합니다. 지도는 값을 기준으로 정렬하는 것과 같은 작업을 수행하지 않아야 합니다. 가장 쉬운 방법은 요구 사항에 맞게 자신의 클래스를 만드는 것입니다.
아래의 예에서는 *가 있는 위치에 TreeMap에 비교기를 추가해야 합니다. 그러나 Java API에 의해 비교기는 값이 아닌 키만 제공합니다. 여기에 언급된 모든 예는 2 지도를 기반으로 합니다. 하나의 해시와 하나의 새로운 트리. 이상합니다.
예:
Map<Driver driver, Float time> map = new TreeMap<Driver driver, Float time>(*);
따라서 다음과 같이 맵을 세트로 변경합니다.
ResultComparator rc = new ResultComparator(); Set<Results> set = new TreeSet<Results>(rc);
Results
클래스를 만들 것입니다.
public class Results { private Driver driver; private Float time; public Results(Driver driver, Float time) { this.driver = driver; this.time = time; } public Float getTime() { return time; } public void setTime(Float time) { this.time = time; } public Driver getDriver() { return driver; } public void setDriver (Driver driver) { this.driver = driver; } }
및 비교기 클래스:
public class ResultsComparator implements Comparator<Results> { public int compare(Results t, Results t1) { if (t.getTime() < t1.getTime()) { return 1; } else if (t.getTime() == t1.getTime()) { return 0; } else { return -1; } } }
이 방법으로 더 많은 종속성을 쉽게 추가할 수 있습니다.
마지막으로 간단한 반복자를 추가하겠습니다.
Iterator it = set.iterator(); while (it.hasNext()) { Results r = (Results)it.next(); System.out.println( r.getDriver().toString //or whatever that is related to Driver class -getName() getSurname() + " " + r.getTime() ); }
Java 8 이상에서 모든 맵을 정렬하는 간단한 방법
Map<String, Object> mapToSort = new HashMap<>(); List<Map.Entry<String, Object>> list = new LinkedList<>(mapToSort.entrySet()); Collections.sort(list, Comparator.comparing(o -> o.getValue().getAttribute())); HashMap<String, Object> sortedMap = new LinkedHashMap<>(); for (Map.Entry<String, Object> map : list) { sortedMap.put(map.getKey(), map.getValue()); }
Java 7 이하를 사용하는 경우
Map<String, Object> mapToSort = new HashMap<>(); List<Map.Entry<String, Object>> list = new LinkedList<>(mapToSort.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String, Object>>() { @Override public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) { return o1.getValue().getAttribute().compareTo(o2.getValue().getAttribute()); } }); HashMap<String, Object> sortedMap = new LinkedHashMap<>(); for (Map.Entry<String, Object> map : list) { sortedMap.put(map.getKey(), map.getValue()); }
@devinmoore 코드를 기반으로 제네릭을 사용하고 오름차순 및 내림차순 정렬을 지원하는 지도 정렬 방법.
/** * Sort a map by it's keys in ascending order. * * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map. * @author Maxim Veksler */ public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map) { return sortMapByKey(map, SortingOrder.ASCENDING); } /** * Sort a map by it's values in ascending order. * * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map. * @author Maxim Veksler */ public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map) { return sortMapByValue(map, SortingOrder.ASCENDING); } /** * Sort a map by it's keys. * * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map. * @author Maxim Veksler */ public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map, final SortingOrder sortingOrder) { Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() { public int compare(Entry<K, V> o1, Entry<K, V> o2) { return comparableCompare(o1.getKey(), o2.getKey(), sortingOrder); } }; return sortMap(map, comparator); } /** * Sort a map by it's values. * * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map. * @author Maxim Veksler */ public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map, final SortingOrder sortingOrder) { Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() { public int compare(Entry<K, V> o1, Entry<K, V> o2) { return comparableCompare(o1.getValue(), o2.getValue(), sortingOrder); } }; return sortMap(map, comparator); } @SuppressWarnings("unchecked") private static <T> int comparableCompare(T o1, T o2, SortingOrder sortingOrder) { int compare = ((Comparable<T>)o1).compareTo(o2); switch (sortingOrder) { case ASCENDING: return compare; case DESCENDING: return (-1) * compare; } return 0; } /** * Sort a map by supplied comparator logic. * * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map. * @author Maxim Veksler */ public static <K, V> LinkedHashMap<K, V> sortMap(final Map<K, V> map, final Comparator<Map.Entry<K, V>> comparator) { // Convert the map into a list of key,value pairs. List<Map.Entry<K, V>> mapEntries = new LinkedList<Map.Entry<K, V>>(map.entrySet()); // Sort the converted list according to supplied comparator. Collections.sort(mapEntries, comparator); // Build a new ordered map, containing the same entries as the old map. LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(map.size() + (map.size() / 20)); for(Map.Entry<K, V> entry : mapEntries) { // We iterate on the mapEntries list which is sorted by the comparator putting new entries into // the targeted result which is a sorted map. result.put(entry.getKey(), entry.getValue()); } return result; } /** * Sorting order enum, specifying request result sort behavior. * @author Maxim Veksler * */ public static enum SortingOrder { /** * Resulting sort will be from smaller to biggest. */ ASCENDING, /** * Resulting sort will be from biggest to smallest. */ DESCENDING }
다음은 OO 솔루션입니다(즉, static
메서드를 사용하지 않음).
import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class SortableValueMap<K, V extends Comparable<V>> extends LinkedHashMap<K, V> { public SortableValueMap() { } public SortableValueMap( Map<K, V> map ) { super( map ); } public void sortByValue() { List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>( entrySet() ); Collections.sort( list, new Comparator<Map.Entry<K, V>>() { public int compare( Map.Entry<K, V> entry1, Map.Entry<K, V> entry2 ) { return entry1.getValue().compareTo( entry2.getValue() ); } }); clear(); for( Map.Entry<K, V> entry : list ) { put( entry.getKey(), entry.getValue() ); } } private static void print( String text, Map<String, Double> map ) { System.out.println( text ); for( String key : map.keySet() ) { System.out.println( "key/value: " + key + "/" + map.get( key ) ); } } public static void main( String[] args ) { SortableValueMap<String, Double> map = new SortableValueMap<String, Double>(); map.put( "A", 67.5 ); map.put( "B", 99.5 ); map.put( "C", 82.4 ); map.put( "D", 42.0 ); print( "Unsorted map", map ); map.sortByValue(); print( "Sorted map", map ); } }
이로써 퍼블릭 도메인에 기부되었습니다.
중복 값이 있는 쌍으로 정렬된 맵을 갖기 위한 몇 가지 간단한 변경 사항입니다. 비교 메서드(클래스 ValueComparator)에서 값이 같을 때 0을 반환하지 않고 2개의 키를 비교한 결과를 반환합니다. 키는 맵에서 구별되므로 중복 값(키별로 정렬됨)을 유지하는 데 성공합니다. 따라서 위의 예는 다음과 같이 수정할 수 있습니다.
public int compare(Object a, Object b) { if((Double)base.get(a) < (Double)base.get(b)) { return 1; } else if((Double)base.get(a) == (Double)base.get(b)) { return ((String)a).compareTo((String)b); } else { return -1; } } }
확실히 Stephen의 솔루션은 정말 훌륭하지만 Guava를 사용할 수 없는 사람들을 위해:
다음은 값 맵으로 정렬하는 솔루션입니다. 이 솔루션은 동일한 값 등이 두 번 있는 경우를 처리합니다.
// If you want to sort a map by value, and if there can be twice the same value: // here is your original map Map<String,Integer> mapToSortByValue = new HashMap<String, Integer>(); mapToSortByValue.put("A", 3); mapToSortByValue.put("B", 1); mapToSortByValue.put("C", 3); mapToSortByValue.put("D", 5); mapToSortByValue.put("E", -1); mapToSortByValue.put("F", 1000); mapToSortByValue.put("G", 79); mapToSortByValue.put("H", 15); // Sort all the map entries by value Set<Map.Entry<String,Integer>> set = new TreeSet<Map.Entry<String,Integer>>( new Comparator<Map.Entry<String,Integer>>(){ @Override public int compare(Map.Entry<String,Integer> obj1, Map.Entry<String,Integer> obj2) { Integer val1 = obj1.getValue(); Integer val2 = obj2.getValue(); // DUPLICATE VALUE CASE // If the values are equals, we can't return 0 because the 2 entries would be considered // as equals and one of them would be deleted (because we use a set, no duplicate, remember!) int compareValues = val1.compareTo(val2); if ( compareValues == 0 ) { String key1 = obj1.getKey(); String key2 = obj2.getKey(); int compareKeys = key1.compareTo(key2); if ( compareKeys == 0 ) { // what you return here will tell us if you keep REAL KEY-VALUE duplicates in your set // if you want to, do whatever you want but do not return 0 (but don't break the comparator contract!) return 0; } return compareKeys; } return compareValues; } } ); set.addAll(mapToSortByValue.entrySet()); // OK NOW OUR SET IS SORTED COOL!!!! // And there's nothing more to do: the entries are sorted by value! for ( Map.Entry<String,Integer> entry : set ) { System.out.println("Set entries: " + entry.getKey() + " -> " + entry.getValue()); } // But if you add them to an hashmap Map<String,Integer> myMap = new HashMap<String,Integer>(); // When iterating over the set the order is still good in the println... for ( Map.Entry<String,Integer> entry : set ) { System.out.println("Added to result map entries: " + entry.getKey() + " " + entry.getValue()); myMap.put(entry.getKey(), entry.getValue()); } // But once they are in the hashmap, the order is not kept! for ( Integer value : myMap.values() ) { System.out.println("Result map values: " + value); } // Also this way doesn't work: // Logic because the entryset is a hashset for hashmaps and not a treeset // (and even if it was a treeset, it would be on the keys only) for ( Map.Entry<String,Integer> entry : myMap.entrySet() ) { System.out.println("Result map entries: " + entry.getKey() + " -> " + entry.getValue()); } // CONCLUSION: // If you want to iterate on a map ordered by value, you need to remember: // 1) Maps are only sorted by keys, so you can't sort them directly by value // 2) So you simply CAN'T return a map to a sortMapByValue function // 3) You can't reverse the keys and the values because you have duplicate values // This also means you can't neither use Guava/Commons bidirectionnal treemaps or stuff like that // SOLUTIONS // So you can: // 1) only sort the values which is easy, but you loose the key/value link (since you have duplicate values) // 2) sort the map entries, but don't forget to handle the duplicate value case (like i did) // 3) if you really need to return a map, use a LinkedHashMap which keep the insertion order
간부: http://www.ideone.com/dq3Lu
출력:
Set entries: E -> -1 Set entries: B -> 1 Set entries: A -> 3 Set entries: C -> 3 Set entries: D -> 5 Set entries: H -> 15 Set entries: G -> 79 Set entries: F -> 1000 Added to result map entries: E -1 Added to result map entries: B 1 Added to result map entries: A 3 Added to result map entries: C 3 Added to result map entries: D 5 Added to result map entries: H 15 Added to result map entries: G 79 Added to result map entries: F 1000 Result map values: 5 Result map values: -1 Result map values: 1000 Result map values: 79 Result map values: 3 Result map values: 1 Result map values: 3 Result map values: 15 Result map entries: D -> 5 Result map entries: E -> -1 Result map entries: F -> 1000 Result map entries: G -> 79 Result map entries: A -> 3 Result map entries: B -> 1 Result map entries: C -> 3 Result map entries: H -> 15
그것이 어떤 사람들에게 도움이되기를 바랍니다.
출처 : http:www.stackoverflow.com/questions/109383/sort-a-mapkey-value-by-values
JavaScript에서 "잘못된 날짜" 날짜 인스턴스 감지 (0) | 2022.01.06 |
---|---|
Git으로 파일의 이전 버전을 보려면 어떻게 해야 합니까? (0) | 2022.01.06 |
호스트에서 Docker 컨테이너의 IP 주소를 얻는 방법 (0) | 2022.01.06 |
객체 자체가 아닌 포인터를 사용해야 하는 이유는 무엇입니까? (0) | 2022.01.06 |
이미지 처리: '코카콜라 캔' 인식을 위한 알고리즘 개선 (0) | 2022.01.06 |