Bir Harita <Anahtar, Değer> değerlerine göre sırala


1635

Java için nispeten yeniyim ve genellikle Map<Key, Value>değerler üzerinde sıralamak gerektiğini buluyorum .

Değerlerin benzersiz olmadığından, kendimi dönüştürme bulmak keySetbir içine arrayve içinden bu diziyi sıralama dizi sıralama bir ile özel karşılaştırıcıya değerine sıralar anahtarla ilişkili olduğunu.

Daha kolay bir yol var mı?


24
Bir haritanın sıralanması değil, hızlı bir şekilde erişilmesi gerekir. Nesne eşit değerleri haritanın kısıtlamasını bozar. Gibi, giriş setini kullanın List<Map.Entry<...>> list =new LinkedList(map.entrySet())ve Collections.sort ....bunun bu şekilde.
Hannes

1
Java'da bir Sayaç kullanmaya çalıştığımızda bunun ortaya çıkabileceği bir durum (Harita <Nesne, Tamsayı>). Olay sayısına göre sıralama, ortak bir işlem olacaktır. Python gibi bir dilde Sayaç veri yapısı yerleşiktir. Java'da alternatif bir uygulama şekli için işte bir örnek
demongolem

7
Sıralı haritalar için birçok kullanım durumu vardır, bu yüzden jdk'de TreeMap ve ConcurrentSkipListMap var.
alobodzk


1
TreeMap ve ConcurrentSkipListMap tuşlarına göre sıralayın. Soru, değere göre sıralama ile ilgilidir.
Peter

Yanıtlar:


899

İşte genel olarak uygun bir sürüm:

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
Bu yardımcı olur sevindim. John, LinkedHashMap tahmin edilebilir yineleme sırası sağladığı için çözüm için önemlidir.
Carter Page

3
@ buzz3791 Doğru. Herhangi bir sıralama algoritmasında durum böyledir. Bir sıralama sırasında yapıdaki düğümlerin değerini değiştirmek öngörülemeyen (ve neredeyse her zaman kötü) sonuçlar oluşturur.
Carter Sayfa

3
@Sheagorath Android'de denedim ve çok çalışıyor. Java 6 sürümünü kullandığınız düşünüldüğünde platforma özgü bir sorun değildir. Karşılaştırılabilir değerini değer nesnenize doğru uyguladınız mı ?
saiyancoder

6
Java 8 sürümü forEachOrderedyerine şunu kullanmamalı forEach, çünkü docs forEach: "Bu işlemin davranışı açıkça belirsizdir."
soymak

1
tamamen bunu kopyaladı, ancak yorumlarda @CarterPage'e aktarıldı (yine de açık kaynaklı bir projede olacak). çok teşekkürler.
Nathan Beach

419

Önemli Not:

Bu kod birden çok şekilde kırılabilir. Sağlanan kodu kullanmayı düşünüyorsanız, sonuçların farkında olmak için yorumları da okuyun. Örneğin, değerler artık anahtarlarından alınamaz. ( gether zaman geri döner null.)


Yukarıdakilerin hepsinden çok daha kolay görünüyor. Aşağıdaki gibi bir TreeMap kullanın:

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

Çıktı:

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
Artık değil ( stackoverflow.com/questions/109383/… ). Ayrıca, neden Double için oyuncu kadrosu vardı? Sadece öyle değil return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))mi?
Stephen

12
@Stephen: Hayır. Bu durumda, değere eşit tüm tuşlar bırakılır (eşitler ve referans ile karşılaştırma arasındaki fark). Ayrıca: Bu kod bile aşağıdaki sıralamayla ilgili sorunlara sahiptir map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);
steffen

43
Ağaç haritası için kullanılan karşılaştırıcı eşittir (bkz. SortMap javadox). Bu, öğeleri ağaç haritasından çıkarmanın işe yaramayacağı anlamına gelir. sort_map.get ("A") null değerini döndürür. Bu, treemap kullanımının bozulduğu anlamına gelir.
mR_fr0g

