TreeMap değerine göre sırala


137

Varsayılan doğal sipariş yerine bir TreeMap değerine göre sıralamak sağlayacak bir karşılaştırıcı yazmak istiyorum.

Böyle bir şey denedim, ama neyin yanlış gittiğini bulamıyorum:

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

Sanırım ne soruyorum: Map.EntryKarşılaştırıcıya geçebilir miyim ?


belki de bu size yardımcı olacaktır stackoverflow.com/questions/109383/…
Inv3r53


Yanıtlar:


179

TreeMapDeğerlere göre sıralama yapamazsınız , çünkü bu SortedMapşartnameye aykırıdır :

A , anahtarlarında toplam siparişMap de sağlar .

Bununla birlikte, harici bir koleksiyon kullanarak Map.entrySet(), anahtarlar, değerler veya hatta ikisinin bir kombinasyonu (!!) ile istediğiniz zaman sıralayabilirsiniz .

İşte döndüren bir jenerik yöntemidir SortedSetarasında Map.Entry, verilen bir Mapolan değerlerdir 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;
}

Şimdi aşağıdakileri yapabilirsiniz:

    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]"

SortedSetKendini veya içini değiştirmeye çalışırsanız korkak şeyler olacağını unutmayın Map.Entry, çünkü bu artık orijinal haritanın "görünümü" gibi entrySet()değildir.

Genel olarak, bir haritanın girişlerini değerlerine göre sıralama ihtiyacı atipiktir.


Hakkında not ==içinInteger

Orijinal karşılaştırıcı karşılaştırır Integerkullanarak ==. Çünkü bu, hemen hemen her zaman yanlış ==olan Integerişlenen bir referans eşitlik değil, değer eşitliktir.

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

İlgili sorular


5
map.put ("D", 2) eklerseniz; sonuç hala "[C = 1, B = 2, A = 3]" değil, "[C = 1, B = 2, D = 2, A = 3]" değil
Igor Milla

3
Düzeltme: int res = e2.getValue (). ComparTo (e1.getValue ()); res döndür! = 0? res: 1;
beshkenadze

new Integer (0) == new Integer (0) Bir nesneye başvuruları karşılaştırırsınız ve iki nesne yaratırsınız! Çıkış koymak kesinlikle doğru ve tahmin edilebilir.
Yuriy Chernyshov

2
"Genel olarak konuşursak, bir haritanın girişlerini değerlerine göre sıralama ihtiyacı atipik" Ben burada bir acemi değilim ama bunu yapmak çok yaygın bir şey olmalı gibi hissediyorum? Örneğin, bir haritadaki en yaşlı 3 kişiyi almak istiyorsanız {"ana" = 37, "peter" = 43, "john" = 4, "mary" = 15, "Matt" = 78}
fartagaintuxedo

@igormilla Kodunuzun neden çalışmasının basit bir nedeni vardır: Otomatik boks, Integer.valueOf kullanır. Ve bunun -128 ila 127 arasında bir önbelleği var!
jokster

75

poligeno-yağlayıcıların cevabı neredeyse mükemmel. Yine de önemli bir hata var. Değerlerin aynı olduğu harita girişlerini işlemez.

Bu kod: ...

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

Çıktı:

ape:1
frog:2
pig:3

İnekimizin maymunumuzla "1" değerini paylaşırken nasıl kaybolduğuna dikkat edin: O!

Kodda yapılan bu değişiklik sorunu çözer:

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 Setuygulamasında birden fazla unsur bulunmaz . Sadece bu kısıtlamayı ihlal ettiniz. Gönderen SortedSetAPI: Not Sıralanmış kümesi tarafından tutulan sipariş olduğunu eşittir ile tutarlı olmalıdır ... . Çözüm, bir Listuygulamaya geçmek olacaktır .
dacwe

4
@dacwe eşit değerler anahtar değil
bellum

1
@bellum: SetBurada burada bahsediyoruz . Yana Comparatorihlal Setsözleşme Set.remove, Set.containsvb yapar değil iş ! İdeone'de bu örneği inceleyin .
dacwe

3
Eğer geçerseniz Sadece bir nota int res= e1.getValue().compareTo(e2.getValue());içine int res= e2.getValue().compareTo(e1.getValue());, bir inen değerleri yerine yükselen sipariş var.
tugcem

