値によるツリーマップのソート


137

デフォルトの自然順ではなく、値によってTreeMapをソートできるコンパレータを作成したいと思います。

私はこのようなことを試しましたが、何がうまくいかなかったのかわかりません:

import java.util.*;

class treeMap {
    public static void main(String[] args) {
        System.out.println("the main");
        byValue cmp = new byValue();
        Map<String, Integer> map = new TreeMap<String, Integer>(cmp);
        map.put("de",10);
        map.put("ab", 20);
        map.put("a",5);

        for (Map.Entry<String,Integer> pair: map.entrySet()) {
            System.out.println(pair.getKey()+":"+pair.getValue());
        }
    }
}

class byValue implements Comparator<Map.Entry<String,Integer>> {
    public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
        if (e1.getValue() < e2.getValue()){
            return 1;
        } else if (e1.getValue() == e2.getValue()) {
            return 0;
        } else {
            return -1;
        }
    }
}

私は何を求めていると思います:Map.Entryコンパレータにパスしてもらえますか?


おそらくこれはあなたが助けるstackoverflow.com/questions/109383/...
Inv3r53


回答:


179

仕様にTreeMap反するため、値自体を並べ替えることはできませんSortedMap

A Mapはさらに、そのキーの全体的な順序を提供します

ただし、外部コレクションを使用するとMap.entrySet()、キー、値、またはこの2つの組み合わせ(!!)のいずれかで、いつでも好きな方法で並べ替えることができます。

ここでは一般的な方法は戻っていることだSortedSetMap.Entry与えられ、Mapその値があるがComparable

static <K,V extends Comparable<? super V>>
SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
    SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
        new Comparator<Map.Entry<K,V>>() {
            @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                int res = e1.getValue().compareTo(e2.getValue());
                return res != 0 ? res : 1;
            }
        }
    );
    sortedEntries.addAll(map.entrySet());
    return sortedEntries;
}

これで、次のことができるようになります。

    Map<String,Integer> map = new TreeMap<String,Integer>();
    map.put("A", 3);
    map.put("B", 2);
    map.put("C", 1);   

    System.out.println(map);
    // prints "{A=3, B=2, C=1}"
    System.out.println(entriesSortedByValues(map));
    // prints "[C=1, B=2, A=3]"

SortedSetそれ自体またはMap.Entry内部を変更しようとすると、ファンキーなものが発生することに注意してください。これは、元のマップの「ビュー」ではなくなったためentrySet()です。

一般的に言って、マップのエントリを値でソートする必要性は典型的ではありません。


上の注意事項==についてInteger

オリジナルのコンパレータはIntegerを使用して比較し==ます。ので、これは、ほとんど常に間違っている==Integerオペランド参照の等価ではなく、価値の平等です。

    System.out.println(new Integer(0) == new Integer(0)); // prints "false"!!!

関連する質問


5
map.put( "D"、2);を追加した場合 結果は「[C = 1、B = 2、A = 3]」であり、「[C = 1、B = 2、D = 2、A = 3]」ではありません
Igor Milla

3
修正:int res = e2.getValue()。compareTo(e1.getValue()); return res!= 0?res:1;
beshkenadze

new Integer(0)== new Integer(0)オブジェクトへの参照を比較して、2つのオブジェクトを作成します。出力は完全に正しく予測可能です。
Yuriy Chernyshov 14年

2
「一般的に言えば、マップのエントリをその値でソートする必要性は典型的ではありません」私はここの初心者ですが、非常に一般的なことのように思えますか?たとえば、マップ内の最も古い3人を取得する場合は、{"ana" = 37、 "peter" = 43、 "john" = 4、 "mary" = 15、 "Matt" = 78}
fartagaintuxedo

@igormillaコードが機能する理由は簡単です。オートボクシングはInteger.valueOfを使用します。そして、それは-128から127のインスタンスのキャッシュを持っています!
jokster

75

polygenelubricantsの答えはほぼ完璧です。ただし、重要なバグが1つあります。値が同じであるマップエントリは処理しません。

このコード:...

Map<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);

for (Entry<String, Integer> entry  : entriesSortedByValues(nonSortedMap)) {
    System.out.println(entry.getKey()+":"+entry.getValue());
}

出力されます:

ape:1
frog:2
pig:3

サルが私たちのサル:O!と値「1」を共有したため、牛がどのように失踪したかに注意してください。

このコードの変更はその問題を解決します:

static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
        SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
            new Comparator<Map.Entry<K,V>>() {
                @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                    int res = e1.getValue().compareTo(e2.getValue());
                    return res != 0 ? res : 1; // Special fix to preserve items with equal values
                }
            }
        );
        sortedEntries.addAll(map.entrySet());
        return sortedEntries;
    }

2
-1。AFASIK Set実装には、要素が2回以上含まれていません。あなたはその制約に違反しました。SortedSetAPI:ソートセットによって維持順序があること注意equalsと一致していなければなりません ...。解決策は、List実装に変更することです。
dacwe

4
@dacwe同じ値ではないキー
bellum