87
Her ne kadar insanlar için açık değilse: aynı değere birden fazla anahtar eşlemeniz varsa, bu çözüm muhtemelen istediğinizi yapmayacaktır - sıralanan sonuçta bu anahtarlardan yalnızca biri görünecektir.
Maxy-B

63
Louis Wasserman (evet, Google Guava adamlarından biri), aslında bu cevabı biraz beğenmiyor: "Komik bile bakarsanız, gerçekten kafa karıştırıcı birkaç şekilde kırılıyor. Destek haritası değişirse, kırılacaktır. aynı değere eşlenirse, kırılacaktır.Bir yedek haritada bulunmayan bir anahtardan get çağrısı yaparsanız, kırılacaktır.Herhangi bir şey yaparsanız, harita - bir Map.equals çağrısı, includeKey, herhangi bir şey - gerçekten tuhaf yığın izleri ile kırılacak. " plus.google.com/102216152814616302326/posts/bEQLDK712MJ
haylem

339

Java 8 yeni bir yanıt sunuyor: girişleri bir akışa dönüştürün ve Map'in karşılaştırıcı birleştiricilerini kullanın.

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue());

Bu, girişleri artan değer sırasına göre sıralamanızı sağlar. Azalan değer istiyorsanız, karşılaştırıcıyı ters çevirmeniz yeterlidir:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));

Değerler karşılaştırılabilir değilse, açık bir karşılaştırıcı iletebilirsiniz:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue(comparator));

Daha sonra verileri tüketmek için diğer akış işlemlerini kullanmaya devam edebilirsiniz. Örneğin, yeni bir haritada ilk 10'u istiyorsanız:

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

Veya şuraya yazdırın System.out:

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue())
   .forEach(System.out::println);

Güzel, ama parallelStream()bu durumda kullanımı ne olacak ?
Benj

11
Paralel olarak çalışacaktır, ancak kısmi sonuçları birleştirmek için haritaları birleştirme maliyetinin çok pahalı olduğunu ve paralel sürümün umduğunuz kadar iyi performans göstermeyebileceğini görebilirsiniz. Ama işe yarıyor ve doğru cevabı veriyor.
Brian Goetz

Yararlı tavsiyeniz için teşekkürler. Tam olarak merak ettiğim şeydi, ancak ne tür bir anahtar kullandığınıza ve birçok parametreye bağlı ... Önemli olan şey "işe yarıyor ve doğru cevabı üretiyor".
Benj

2
top10 örneğinde CompareByValue kullanmanız gerekmez mi?
Leo

1
@Benj ilk 10'un çıkarılması açısından çalışacak, ancak ortaya çıkan harita artık sipariş edilmeyecek.
OrangeDog

211

Üç adet 1 satırlık cevap ...

Bunu yapmak için Google Koleksiyonlar Guava'yı kullanırdım - değerleriniz varsa Comparablekullanabilirsiniz

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

Bu da harita için [anahtarlardan herhangi birini girdi olarak alan, ilgili değeri döndüren] bir işlev (nesne) oluşturacak ve sonra onlara [değerler] doğal (karşılaştırılabilir) sıralaması uygulayacaktır.

Eğer karşılaştırılamazlarsa, o zaman

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

Bunlar, bir TreeMap (şekilde tatbik edilebilir Orderinguzanır Comparator) veya bir ayırma sonrası LinkedHashMap

Not : Bir TreeMap kullanacaksanız, bir karşılaştırma == 0 ise, öğenin zaten listede olduğunu (aynı olan birden fazla değeriniz varsa olur) unutmayın. Bunu hafifletmek için anahtarınızı karşılaştırıcıya şu şekilde ekleyebilirsiniz (anahtarlarınızın ve değerlerinizin olduğu varsayılarak Comparable):

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

= Anahtarla eşlenen değere doğal sıralama uygulayın ve anahtarın doğal sırasına göre birleştirin

Bu hala tuşları 0'a karşılaştırmak, ancak bu çoğu için yeterli olmalıdır eğer çalışmaz Not comparableöğeleri (aynı hashCode, equalsve compareTosenkronize genellikle ...)

Bkz. Ordering.onResultOf () ve Functions.forMap () .

uygulama