6
res != 0 ? res : e1.getKey().compareTo(e2.getKey())Eşit değerlere sahip anahtarların sırasını korumak için dönmeyi tercih ederim .
Marco Lackovic

46

Java 8'de:

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

17

A TreeMapher zaman tuşlara göre sıralanır, başka bir şey imkansızdır. Bir Comparatortek isteğim kontrol etmenizi sağlar nasıl tuşları sıralanır.

Sıralanan değerleri istiyorsanız, bunları bir ayıklamak Listve sıralamak zorundasınız .


10

ComparatorHer zaman karşılaştırılacak haritanın anahtarını alacağından, a kullanılarak yapılamaz . TreeMapyalnızca tuşa göre sıralayabilir.


2
TreeMap yalnızca Karşılaştırıcı'ya anahtarı iletirse, karşılaştırıcının yapıcısında TreeMap'e bir başvuruda bulunursam, değeri almak için anahtarı kullanarak, böyle bir şey (referansla nasıl geçileceğinden emin değilim): class byValue Comparator {TreeMap theTree; public byValue (TreeMap theTree) {this.theTree = theTree; } public int compar (Dize k1, Dize k2) {// değere TreeMap için getKey yöntemini kullan}}
vito huang

1
@vito: hayır, çünkü genellikle iki anahtardan biri henüz haritada olmayacak ve değerini alamayacaksınız.
Joachim Sauer

Bu, TreeMap Karşılaştırıcısında bunu yapmanın bir örneği değil mi? (yukarıda belirtildiği gibi, bu, SortedMapanahtarlara göre sıralamayı belirleyen özellikleri
Marcus

@Marcus: Sıralama değerlerini almak için mevcut bir haritayı Comparatorkullanır . Başka bir deyişle, daha sonra yerleştirilen rastgele değerleri sıralayamaz , sadece orijinal Harita'da bulunan değerleri sıralayabilir . TreeMap
Joachim Sauer

5

Olof'un cevabı iyi, ama mükemmel olmadan önce bir şeye daha ihtiyacı var. Cevabının altındaki yorumlarda, dacwe (doğru) uygulamasının Setler için Karşılaştır / Eşittir sözleşmesini ihlal ettiğini belirtiyor. Kümede açıkça bulunan bir girdiyi içerir veya kaldırmaya çalışırsanız, eşit değerlere sahip girdilerin kümeye yerleştirilmesine izin veren kod nedeniyle küme tanımaz. Yani, bunu düzeltmek için anahtarlar arasında eşitliği test etmemiz gerekiyor:

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

"Sıralı küme Set arayüzünü doğru şekilde uygulamak için, sıralı bir kümenin sürdürdüğü (açık bir karşılaştırıcı sağlanmış olsun veya olmasın) sıralamanın eşit olması gerektiğini unutmayın ... Set arayüzü eşittir işlemi olarak tanımlanır , ancak sıralı bir küme karşılaştırma öğesi (veya karşılaştırma) yöntemini kullanarak tüm öğe karşılaştırmalarını gerçekleştirir, bu nedenle bu yöntemle eşit kabul edilen iki öğe, sıralı küme açısından eşittir . " ( http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html )

Başlangıçta seti eşit değerli girişler eklemeye zorlamak için eşitliği göz ardı ettiğimizden, kümenin aradığınız girişi gerçekten döndürmesi için anahtarlarda eşitliği test etmemiz gerekiyor. Bu biraz dağınık ve kesinlikle setlerin nasıl kullanılması amaçlanmadı - ama işe yarıyor.


3

Bu yazının özellikle bir TreeMap değerini değerlere göre sıralamasını istediğini biliyorum, ancak gerçekten uygulamayı önemsemeyen, ancak öğeleri ekledikçe sıralanmış koleksiyonu tutan bir çözüm isteyenler için, bu TreeSet tabanlı geri bildirimi takdir ediyorum çözüm. Birincisi, elemanlar anahtarla kolayca alınamaz, ancak elimde olan kullanım durumunda (en düşük değerlere sahip n anahtarlarını bulmak), bu bir gereklilik değildi.

  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

Bir çok insan List'i kullanmak için tavsiye duyuyor ve ben de kullanmayı tercih ediyorum

Haritanın girişlerini değerlerine göre sıralamak için iki yöntem vardır.

    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;
        }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.