Map <Key、Value>を値でソート


1635

私はJavaに比較的慣れていないためMap<Key, Value>、値を並べ替える必要があることがよくあります。

値が一意ではありませんので、私は自分自身が変換を見つけるkeySetarray、そしてを通じてその配列のソート配列のソートカスタムコンパレータキーに関連付けられた値でソートすること。

もっと簡単な方法はありますか?


24
マップはソートするためのものではなく、高速にアクセスできます。オブジェクトの等しい値は、マップの制約を破ります。同様に、エントリのセットを使用List<Map.Entry<...>> list =new LinkedList(map.entrySet())し、Collections.sort ....それそのように。
Hannes

1
Java(Map <Object、Integer>)でCounterを利用しようとすると、これが発生する場合があります。その場合、発生数によるソートが一般的な操作になります。Pythonのような言語には、カウンターデータ構造が組み込まれています。Javaで実装する別の方法については、ここでの例である
demongolem

7
ソートされたマップには多くのユースケースがあります。そのため、jdkにはTreeMapとConcurrentSkipListMapがあります。
alobodzk


1
TreeMapとConcurrentSkipListMapはキーでソートされます。問題は、値によるソートについてです。
ピーター

回答:


899

これは、一般的なバージョンです。

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

10
嬉しいです。John、LinkedHashMapは予測可能な反復順序を提供するため、ソリューションにとって重要です。
カーターページ

3
@ buzz3791はい。これは、あらゆるソートアルゴリズムに当てはまります。ソート中に構造内のノードの値を変更すると、予測できない(そしてほとんど常に悪い)結果が生じます。
カーターページ

3
@Sheagorath Androidで試してみましたが、うまくいきました。Java 6バージョンを使用していることを考えると、これはプラットフォーム固有の問題ではありません。値オブジェクトにComparableを正しく実装しましたか?
saiyancoder 2014

6
「この操作の動作は明示的に非決定的です」という状態のドキュメントがあるため、Java 8バージョンではのforEachOrdered代わりに使用するべきではありません。forEachforEach
rob

1
これを完全に取り除きましたが、コメントで@CarterPageをクレジットしました(とにかくオープンソースプロジェクトに含まれます)。本当にありがとう。
ネイサンビーチ

420

重要な注意点:

このコードは複数の方法で破損する可能性があります。提供されたコードを使用する場合は、コメントも読んで、その影響を認識してください。たとえば、値はキーで取得できなくなります。(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}

18
これ以上ありません(stackoverflow.com/questions/109383/…)。また、なぜダブルへのキャストがあったのですか?それだけじゃないのreturn ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))
スティーブン

12
@Stephen:いいえ。この場合、値が等しいすべてのキーが削除されます(等しいとの比較と参照による比較)。さらに、このコードでも次のシーケンスに問題があります map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);
steffen

43
ツリーマップに使用されるコンパレータは、equalsと一致しません(sortMap javadoxを参照)。つまり、ツリーマップからのアイテムの廃棄は機能しません。Sorted_map.get( "A")はnullを返します。つまり、このツリーマップの使用は壊れています。
mR_fr0g

87
わかりにくい場合に備えて、同じ値に複数のキーがマッピングされている場合、このソリューションではおそらく期待どおりの結果が得られません。ソートされた結果に表示されるのは、これらのキーの1つだけです。
Maxy-B

63
Louis Wasserman(そう、Google Guavaの仲間の1人)は、実際にこの答えをかなり嫌っています。同じ値にマップすると、ブレークします。バッキングマップにないキーに対してgetを呼び出すと、ブレークします。含まれていないキーでルックアップが発生するような操作を行うと、マップ-Map.equals呼び出し、containsKey、その他-本当に奇妙なスタックトレースで壊れます。 " plus.google.com/102216152814616302326/posts/bEQLDK712MJ
ヘイレム

339

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

いいですがparallelStream()、この場合の使用についてはどうですか?
ベンジ

11
それは並列に動作しますが、部分的な結果を組み合わせるためにマップをマージするコストは高すぎるため、並列バージョンは期待どおりに機能しない可能性があります。しかし、それは機能し、正しい答えを生成します。
Brian Goetz、2014