Şimdi istediğimizi yapan bir karşılaştırıcıya sahip olduğumuza göre, bundan bir sonuç almamız gerekiyor.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Şimdi bu muhtemelen işe yarayacak, ancak:

  1. tamamlanmış bir harita verildiğinde yapılması gerekenler
  2. Yukarıdaki karşılaştırıcılar bir üzerinde denemeyin TreeMap; yerleştirilen bir anahtarı koymadan sonra değere sahip olmadığında karşılaştırmaya çalışmanın bir anlamı yoktur, yani, gerçekten hızlı kırılır

Nokta 1 benim için bir anlaşma kırıcı; google koleksiyonları inanılmaz derecede tembeldir (bu iyidir: hemen hemen her işlemi yapabilirsiniz; gerçek çalışma sonucu kullanmaya başladığınızda yapılır) ve bu tüm haritayı kopyalamayı gerektirir !

"Tam" cevap / Değerlere göre canlı sıralanmış harita

Endişelenmeyin; bu şekilde sıralanmış bir "canlı" haritaya sahip olmak konusunda yeterince takıntılı olsaydınız, yukarıdaki sorunlardan birini değil, her ikisini de (!) aşağıdaki gibi çılgın bir şeyle çözebilirsiniz:

Not: Bu, Haziran 2012'de önemli ölçüde değişti - önceki kod asla işe yaramadı: TreeMap.get()-> compare()ve compare()-> arasında sonsuz bir döngü oluşturmadan değerleri aramak için dahili bir HashMap gerekirget()

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

Koyduğumuzda, karma haritasının karşılaştırıcı için değere sahip olduğundan emin oluruz ve sonra sıralama için TreeSet'e koyarız. Ancak bundan önce, anahtarın aslında bir kopya olmadığını görmek için karma haritayı kontrol ediyoruz. Ayrıca, oluşturduğumuz karşılaştırıcı anahtar da içerecek, böylece yinelenen değerler yinelenmeyen anahtarları silmeyecektir (== karşılaştırma nedeniyle). Bu 2 madde , harita sözleşmesinin yapılmasını sağlamak için hayati öneme sahiptir ; bunu istemediğinizi düşünüyorsanız, haritayı neredeyse tamamen tersine çevirme noktasındasınız demektir Map<V,K>.

Yapıcı olarak adlandırılmalıdır

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

Merhaba @Stephen, Sipariş nasıl kullanılır bir örnek verebilir misiniz? Ben sipariş kaynak kodu içine bakmak ve tamamen .natural (). OnResultOf (...) ne döndürdüğünü anlayamıyorum! Kaynak kodu "ortak <F> <F> onResultOf sipariş", nasıl derlediğini bile bilmiyorum! En önemlisi, bir haritayı sıralamak için "<F> Sipariş <F>" nasıl kullanılır? Karşılaştırıcı falan mı? Teşekkürler.
smallufo

Orderingsadece zengin Comparator. Ben her bir örnek (her birinin altında italik) yorum denedim. "doğal" nesnelerin Comparable; apache common's ComparableComparator gibi. onResultOfkarşılaştırılan öğeye bir işlev uygular. Eğer bir tamsayıya 1 ekleyen bir fonksiyonunuz natural().onResultOf(add1Function).compare(1,2)olsaydı , o zaman bunu yapardınız2.compareTo(3)
Stephen

ImmutableSortedMap.copyOf, orijinal haritada yinelenen değerler varsa IllegalArgumentException öğesini atar.
lbalazscs

@Ibalazscs Evet olacak - Yinelenen değişkenlerin koleksiyonunu kullanabilmeniz ImmutableSetMultiMapveya içermeniz gerekir ImmutableListMultiMap.
Stephen

1
Bunun için teşekkürler, çözümünüzü tek bir projede kullandım. Bence bir sorun var sanırım: bir harita gibi davranmak için, daha önce anahtarla ilişkili değeri döndürmek gerekiyor, eğer var, ama bunun gibi asla olmayacak. Kullandığım çözüm, varsa kaldırılan değeri döndürmektir.
alex

185