1
@bellum:Setここでs について話しています。以来Comparator違反するSet契約をSet.removeSet.containsなどが動作しませんideoneでこの例を確認してください。
dacwe 2013年

3
注:に切り替えるint res= e1.getValue().compareTo(e2.getValue());int res= e2.getValue().compareTo(e1.getValue());、値の昇順ではなく降順になります。
tugcem 2013年

6
res != 0 ? res : e1.getKey().compareTo(e2.getKey())同じ値のキーの順序を保持するために戻ります。
マルコラッコビッチ2013年

45

Java 8の場合:

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

17

TreeMap常にキーでソートされ、それ以外は不可能です。をComparator使用すると、キーのソート方法を制御できます 。

ソートされた値が必要な場合は、それらを抽出して Listソートあります。


10

比較するマップComparatorキーを常に取得するため、を使用してこれを行うことはできません。TreeMapキーでのみソートできます。


2
TreeMapがキーをコンパレータにのみ渡す場合、コンパレータのコンストラクタでTreeMapの参照を作成し、キーを使用して次のような値を取得することができます(参照渡しの方法がわからない):クラスbyValue Comparator {TreeMap theTree;を実装します。public byValue(TreeMap theTree){this.theTree = theTree; } public int compare(String k1、String k2){//値にTreeMapのgetKeyメソッドを使用}}
vito huang

1
@vito:いいえ。通常、2つのキーの1つはまだマップに含まれておらず、その値を取得することができないためです。
Joachim Sauer、

これは、TreeMapのコンパレータ内でこれを行う例ではありませんか?(上述のようにもかかわらず、これは、の仕様破壊しSortedMap指定キーでソートれる)をbeginnersbook.com/2014/07/...
マーカス

@Marcus:既存のマップをComparator使用して、ソートする値を取得します。つまり、後で入力された任意の値を並べ替えることはできません。元のマップに既にある値のみを並べ替えることができます。TreeMap
Joachim Sauer

5

オロフの答えは良いですが、完璧になる前にもう1つ必要なことがあります。彼の回答の下のコメントで、dacweは(正しく)彼の実装がセットのCompare / Equals規約に違反していると指摘しています。セットに含まれていることが明らかなエントリでcontainsまたはremoveを呼び出そうとすると、セットに等しい値のエントリを配置できるようにするコードがあるため、セットはそれを認識しません。したがって、これを修正するには、キー間の等価性をテストする必要があります。

static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
    SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
        new Comparator<Map.Entry<K,V>>() {
            @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                int res = e1.getValue().compareTo(e2.getValue());
                if (e1.getKey().equals(e2.getKey())) {
                    return res; // Code will now handle equality properly
                } else {
                    return res != 0 ? res : 1; // While still adding all entries
                }
            }
        }
    );
    sortedEntries.addAll(map.entrySet());
    return sortedEntries;
}

「ソートセットが(明示的なコンパレーターが提供されているかどうかにかかわらず)維持される順序は、ソートセットがSetインターフェースを正しく実装する場合、equalsと一致している必要があることに注意してください... Setインターフェースは、equals操作で定義されます。ですが、ソートされたセットは、compareTo(またはcompare)メソッドを使用してすべての要素の比較を実行するため、このメソッドによって等しいと見なされる2つの要素は、ソートされたセットの観点から、等しいです。(http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html

セットに等しい値のエントリを強制的に追加するために当初は等価性を見落としていたため、今度はキーの等価性をテストして、セットが探しているエントリを実際に返すようにする必要があります。これはちょっと厄介で、セットがどのように使用されることを意図していたのかは間違いありませんが、それは機能します。


3

この投稿では具体的にTreeMapを値で並べ替えることを求めていることは知っていますが、実装についてはあまり気にせず、要素が追加されたときにコレクションを並べ替えておくソリューションが必要な場合は、このTreeSetベースのフィードバックに感謝します解決。1つは、要素がキーによって簡単に取得されないことですが、私が手元に持っていたユースケース(最低値を持つn個のキーを見つける)では、これは必須ではありませんでした。

  TreeSet<Map.Entry<Integer, Double>> set = new TreeSet<>(new Comparator<Map.Entry<Integer, Double>>()
  {
    @Override
    public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2)
    {
      int valueComparison = o1.getValue().compareTo(o2.getValue());
      return valueComparison == 0 ? o1.getKey().compareTo(o2.getKey()) : valueComparison;
    }
  });
  int key = 5;
  double value = 1.0;
  set.add(new AbstractMap.SimpleEntry<>(key, value));

2

多くの人がリストを使用するようにアドバイスを聞いて、私もそれを使用することを好みます

ここでは、値に従ってマップのエントリをソートするために必要な2つの方法を示します。

    static final Comparator<Entry<?, Double>> DOUBLE_VALUE_COMPARATOR = 
        new Comparator<Entry<?, Double>>() {
            @Override
            public int compare(Entry<?, Double> o1, Entry<?, Double> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        };

        static final List<Entry<?, Double>> sortHashMapByDoubleValue(HashMap temp)
        {
            Set<Entry<?, Double>> entryOfMap = temp.entrySet();

            List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
            Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
            return entries;
        }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.