有益なアドバイスをありがとう。使用しているキーの種類と非常に多くのパラメーターに依存しますが、まさにそれは私が思っていたものでした...重要なことは、「機能し、正しい答えを出す」ことです。
Benj

2
top10の例でcompareByValueを使用する必要はありませんか?
レオ、

1
@Benjトップ10の抽出に関しては機能しますが、結果のマップは順序付けされなくなります。
OrangeDog

211

3つの1行回答...

私が使用するGoogleのコレクション グアバをこれを行うには-あなたの値がされている場合はComparable、あなたが使用することができます

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

これは、マップの関数(オブジェクト)を作成し(任意のキーを入力として受け取り、それぞれの値を返します)、それらに自然な(比較可能な)順序を適用します[値]。

それらが比較できない場合、あなたは次の線に沿って何かをする必要があります

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

これらはTreeMap(Orderingextends としてComparator)またはLinkedHashMapにいくつかのソート後に適用できます

:TreeMapを使用する場合は、比較== 0の場合、項目は既にリストにあることに注意してください(これは、同じものを比較する複数の値がある場合に発生します)。これを軽減するには、次のようにキーをコンパレータに追加します(キーと値がであると想定Comparable)。

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

= キーによってマップされた値に自然順序付けを適用し、それをキーの自然順序付けと合成します

あなたの鍵は、0との比較が、これはほとんどのために十分なものでなければならない場合、これはまだ動作しないことに注意comparable項目(としてhashCodeequalsおよびcompareTo同期であることが多いです...)

Ordering.onResultOf()およびFunctions.forMap()を参照してください。

実装

これで、必要な処理を実行するコンパレータが用意できたので、そこから結果を取得する必要があります。

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

今これはおそらくうまくいくでしょうが:

  1. 完成した完全なマップを前提として実行する必要があります
  2. 上の上記のコンパレータを試さないでくださいTreeMap。挿入後まで値がない場合に挿入されたキーを比較しようとしても意味がありません。

ポイント1は、私にとってちょっとした決断です。googleコレクションは信じられないほど怠惰です(これは良いことです:ほとんどすべての操作を瞬時に実行できます。実際の作業は結果の使用を開始すると行われます)。これにはマップ全体をコピーする必要があります。

「完全な」回答/値でライブソートされたマップ

でも心配しないでください。この方法で「ライブ」マップをソートすることに夢中なら、次のようなクレイジーな方法で上記の問題の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に配置します。ただしその前に、ハッシュマップをチェックして、キーが実際には重複していないことを確認します。また、作成するコンパレーターにはキーも含まれるため、重複する値が重複しないキーを削除しないようにします(==比較のため)。これらの2つの項目は、マップコントラクトを維持するために不可欠です。もしそれを望まないのであれば、マップを完全に逆転させようとしています(にMap<V,K>)。

コンストラクタは次のように呼び出す必要があります

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

こんにちは@Stephen、注文方法の使用例を教えていただけますか?Orderingのソースコードを調べたところ、.natural()。onResultOf(...)が何を返すかまったくわかりません。ソースコードは "public <F> Ordering <F> onResultOf"です。コンパイル方法もわかりません。最も重要なのは、「<F> Ordering <F>」を使用してマップをソートする方法ですか。それはコンパレータか何かですか?ありがとう。
smallufo

Ordering単に金持ちComparatorです。私はそれぞれの例(それぞれの下のイタリック体)にコメントしてみました。「自然」は、​​オブジェクトがであることを示しますComparable。これは、Apache CommonのComparableComparatorのようなものです。onResultOf比較されるアイテムに関数を適用します。したがって、整数に1を追加する関数がある場合、natural().onResultOf(add1Function).compare(1,2)最終的には次のようになります2.compareTo(3)
Stephen

元のマップに重複する値がある場合、ImmutableSortedMap.copyOfはIllegalArgumentExceptionをスローします。
lbalazscs 2013

@Ibalazscsはい、できます- 重複する変数のコレクションを使用ImmutableSetMultiMapまたはImmutableListMultiMap格納できるはずです。
Stephen