Gönderen 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
Sıralanacak liste "yeni LinkedList" ?? Gee. Neyse ki Collections.sort (), bu tür bir hatadan kaçınmak için listeyi bir diziye atar (ancak yine de bir ArrayList'i bir diziye dökmek, LinkedList için aynı işlemi yapmaktan daha hızlı olmalıdır).
Dimitris Andreou

Iterator için TernaryTree.Iterator dönüştürebilirsiniz
lisak

4
@ gg.kaspersky "LinkedList'i sıralamak kötü" demiyorum, ancak LinkedList'in kendisi, sıralamadan bağımsız olarak burada kötü bir seçimdir. Bir ArrayList kullanmak çok daha iyi ve ekstra puan için tam olarak map.size () boyutuna getirin. Ayrıca ArrayList'te code.google.com/p/memory-measurer/wiki/… öğe başına ortalama maliyetine bakın : LinkedList'te öğe başına 5 bayt ortalama maliyeti: 24 bayt. Tam boyutlu bir ArrayList için ortalama maliyet 4 bayt olur. Yani LinkedList , ArrayList'in ihtiyaç duyduğu bellek miktarının SIX katını alır . Bu sadece şişkinlik
Dimitris Andreou

2
yukarıdaki değerleri kullanarak artan düzende sıralanmıştır. Azalan sıralama nasıl yapılır?
Koç

1
Azalan düzeni sıralamak için o1 ve o2'yi değiştirin.
Soheil

68

Java 8 ile, api akışlarını önemli ölçüde daha az ayrıntılı bir şekilde yapmak için kullanabilirsiniz :

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

Ters sırada nasıl sıralanır?
Vlad Holubiev

6
bir çözüm bulundu -Collections.reverseOrder(comparing(Entry::getValue))
Vlad Holubiev

1
Bence bir yazım hatası görüyorum - "toMap" "Collectors.toMap ()" olarak adlandırılmamalıdır?
Jake Stokes

1
@JakeStokes Veya statik bir içe aktarma kullanın :-)
assylias

6
Giriş değerine göre ters sırayla sıralamanın daha iyi bir yolu:Entry.comparingByValue(Comparator.reverseOrder())
Gediminas Rimsa

31

Anahtarları sıralamak için Karşılaştırıcı'nın her karşılaştırma için her bir değeri araması gerekir. Daha ölçeklenebilir bir çözüm, inputSet'i doğrudan kullanacaktır, çünkü o zaman değer her karşılaştırma için hemen kullanılabilir olacaktır (ancak bunu sayılarla yedeklemedim).

İşte böyle bir şeyin genel bir sürümü:

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

Yukarıdaki çözüm için bellek dönüşünü azaltmanın yolları vardır. Oluşturulan ilk ArrayList, örneğin bir dönüş değeri olarak yeniden kullanılabilir; bu, bazı genel uyarıların bastırılmasını gerektirir, ancak yeniden kullanılabilir kütüphane kodu için buna değebilir. Ayrıca, Karşılaştırıcı'nın her çağrıda yeniden tahsis edilmesi gerekmez.

İşte daha az çekici bir sürüm de olsa daha verimli:

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

Son olarak, sıralanan bilgilere sürekli olarak erişmeniz gerekiyorsa (yalnızca arada bir sıralamak yerine), ek bir çoklu harita kullanabilirsiniz. Daha fazla ayrıntıya ihtiyacınız varsa bize bildirin ...


Liste <Map.Entry <K, V >> döndürürseniz, ikinci sürüm daha kısa olabilir. Bu kod iş parçacığı güvensiz olmak ile Tamam olduğunu varsayar. Destek haritası veya sıralanmış liste çok iş parçacıklı bir ortamda paylaşılıyorsa, tüm bahisler kapalıdır.
Mike Miller

26

Müşterek-koleksiyon kitaplığı TreeBidiMap adlı bir çözüm içerir . Veya Google Koleksiyonlar API'sına göz atabilirsiniz. Bu sahiptir TreeMultimap sen kullanabilirsiniz.

Ve bu çerçeveyi kullanmak istemiyorsanız ... kaynak kodu ile birlikte gelirler.


Ortak koleksiyonu kullanmak zorunda değilsiniz. Java, kendi java.util.TreeMap ile birlikte gelir.
yoliho

2
evet, ancak TreeMap mapentries'in değer kısmında sıralama yaparken çok daha az esnektir .
p3t0r

9
BidiMap ile ilgili sorun, ilişkiyi tersine çevrilebilir hale getirmek için anahtarlar ve değerler arasında 1: 1 ilişki kısıtlaması eklemesidir (yani, hem anahtarların hem de değerlerin benzersiz olması gerekir). Bu, kelime sayımı nesnesi gibi bir şeyi saklamak için kullanamayacağınız anlamına gelir, çünkü birçok kelime aynı sayıya sahip olacaktır.
Doug

26

Verilen cevaplara baktım, ancak birçoğu gerekenden daha karmaşık veya birkaç anahtar aynı değere sahip olduğunda harita öğelerini kaldırıyor.

İşte size daha uygun olduğunu düşündüğüm bir çözüm:

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

Haritanın en yüksek değerden en düşük değere doğru sıralandığını unutmayın.


6
SORUN: İade edilen haritayı daha sonra kullanmak istiyorsanız, örneğin belirli bir öğe içerip içermediğini kontrol etmek için, özel karşılaştırıcınız nedeniyle her zaman yanlış olur! Olası bir çözüm: son satırı şu şekilde değiştirin: return new LinkedHashMap <K, V> (sortByValues);
Erel Segal-Halevi

@ErelSegalHalevi'nin işaret ettiği, haritadaki değerlerin karşılaştırıcıyı belirttiğiniz gibi mümkün olup olmadığını kontrol etmesinin dışında bu bana temiz bir çözüm gibi görünüyor. map.put ("1", "Bir"); map.put ("2", "İki"); map.put ("3", "Üç"); map.put ("4", "Dört"); map.put ("5", "Beş"); Yeni TreeMap <K, V> (sortByValues) döndürmek gibi sortByValues ​​() işlevinde yeni bir nesne döndürürseniz map.containsKey ("1") her zaman false değerini döndürür; sorunu çözer. Teşekkürler Abhi
abhi

user157196 ve Carter Page'ın cevabı ile hemen hemen aynı. Carter Page's, LinkedHashMap düzeltmesini içeriyor
Kirby

4. çözüm satırı int compar = map.get (k1) .compareTo (map.get (k2)) olmalıdır; artan bir sipariş gerekiyorsa
www.Decompiler.com 22:14

19

Bunu Java 8'deki yeni özelliklerle gerçekleştirmek için:

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

Girdiler, verilen karşılaştırıcı kullanılarak değerlerine göre sıralanır. Alternatif olarak, değerleriniz karşılıklı olarak karşılaştırılabilirse, açık bir karşılaştırıcıya gerek yoktur:

<K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) {
    return map.entrySet().stream().sorted(comparingByValue()).collect(toList());
}

Döndürülen liste, bu yöntemin çağrıldığı sırada verilen haritanın bir anlık görüntüsüdür, bu nedenle ikisi de diğerinde yapılan değişiklikleri yansıtmaz. Haritanın canlı bir tekrarlanabilir görünümü için:

<K, V extends Comparable<? super V>> Iterable<Entry<K, V>> sort(Map<K, V> map) {
    return () -> map.entrySet().stream().sorted(comparingByValue()).iterator();
}

Döndürülen yinelenebilir, her tekrarlandığında verilen haritanın yeni bir anlık görüntüsünü oluşturur;


Bu, değere göre sıralanmış bir harita yerine bir Giriş Listesi döndürür. Bir haritayı döndüren diğer sürüm: stackoverflow.com/a/22132422/829571
assylias

17

Özelleştirilmiş karşılaştırıcı oluşturun ve yeni TreeMap nesnesi oluştururken kullanın.

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

    }
}

Ana işlevinizde aşağıdaki kodu kullanın

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

Çıktı:

{B=75, D=50, C=50, A=35}

Değerlerin eşit olması durumunda, anahtarları karşılaştırmak için "dönüş 1" satırını değiştirdim: "return ((String) o1) .compareTo ((String) o2);"
Ocak 19:57

14