1
このおかげで、私は1つのプロジェクトであなたのソリューションを使用しました。でも、問題があると思います。マップのように振る舞うには、以前にキーに関連付けられていた値が存在する場合は、それを返す必要がありますが、このようにしないでください。私が使用した解決策は、削除された値が存在する場合はそれを返すことです。
アレックス2013

185

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

16
ソートされるリストは「新しいLinkedList」ですか?ああ。ありがたいことに、Collections.sort()は、この種のエラーを正確に回避するために、最初にリストを配列にダンプします(ただし、ArrayListを配列にダンプする方が、LinkedListに対して同じことをするよりも高速です)。
Dimitris Andreou 2010

IteratorからTernaryTree.Iteratorに変換できません
lisak

4
@ gg.kaspersky「LinkedListをソートするのは悪いことだ」と言っているわけではありませんが、ここでは、LinkedList自体はソートに関係なく悪い選択です。 ArrayListを使用する方がはるかによく、追加のポイントについては、正確にmap.size()でサイズを設定します。code.google.com/p/memory-measurer/wiki/…も参照してください。ArrayListの 要素あたりの平均コスト:5バイトLinkedListの要素あたりの平均コスト:24バイト。正確なサイズのArrayListの場合、平均コストは4バイトになります。つまり、LinkedListは、ArrayListが必要とするメモリの6倍のメモリを必要とします。それはただの膨らみです
Dimitris Andreou

2
上記の値を使用すると、昇順でソートされます。降順で並べ替える方法は?
ramは

1
o1とo2を置き換えて、降順でソートします。
Soheil 2017年

68

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

逆の順序で並べ替える方法は?
Vlad Holubiev 2014年

6
解決策を見つけましたCollections.reverseOrder(comparing(Entry::getValue))
Vlad Holubiev 2014

1
私はそこにタイプミスがあると思います-「toMap」は「Collectors.toMap()」として呼び出されるべきではありませんか?
Jake Stokes

1
@JakeStokesまたは静的インポートを使用します:-)
assylias

6
逆の順序でエントリ値でソートするには良い方法がある:Entry.comparingByValue(Comparator.reverseOrder())
ゲディミナスRimsa

31

キーをソートするには、コンパレータが各比較の各値を検索する必要があります。よりスケーラブルなソリューションでは、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();

最後に、(たまに並べ替えるだけでなく)並べ替えられた情報に継続的にアクセスする必要がある場合は、追加のマルチマップを使用できます。詳細が必要な場合はお知らせください...


List <Map.Entry <K、V >>を返す場合、2番目のバージョンはより簡潔になる可能性があります。これにより、マップへの追加の大量の取得を行わなくても、反復とキーと値の両方の取得が容易になります。これはすべて、このコードがスレッドセーフでないことに問題がないことを前提としています。バッキングマップまたは並べ替えられたリストがマルチスレッド環境で共有されている場合、すべてのベットはオフになります。
Mike Miller、

26

commons-collectionsライブラリには、TreeBidiMapと呼ばれるソリューションが含まれています。または、GoogleコレクションAPIを確認することもできます。それは持っていTreeMultimapあなたが使用することができます。

また、これらのフレームワークを使用したくない場合は、ソースコードが付属しています。


commons-collectionを使用する必要はありません。Javaには、独自のjava.util.TreeMapが付属しています。
yoliho 2008

2
はい、しかし、MapMapは、mapentriesの部分でソートする場合、はるかに柔軟性が低くなります。
p3t0r 2008

9
BidiMapの問題は、関係を反転可能にするために、キーと値の間に1:1の関係制約を追加することです(つまり、キーと値の両方が一意である必要があります)。これは、多くの単語が同じ数を持つため、これを使用して単語数オブジェクトのようなものを格納することができないことを意味します。
Doug

26

私は与えられた回答を見てきましたが、それらの多くは必要以上に複雑であるか、いくつかのキーが同じ値を持つ場合はマップ要素を削除します。

ここに私がよりよく合うと思う解決策があります:

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

マップは最高値から最低値にソー​​トされることに注意してください。


6
問題:返されたマップを後で使用する場合、たとえば、特定の要素が含まれているかどうかを確認する場合、カスタムコンパレーターがあるため、常にfalseになります。考えられる解決策:最後の行を次のものに置き換えます:return new LinkedHashMap <K、V>(sortedByValues);
Erel Segal-Halevi、2011年