Bir haritayı sıralamak için sürekli ihtiyaç muhtemelen bir koku olduğunu kabul ederken, aşağıdaki kod farklı bir veri yapısı kullanmadan bunu yapmanın en kolay yolu olduğunu düşünüyorum.

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

}

Ve işte utanç verici derecede eksik bir birim test:

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

}

Sonuç, anahtarları ve değerleri alabileceğiniz Map.Entry nesnelerinin sıralı bir listesidir.


Bu yöntem, hemen hemen aynı etkiye sahip bir Map <V, List <K>> nesnesi oluşturmaktan çok daha kolay ve sezgiseldir. Değerlerin bir Harita nesnesindeki anahtarlar olması gerekmez, gerçekten aradığınız şey bu durumda bir listedir, IMHO.
Jeff Wu

Bu çözüm çok fazla değerle çalışmıyor, sayımlarımdan mahrum kaldı (her bir anahtarla ilişkili değer)
Sam Levin

1
Bu garip. Açıklayabilir misiniz? Çıktınız neydi ve beklenen çıktı neydi?
Lyudmil

12

Aşağıdaki gibi genel bir karşılaştırıcı kullanın:

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

Eşit olan 2 öğeniz olduğunda en çok oyu veren cevap çalışmaz. TreeMap eşit değerleri dışarıda bırakır.

örnek: sıralanmamış harita

anahtar / değer: D / 67.3
anahtar / değer: A / 99.5
anahtar / değer: B / 67.4
anahtar / değer: C / 67.5
anahtar / değer: E / 99.5

Sonuçlar

anahtar / değer: A / 99.5
anahtar / değer: C / 67.5
anahtar / değer: B / 67.4
anahtar / değer: D / 67.3

Yani E dışarı bırakır !!

Benim için karşılaştırıcıyı ayarlamak iyi çalıştı, eğer eşit değilse 0 döndürmez -1.

örnekte:

class ValueComparator Comparator {

Harita tabanı; public ValueComparator (Harita tabanı) {this.base = temel; }

public int karşılaştırması (Nesne a, Nesne 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;
}

}}

şimdi geri dönüyor:

sıralanmamış harita:

anahtar / değer: D / 67.3
anahtar / değer: A / 99.5
anahtar / değer: B / 67.4
anahtar / değer: C / 67.5
anahtar / değer: E / 99.5

Sonuçlar:

anahtar / değer: A / 99.5
anahtar / değer: E / 99.5
anahtar / değer: C / 67.5
anahtar / değer: B / 67.4
anahtar / değer: D / 67.3

Yabancılar yanıt olarak (2011 Kasım 22): Ben bu çözümü bir Tamsayı Kimliği ve adları haritası için kullanıyorum, ama fikir aynı, bu yüzden yukarıdaki kod doğru olmayabilir (bir testte yazacağım) ve size doğru kodu verin), bu, yukarıdaki çözüme dayanarak bir Harita sıralama kodudur:

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


}

ve bu test sınıfı (Ben sadece test ve bu Tamsayı, String Map için çalışır:

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

bir Haritanın Karşılaştırıcısı için kod:

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

ve bu da bunun için bir test çantası:

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

Bu çok daha genel yapabilirsiniz, ama sadece 1 dava için ihtiyacım vardı (Harita)


haklıydın, ilk başta verdiğim kodda bir hata oluştu! i Son düzenlemem size yardımcı olacaktır.
michel.iamit

9

Collections.sortBazılarını kullanmak yerine kullanmayı öneririm Arrays.sort. Aslında Collections.sortböyle bir şey nedir :

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

Sadece toArraylisteyi çağırır ve kullanır Arrays.sort. Bu şekilde tüm harita girişleri üç kez kopyalanır: bir kez haritadan geçici listeye (bir LinkedList veya ArrayList olsun), sonra geçici diziye ve son olarak yeni haritaya.

Çözümüm, gereksiz LinkedList oluşturmadığı için bu adımı atmaktadır. İşte, genel dostu ve performans açısından optimum kod:

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

Bu, yinelenen değerler varsa işe yaramayan Anthony'nin cevabının bir varyasyonudur:

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'ların nasıl ele alınacağının havada olduğunu unutmayın.

Bu yaklaşımın önemli bir avantajı, burada sunulan diğer çözümlerin aksine aslında bir Harita döndürmesidir.


Yanlış, yöntemim yinelenen değerler varsa çalışır. Ben değer olarak "1" ile 100'den fazla anahtarları olan haritalar ile kullandım.
Anthony

8

En İyi Yaklaşım

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

Çıktı

java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93

7

Büyük sorun. İlk cevabı kullanırsanız (Google sizi buraya götürür), eşit bir cümle eklemek için karşılaştırıcıyı değiştirin, aksi takdirde sort_map'den anahtarlarla değer alamazsınız:

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
    }