@ErelSegalHaleviが指摘したことを除いて、これは私にはクリーンな解決策に見えます。コンパレータに指定したため、マップに値が存在するかどうかを確認することはできません。map.put( "1"、 "One"); map.put( "2"、 "Two"); map.put( "3"、 "Three"); map.put( "4"、 "4"); map.put( "5"、 "5"); return new TreeMap <K、V>(sortedByValues);のように関数sortByValues()で新しいオブジェクトを返す場合、map.containsKey( "1")は常にfalseを返します。問題を解決します。ありがとうAbhi
abhi

user157196の回答とCarter Pageの回答とほぼ同じです。カーターページにはLinkedHashMapの修正が含まれています
Kirby

解の4行目はint compare = map.get(k1).compareTo(map.get(k2));である必要があります。昇順が必要な場合
www.Decompiler.com 2014

19

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

返された反復可能オブジェクトは、反復されるたびに指定されたマップの新しいスナップショットを作成するため、同時変更を禁止すると、常にマップの現在の状態が反映されます。


これは、値でソートされたマップではなく、エントリーのリストを返します。マップを返す他のバージョン:stackoverflow.com/a/22132422/829571
assylias

17

カスタマイズされたコンパレーターを作成し、それを新しい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}

値が等しい場合は、「return 1」の行を変更してキーを比較しました。「return((String)o1).compareTo((String)o2);」
gjgjgj

14

マップをソートするための絶え間ない必要性はおそらく匂いであることに同意しますが、次のコードは、異なるデータ構造を使用せずにそれを行う最も簡単な方法だと思います。

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オブジェクトのソートされたリストで、そこからキーと値を取得できます。


この方法は、ほぼ同じ効果を持つMap <V、List <K >>オブジェクトを作成するよりもはるかに簡単で直感的です。値は実際にはMapオブジェクトのキーではありません。実際に探しているのは、この状況でのリストです。
Jeff Wu

このソリューションは多くの値では機能せず、私のカウント(各キーに関連付けられた値)にねじ込まれています
Sam Levin

1
それは奇妙だ。詳しく説明してもらえますか?あなたの出力は何で、期待した出力は何でしたか?
Lyudmil 2012

12

次のような汎用コンパレータを使用します。

final class MapValueComparator<K,V extends Comparable<V>> implements Comparator<K> {

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

11

等しい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(Map base){this.base = base; }

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 -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つのケース(マップ)にそれを必要としました


あなたが正しかった、私が最初に与えたコードにいくつかのエラーがありました!最近の編集がお役に立てば幸いです。
michel.iamit

9

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ます。このようにして、すべてのマップエントリが3回コピーされます。1回はマップから一時リスト(LinkedListまたはArrayList)にコピーされ、次に一時配列にコピーされ、最後に新しいマップにコピーされます。

私のソリューションは、不要なLinkedListを作成しないため、この1つのステップを省略しています。以下は、一般的に扱いやすく、パフォーマンスが最適なコードです。

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

8

これは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の処理方法はかなり空中であることに注意してください。

このアプローチの重要な利点の1つは、ここで提供されている他のソリューションとは異なり、実際にマップを返すことです。


不正解です。重複した値がある場合、私の方法は機能します。私は、値が「1」の100個を超えるキーを持つマップで使用しました。
アンソニー

8

最善のアプローチ

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

7

大きな問題。最初の回答を使用する場合(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
    }

等しい値を持つ2つのエントリを追加すると、それらはマージされます。オブジェクトが同じ(等しい)であることが確実な場合にのみ0を返す必要があります
Masood_mj

7

この質問にはすでにたくさんの回答がありますが、私が探していたもの、つまり関連する値でソートされたキーとエントリを返し、キーと値がマップで変更されたときにこのプロパティを維持するマップ実装はどれも提供されませんでした。他の 2つ質問は、これを具体的に要求します。

この使用例を解決する一般的なフレンドリーな例を作成しました。この実装は、元のオブジェクトの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;
    }
}

ComparableとComparatorが許可されていない場合、それを行う方法は?
Ved Prakash、