Şimdi, eşit değerlere sahip iki giriş eklediğinizde birleştirileceklerdir, yalnızca nesnelerin aynı (eşit) olduğundan eminseniz 0 döndürmelisiniz
Masood_mj

7

Bu soru için zaten çok sayıda cevap var, ancak hiçbiri bana ne aradığımı sağlamadı, ilişkili değere göre sıralanmış anahtarları ve girdileri döndüren ve bu özelliği anahtarlar ve değerler haritada değiştirildikçe koruyan bir harita uygulaması. Diğer iki soru bunu özellikle soruyor.

Bu kullanım durumunu çözen genel bir dostça örnek hazırladım. Bu uygulama, orijinal nesnedeki keySet () ve inputSet () öğelerinden dönen kümelerdeki değer değişikliklerini ve kaldırmaları yansıtma gibi, Map arabiriminin tüm sözleşmelerini yerine getirmez. Böyle bir çözümün Yığın Taşması cevabına eklenemeyecek kadar büyük olacağını hissettim. Daha eksiksiz bir uygulama oluşturmayı başarırsam, belki de bunu Github'a gönderirim ve daha sonra bu cevabın güncellenmiş bir versiyonuna bağlarım.

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

Karşılaştırılabilir ve Karşılaştırıcıya izin verilmiyorsa nasıl yapılır?
Ved Prakash

Kullanım durumunuzu anladığımdan emin değilim, belki detaylandırabilirsiniz. Değer olarak kullanmak istediğiniz nesne Karşılaştırılabilir değilse, nesneyi olan bir nesneye dönüştürmeniz gerekir.
David Bleckmann

6

Geç Giriş.

Java-8'in ortaya çıkmasıyla, veri manipülasyonu için akışları çok kolay / özlü bir şekilde kullanabiliriz. Harita girişlerini değere göre sıralamak ve ekleme sırası yinelemesini koruyan bir LinkedHashMap oluşturmak için akışları kullanabilirsiniz .

Örneğin:

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

Ters sipariş için aşağıdakileri değiştirin:

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)

ile

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey).reversed()

Bu yorum yapılan sürüm için teşekkürler. Bir soru: Kullanmanın Entry.comparingByValue()(yukarıdaki assylias yanıtının stackoverflow.com/a/22132422/1480587 olarak ) ya comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)da kullandığınız fark nedir? Değerler aynı ise anahtarları da karşılaştırdığınızı anlıyorum, değil mi? Sıralama aynı değere sahip öğelerin sırasını koruduğunu fark ettim - eğer anahtarlar daha önce sıralanmışsa, anahtarlara göre sıralama gerekli mi?
Peter T.

6

Given Haritası

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

Haritayı artan değere göre sıralayın

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

Haritayı istenen sıradaki değere göre sıralayın

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

Çıktı:

{yazılım = 50, teknoloji = 70, ABD = 100, iş = 200, fırsat = 200}

{jobs = 200, fırsat = 200, ABD = 100, teknoloji = 70, yazılım = 50}


Sanırım harita azaltmak gerçekten "Azaltılmış" .... iyi bir çözüm.
ha9u63ar

thanks @ ha9u63ar
Arpan Saini

İşe yarıyor ama bir HashMap'te öğelerin sırasının nasıl oyuna girdiğini anlamıyorum?
Ali Tou

5