私があなたのユースケースを理解しているかどうかわからない、おそらくあなたは詳しく説明することができます。値として使用するオブジェクトがComparableでない場合は、それをそうであるオブジェクトに変換する必要があります。
David Bleckmann、

6

後期エントリー。

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

このコメント付きのバージョンをありがとう。一つの質問:使用しての違いは何ですかEntry.comparingByValue()(assyliasは上記の答えとしてstackoverflow.com/a/22132422/1480587)またはcomparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)あなたが使用していることは?値が同じ場合は、キーも比較していると思いますよね?並べ替えが同じ値の要素の順序を保持していることに気づきました。キーがすでに並べ替えられている場合、キーによる並べ替えが必要ですか?
Peter T.

6

与えられたマップ

   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}

{jobs = 200、offportity = 200、USA = 100、technology = 70、software = 50}


map-reduceは本当に "Reduced"だと思います...良い解決策です。
ha9u63ar

おかげで@ ha9u63ar
アルパンSaini

それは機能しますが、要素の順序がHashMapでどのように機能するのかわかりませんか?
Ali Tou

5

コンテキストに応じて、java.util.LinkedHashMap<T>アイテムをマップに配置する順序をどのメモを使用するか。それ以外の場合、自然な順序に基づいて値をソートする必要がある場合は、でソートできる個別のリストを維持することをお勧めしCollections.sort()ます。


これが-1だった理由はわかりません。これまでのところLinkedHashMapがおそらく私にとって最良の解決策です。ただ捨てて、新しいLinkedHashMapを作成するのにどれほどの費用がかかるかを理解しようとしています。
NobleUplift 2016

5

以来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に入れたいと思うかもしれませんが、すぐにそれを反復するだけなら、それは不必要です...


そうですが、コンパレータは値が等しい場合を処理しません
Sebastien Lorber '27

5

これは複雑すぎます。マップは、値で並べ替えるなどの処理を行うことは想定されていませんでした。最も簡単な方法は、要件に合うように独自のクラスを作成することです。

下の例では、*がある場所にTreeMapコンパレータを追加することになっています。しかし、Java APIでは、コンパレーターは値ではなくキーのみを提供します。ここに記載されている例はすべて2つのマップに基づいています。1つのハッシュと1つの新しいツリー。それは奇妙です。

例:

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

4

@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
}

その後、再び多分よりよい解決策は、念の使用org.apache.commons.collections.bidimap.TreeBidiMapで、自己ソートマップを使用することです
マキシムVeksler

4

これはオブジェクト指向ソリューションです(つまり、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 );
  }
}

これにより、パブリックドメインに寄付されました。


4

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

4

重複する値を持つペアを含むマップをソートするためのいくつかの簡単な変更。比較メソッド(クラス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;
        }
      }
    }

4

確かにスティーブンのソリューションは本当に素晴らしいですが、グアバを使用できない人のために:

これは、値によってマップをソートするための私のソリューションです。このソリューションは、同じ値が2倍ある場合などを処理します...

// 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

それが一部の人々を助けることを願っています


3

キーが重複していて、データのセットが少ない場合(<1000)、コードのパフォーマンスが重要ではない場合は、次のようにします。

Map<String,Integer> tempMap=new HashMap<String,Integer>(inputUnsortedMap);
LinkedHashMap<String,Integer> sortedOutputMap=new LinkedHashMap<String,Integer>();

for(int i=0;i<inputUnsortedMap.size();i++){
    Map.Entry<String,Integer> maxEntry=null;
    Integer maxValue=-1;
    for(Map.Entry<String,Integer> entry:tempMap.entrySet()){
        if(entry.getValue()>maxValue){
            maxValue=entry.getValue();
            maxEntry=entry;
        }
    }
    tempMap.remove(maxEntry.getKey());
    sortedOutputMap.put(maxEntry.getKey(),maxEntry.getValue());
}

inputUnsortedMapはコードへの入力です。

変数sortedOutputMapは、反復されると降順でデータを含みます。順序を変更するには、ifステートメントで>を<に変更します。

最速の並べ替えではありませんが、追加の依存関係なしで作業を行います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.