Bağlama bağlı olarak, java.util.LinkedHashMap<T>hangi öğelerin haritaya yerleştirilme sırasını yeniden kullanır . Aksi takdirde, değerleri doğal sıralarına göre sıralamanız gerekiyorsa, üzerinden sıralanabilecek ayrı bir Liste tutmanızı öneririm Collections.sort().


Neden -1 olduğunu anlamıyorum, şu ana kadar LinkedHashMap muhtemelen benim için en iyi çözüm, sadece atmak ve yeni bir LinkedHashMap oluşturmak için ne kadar pahalı olduğunu anlamaya çalışıyorum.
NobleUplift

5

Yana TreeMap <> çalışmıyor eşit olabilir değerler için, bunu kullandım:

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

Sen koymak isteyebilirsiniz listeyi bir de LinkedHashMap , ama sadece bunu hemen tekrarlatacak gidiyoruz, o gereksiz olduğunu ...


bu doğru fakat karşılaştırıcınız eşit değer davasını ele almıyor
Sebastien Lorber

5

Bu çok karmaşık. Haritaların bunları Değer'e göre sıralamak gibi bir iş yapmaması gerekiyordu. En kolay yol, kendi Sınıfınızı oluşturmaktır, böylece gereksiniminize uyar.

Örneğin daha aşağıda * olan yerde bir karşılaştırıcı TreeMap eklemeniz gerekir. Ancak java API ile karşılaştırıcıya değerleri değil, yalnızca anahtarları verir. Burada belirtilen tüm örnekler 2 Haritaya dayanmaktadır. Bir Karma ve bir yeni Ağaç. Hangisi tuhaf.

Örnek:

Map<Driver driver, Float time> map = new TreeMap<Driver driver, Float time>(*);

Haritayı şu şekilde bir set halinde değiştirin:

ResultComparator rc = new ResultComparator();
Set<Results> set = new TreeSet<Results>(rc);

Sınıf yaratacaksınız 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;
    }
}

ve Karşılaştırıcı sınıfı:

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

Bu şekilde kolayca daha fazla bağımlılık ekleyebilirsiniz.

Ve son nokta olarak basit yineleyici ekleyeceğim:

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 koduna göre, jenerikler kullanan ve hem artan hem de azalan sıralamayı destekleyen bir harita sıralama yöntemleri.

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

Sonra tekrar belki daha iyi bir çözüm sadece kendi kendine sıralama haritası kullanmak olacaktır, durumda org.apache.commons.collections.bidimap.TreeBidiMap
Maxim Veksler

4

İşte bir OO çözümü (yani, staticyöntemleri kullanmaz ):

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

Bu vesile ile kamu malı bağışlanmıştır.


4

Afaik'in en temiz yolu, haritayı değere göre sıralamak için koleksiyonları kullanmaktır:

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

Yinelenen değerlere sahip çiftlere sahip sıralanmış bir haritaya sahip olmak için bazı basit değişiklikler. Karşılaştırma yönteminde (sınıf ValueComparator) değerler eşit olduğunda 0 döndürmeyin, ancak 2 anahtarı karşılaştırmanın sonucunu döndürün. Anahtarlar haritada farklıdır, bu nedenle yinelenen değerleri (anahtarlara göre sıralanır) tutmayı başarabilirsiniz. Yukarıdaki örnek şu şekilde değiştirilebilir:

    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

Elbette Stephen'ın çözümü gerçekten harika, ama Guava'yı kullanamayanlar için:

İşte bir haritaya göre değer sıralamak için benim çözümüm. Bu çözüm, aynı değerin iki katı olduğu vb.

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

Exec: http://www.ideone.com/dq3Lu

Çıktı:

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

Umarım bazı milletlere yardımcı olur


3

Yinelenen anahtarlarınız ve yalnızca küçük bir veri kümeniz (<1000) varsa ve kodunuz performans açısından kritik değilse aşağıdakileri yapabilirsiniz:

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 kodun girdisidir .

SortOutputMap değişkeni , verileri yinelendiğinde azalan sırada içerecektir. Sırayı değiştirmek için, if ifadesinde> öğesini <olarak değiştirin.

En hızlı sıralama değil, ancak herhangi bir ek bağımlılık olmadan işi yapar.

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.