Sortir Peta <Kunci, Nilai> menurut nilai

1635

Saya relatif baru ke Jawa, dan sering menemukan bahwa saya perlu mengurutkan Map<Key, Value>nilai-nilai.

Karena nilainya tidak unik, saya menemukan diri saya mengubah keySetmenjadi array, dan mengurutkan array melalui sortir dengan komparator kustom yang mengurutkan pada nilai yang terkait dengan kunci.

Apakah ada cara yang lebih mudah?

Lii
sumber
24
Peta tidak dimaksudkan untuk diurutkan, tetapi diakses dengan cepat. Nilai yang sama dengan objek mematahkan batasan peta. Gunakan set entri, suka List<Map.Entry<...>> list =new LinkedList(map.entrySet())dan seperti Collections.sort ....itu.
Hannes
1
Kasus di mana ini mungkin muncul ketika kami mencoba menggunakan Penghitung di Jawa (Peta <Objek, Integer>). Menyortir berdasarkan jumlah kejadian kemudian akan menjadi operasi yang umum. Bahasa seperti Python memiliki struktur data Counter bawaan. Untuk cara implementasi alternatif di Jawa, berikut adalah contohnya
demongolem
7
Ada banyak kasus penggunaan untuk peta yang diurutkan, itu sebabnya Anda memiliki TreeMap dan ConcurrentSkipListMap di jdk.
alobodzk
1
TreeMap dan ConcurrentSkipListMap urutkan berdasarkan kunci. Pertanyaannya adalah tentang pengurutan berdasarkan nilai.
Peter

Jawaban:

900

Ini versi ramah-generik:

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;
    }
}
Halaman Carter
sumber
10
Senang ini bisa membantu. John, LinkedHashMap penting untuk solusi karena memberikan urutan iterasi yang dapat diprediksi.
Halaman Carter
3
@ buzz3791 Benar. Itu akan menjadi kasus dalam algoritma pengurutan apa pun. Mengubah nilai node dalam struktur selama pengurutan menciptakan hasil yang tidak terduga (dan hampir selalu buruk).
Halaman Carter
3
@Sheagorath Saya sudah mencobanya di Android dan berhasil juga. Ini bukan masalah platform spesifik, mengingat Anda menggunakan versi Java 6. Sudahkah Anda menerapkan Comparable dengan benar di objek nilai Anda?
saiyancoder
6
Tidakkah seharusnya versi Java 8 digunakan forEachOrderedsebagai ganti forEach, karena dokumen forEachnegara menyatakan: "Perilaku operasi ini secara eksplisit tidak deterministik."?
merampok
1
benar-benar merobek ini, tetapi dikreditkan @CarterPage dalam komentar (itu akan tetap dalam proyek open source). Terima kasih banyak.
Pantai Nathan
420

Catatan penting:

Kode ini dapat pecah dalam berbagai cara. Jika Anda bermaksud menggunakan kode yang disediakan, pastikan untuk membaca komentar dan juga untuk mengetahui implikasinya. Misalnya, nilai tidak lagi dapat diambil dengan kunci mereka. ( getselalu kembali null.)


Tampaknya jauh lebih mudah daripada semua hal di atas. Gunakan TreeMap sebagai berikut:

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

Keluaran:

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}
pengguna157196
sumber
18
Tidak lagi ( stackoverflow.com/questions/109383/… ). Juga, mengapa ada pemeran untuk Double? Bukankah seharusnya begitu return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))?
Stephen
12
@Stephen: Tidak. Dalam hal ini semua kunci sama dengan nilai dijatuhkan (perbedaan antara sama dan perbandingan dengan referensi). Selain itu: Bahkan kode ini memiliki masalah dengan urutan berikut map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);
steffen
43
Komparator yang digunakan untuk treemap tidak konsisten dengan yang sama (lihat javadox sortMap). Ini berarti menarik barang-barang dari peta pohon tidak akan berfungsi. sort_map.get ("A") akan mengembalikan nol. Itu berarti penggunaan treemap ini rusak.
mR_fr0g
87
Kalau-kalau itu tidak jelas bagi orang-orang: solusi ini mungkin tidak akan melakukan apa yang Anda inginkan jika Anda memiliki beberapa kunci pemetaan dengan nilai yang sama - hanya satu dari kunci-kunci itu akan muncul dalam hasil yang diurutkan.
Maxy-B
63
Louis Wasserman (ya, salah satu orang Google Guava), sebenarnya sedikit tidak menyukai jawaban ini: "Itu pecah dalam beberapa cara yang benar-benar membingungkan jika Anda bahkan melihatnya lucu. Jika peta dukungan berubah, itu akan rusak. Jika beberapa tombol peta ke nilai yang sama, itu akan pecah. Jika Anda memanggil dapatkan pada kunci yang tidak ada di peta dukungan, itu akan rusak. Jika Anda melakukan apa pun yang akan menyebabkan pencarian terjadi pada kunci yang tidak ada dalam peta - panggilan Map.equals, berisiKey, apa pun - itu akan pecah dengan jejak tumpukan yang sangat aneh. " plus.google.com/102216152814616302326/posts/bEQLDK712MJ
haylem
339

Java 8 menawarkan jawaban baru: ubah entri menjadi arus, dan gunakan kombinator pembanding dari Map.Entry:

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

Ini akan memungkinkan Anda mengonsumsi entri yang diurutkan dalam urutan nilai naik. Jika Anda ingin nilai menurun, cukup membalikkan komparator:

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

Jika nilainya tidak sebanding, Anda dapat melewati komparator eksplisit:

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

Anda kemudian dapat melanjutkan untuk menggunakan operasi aliran lain untuk mengkonsumsi data. Misalnya, jika Anda ingin top 10 di peta baru:

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

Atau cetak ke System.out:

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue())
   .forEach(System.out::println);
Brian Goetz
sumber
Bagus, tapi bagaimana dengan penggunaan parallelStream()dalam kasus ini?
Benj
11
Akan bekerja secara paralel, namun, Anda mungkin menemukan bahwa biaya menggabungkan peta untuk menggabungkan hasil parsial terlalu mahal dan versi paralel mungkin tidak berkinerja sebaik yang Anda harapkan. Tetapi itu berhasil dan menghasilkan jawaban yang benar.
Brian Goetz
Terima kasih atas saran Anda yang bermanfaat. Itu persis apa yang saya ingin tahu, meskipun itu tergantung pada jenis kunci apa yang Anda gunakan dan begitu banyak parameter ... Yang penting adalah "itu berfungsi dan menghasilkan jawaban yang benar".
Benj
2
tidakkah Anda harus menggunakan compareByValue dalam contoh top10?
Leo
1
@Benj akan berhasil dalam hal mengekstraksi 10 besar, tetapi peta yang dihasilkan tidak akan lagi dipesan.
OrangeDog
211

Tiga jawaban 1 baris ...

Saya akan menggunakan Google Collections Guava untuk melakukan ini - jika nilai ComparableAnda maka Anda dapat menggunakannya

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

Yang akan membuat fungsi (objek) untuk peta [yang mengambil salah satu kunci sebagai input, mengembalikan nilai masing-masing], dan kemudian menerapkan urutan alami (sebanding) dengan mereka [nilai-nilai].

Jika mereka tidak sebanding, maka Anda harus melakukan sesuatu di sepanjang garis

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

Ini dapat diterapkan ke TreeMap (sebagai Orderingekstensi Comparator), atau LinkedHashMap setelah pengurutan

NB : Jika Anda akan menggunakan TreeMap, ingatlah bahwa jika perbandingan == 0, maka item tersebut sudah ada dalam daftar (yang akan terjadi jika Anda memiliki beberapa nilai yang membandingkan yang sama). Untuk meringankan ini, Anda dapat menambahkan kunci Anda ke komparator seperti itu (dengan anggapan bahwa kunci dan nilai Anda Comparable):

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

= Terapkan pemesanan alami untuk nilai yang dipetakan oleh kunci, dan gabungkan bahwa dengan pemesanan alami kunci

Perhatikan bahwa ini masih tidak berfungsi jika kunci Anda dibandingkan dengan 0, tetapi ini harus cukup untuk sebagian besar comparableitem (seperti hashCode, equalsdancompareTo sering disinkronkan ...)

Lihat Memesan.onResultOf () dan Functions.forMap () .

Penerapan

Jadi sekarang kita memiliki pembanding yang melakukan apa yang kita inginkan, kita perlu mendapatkan hasil darinya.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Sekarang ini kemungkinan besar akan berhasil, tetapi:

  1. perlu dilakukan mengingat peta selesai yang lengkap
  2. Jangan coba pembanding di atas pada TreeMap; tidak ada gunanya mencoba membandingkan kunci yang dimasukkan ketika tidak memiliki nilai sampai setelah put, yaitu, itu akan pecah sangat cepat

Poin 1 adalah sedikit pemecah kesepakatan bagi saya; koleksi google sangat malas (yang bagus: Anda dapat melakukan hampir semua operasi dalam sekejap; pekerjaan nyata dilakukan ketika Anda mulai menggunakan hasilnya), dan ini membutuhkan penyalinan secara keseluruhan peta!

Jawaban "Lengkap" / Peta yang disortir langsung berdasarkan nilai

Jangan khawatir; jika Anda cukup terobsesi untuk memiliki peta "langsung" yang diurutkan dengan cara ini, Anda tidak dapat menyelesaikan tidak satu tetapi keduanya (!) dari masalah di atas dengan sesuatu yang gila seperti berikut:

Catatan: Ini telah berubah secara signifikan pada Juni 2012 - kode sebelumnya tidak pernah bisa berfungsi: HashMap internal diperlukan untuk mencari nilai-nilai tanpa membuat loop tak terbatas antara TreeMap.get()-> compare()dan compare()->get()

import static org.junit.Assert.assertEquals;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import com.google.common.base.Functions;
import com.google.common.collect.Ordering;

class ValueComparableMap<K extends Comparable<K>,V> extends TreeMap<K,V> {
    //A map for doing lookups on the keys for comparison so we don't get infinite loops
    private final Map<K, V> valueMap;

    ValueComparableMap(final Ordering<? super V> partialValueOrdering) {
        this(partialValueOrdering, new HashMap<K,V>());
    }

    private ValueComparableMap(Ordering<? super V> partialValueOrdering,
            HashMap<K, V> valueMap) {
        super(partialValueOrdering //Apply the value ordering
                .onResultOf(Functions.forMap(valueMap)) //On the result of getting the value for the key from the map
                .compound(Ordering.natural())); //as well as ensuring that the keys don't get clobbered
        this.valueMap = valueMap;
    }

    public V put(K k, V v) {
        if (valueMap.containsKey(k)){
            //remove the key in the sorted set before adding the key again
            remove(k);
        }
        valueMap.put(k,v); //To get "real" unsorted values for the comparator
        return super.put(k, v); //Put it in value order
    }

    public static void main(String[] args){
        TreeMap<String, Integer> map = new ValueComparableMap<String, Integer>(Ordering.natural());
        map.put("a", 5);
        map.put("b", 1);
        map.put("c", 3);
        assertEquals("b",map.firstKey());
        assertEquals("a",map.lastKey());
        map.put("d",0);
        assertEquals("d",map.firstKey());
        //ensure it's still a map (by overwriting a key, but with a new value) 
        map.put("d", 2);
        assertEquals("b", map.firstKey());
        //Ensure multiple values do not clobber keys
        map.put("e", 2);
        assertEquals(5, map.size());
        assertEquals(2, (int) map.get("e"));
        assertEquals(2, (int) map.get("d"));
    }
 }

Ketika kami meletakkan, kami memastikan bahwa peta hash memiliki nilai untuk komparator, dan kemudian dimasukkan ke TreeSet untuk disortir. Tapi sebelum itu kita periksa peta hash untuk melihat bahwa kuncinya sebenarnya bukan duplikat. Selain itu, pembanding yang kami buat juga akan menyertakan kunci sehingga nilai duplikat tidak menghapus kunci non-duplikat (karena perbandingan ==). 2 item ini sangat penting untuk memastikan kontrak peta disimpan; jika Anda pikir Anda tidak menginginkan itu, maka Anda hampir pada titik membalikkan peta sepenuhnya (ke Map<V,K>).

Konstruktor perlu disebut sebagai

 new ValueComparableMap(Ordering.natural());
 //or
 new ValueComparableMap(Ordering.from(comparator));
Stephen
sumber
Hai @Stephen, dapatkah Anda memberikan contoh cara menggunakan Pemesanan? Saya melihat kode sumber Memesan, dan benar-benar tidak tahu apa yang mengembalikan .natural (). OnResultOf (...)! Kode sumbernya adalah "publik <F> Memesan <F> onResultOf", saya bahkan tidak tahu cara kompilasi! Paling penting, bagaimana cara menggunakan "<F> Memesan <F>" untuk mengurutkan peta? Apakah itu pembanding atau sesuatu? Terima kasih.
smallufo
Orderinghanya kaya Comparator. Saya sudah mencoba mengomentari setiap contoh (huruf miring di bawah masing-masing). "alami" menunjukkan bahwa objeknya adalah Comparable; itu seperti ComparableComparator dari apache common. onResultOfberlaku fungsi untuk item yang dibandingkan. Jadi jika Anda memiliki fungsi yang menambahkan 1 ke integer, maka natural().onResultOf(add1Function).compare(1,2)akhirnya akan melakukan2.compareTo(3)
Stephen
ImmutableSortedMap.copyOf melempar IllegalArgumentException jika ada nilai duplikat di peta asli.
lbalazscs
@Ibalazscs Ya itu akan - Anda harus dapat menggunakan ImmutableSetMultiMapatau ImmutableListMultiMapmengandung koleksi variabel duplikat.
Stephen
1
Terima kasih untuk ini, saya menggunakan solusi Anda dalam satu proyek. Saya pikir ada masalah di put: untuk berperilaku seperti peta perlu mengembalikan nilai yang sebelumnya terkait dengan kunci, jika ada, tetapi seperti ini tidak akan pernah dilakukan. Solusi yang saya gunakan adalah mengembalikan nilai yang dihapus jika ada.
alex
185

Dari 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;
}
devinmoore
sumber
16
Daftar yang akan disortir adalah "LinkedList baru" ?? Wah. Untungnya Collections.sort () membuang daftar ke array terlebih dahulu, untuk menghindari kesalahan seperti ini (tapi tetap saja, membuang ArrayList ke array harus lebih cepat daripada melakukan hal yang sama untuk LinkedList).
Dimitris Andreou
tidak dapat mengonversi dari Iterator ke TernaryTree.Iterator
lisak
4
@ gg.kaspersky Saya tidak mengatakan "buruk untuk mengurutkan LinkedList", tetapi LinkedList itu sendiri adalah pilihan yang buruk di sini, terlepas dari pengurutan. Jauh lebih baik menggunakan ArrayList, dan untuk poin tambahan, ukurannya tepat di map.size (). Lihat juga code.google.com/p/memory-measurer/wiki/... biaya rata-rata per elemen di ArrayList: 5 byte biaya rata-rata per elemen di LinkedList: 24 byte. Untuk ArrayList yang berukuran tepat, biaya rata-rata adalah 4 byte. Artinya, LinkedList membutuhkan ENAM kali jumlah memori yang dibutuhkan ArrayList. Ini hanya mengasapi
Dimitris Andreou
2
menggunakan nilai-nilai di atas telah disortir dalam urutan menaik. Bagaimana cara menyortir turun?
ram
1
Ganti o1 dan o2 untuk mengurutkan turun.
Soheil
68

Dengan Java 8, Anda dapat menggunakan api stream untuk melakukannya dengan cara yang jauh lebih sedikit:

Map<K, V> sortedMap = map.entrySet().stream()
                         .sorted(Entry.comparingByValue())
                         .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
assylias
sumber
Bagaimana cara mengurutkannya dalam urutan terbalik?
Vlad Holubiev
6
menemukan solusi -Collections.reverseOrder(comparing(Entry::getValue))
Vlad Holubiev
1
Saya rasa saya melihat kesalahan ketik di sana - bukankah "toMap" disebut sebagai "Collectors.toMap ()"?
Jake Stokes
1
@JakeStokes Atau gunakan impor statis :-)
assylias
6
Cara yang lebih baik untuk mengurutkan berdasarkan nilai entri dalam urutan terbalik adalah:Entry.comparingByValue(Comparator.reverseOrder())
Gediminas Rimsa
31

Mengurutkan kunci membutuhkan Pembanding untuk mencari setiap nilai untuk setiap perbandingan. Solusi yang lebih scalable akan menggunakan entriSet secara langsung, sejak saat itu nilai akan segera tersedia untuk setiap perbandingan (walaupun saya belum mendukung ini dengan angka).

Inilah versi generik dari hal semacam itu:

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

Ada beberapa cara untuk mengurangi rotasi memori untuk solusi di atas. ArrayList pertama yang dibuat misalnya dapat digunakan kembali sebagai nilai kembali; ini membutuhkan penindasan terhadap beberapa peringatan umum, tetapi mungkin layak untuk kode pustaka yang dapat digunakan kembali. Juga, Pembanding tidak harus dialokasikan kembali pada setiap doa.

Berikut adalah versi yang lebih efisien meskipun kurang menarik:

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

Terakhir, jika Anda perlu mengakses informasi yang disortir secara terus-menerus (bukan hanya menyortir sesekali), Anda dapat menggunakan multi-peta tambahan. Beri tahu saya jika Anda membutuhkan detail lebih lanjut ...

voli
sumber
Versi kedua bisa lebih ringkas jika Anda mengembalikan Daftar <Map. Masukkan <K, V >> Ini juga membuatnya lebih mudah untuk beralih dan mendapatkan kedua kunci dan nilai-nilai tanpa harus melakukan banyak tambahan untuk sampai ke peta. Ini semua dengan asumsi Anda baik-baik saja dengan kode ini tidak aman. Jika peta dukungan atau daftar diurutkan dibagi dalam lingkungan multithreaded, semua taruhan dimatikan.
Mike Miller
26

Perpustakaan commons-collections berisi solusi yang disebut TreeBidiMap . Atau, Anda bisa melihat di Google Collections API. Ini memiliki TreeMultimap yang dapat Anda gunakan.

Dan jika Anda tidak ingin menggunakan kerangka kerja ini ... mereka datang dengan kode sumber.

p3t0r
sumber
Anda tidak harus menggunakan koleksi umum. Java dilengkapi dengan java.util.TreeMap sendiri.
yoliho
2
ya, tetapi TreeMap jauh lebih tidak fleksibel ketika menyortir bagian nilai dari mapentries.
p3t0r
9
Masalahnya dengan BidiMap adalah ia menambahkan batasan relasi 1: 1 antara kunci dan nilai-nilai untuk membuat relasi tidak dapat dibalik (mis. Kunci dan nilai harus unik). Ini berarti Anda tidak dapat menggunakan ini untuk menyimpan sesuatu seperti objek jumlah kata karena banyak kata akan memiliki jumlah yang sama.
Doug
26

Saya telah melihat jawaban yang diberikan, tetapi banyak dari mereka lebih rumit daripada yang dibutuhkan atau menghapus elemen peta ketika beberapa kunci memiliki nilai yang sama.

Berikut adalah solusi yang menurut saya lebih baik:

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

Perhatikan bahwa peta diurutkan dari nilai tertinggi ke terendah.

Anthony
sumber
6
MASALAH: jika Anda ingin menggunakan peta yang dikembalikan nanti, misalnya untuk memeriksa apakah peta itu mengandung elemen tertentu, Anda akan selalu salah, karena pembanding khusus Anda! Solusi yang mungkin: ganti baris terakhir dengan: kembalikan LinkedHashMap <K, V> yang baru (diurutkanByValues);
Erel Segal-Halevi
Ini terlihat solusi bersih bagi saya, kecuali fakta yang ditunjukkan oleh @ErelSegalHalevi, memeriksa apakah nilai yang ada di Peta tidak akan mungkin saat Anda menentukan komparator. map.put ("1", "One"); map.put ("2", "Two"); map.put ("3", "Three"); map.put ("4", "Empat"); map.put ("5", "Five"); map.containsKey ("1") akan selalu mengembalikan false, jika Anda mengembalikan objek baru di fungsi sortByValues ​​() seperti mengembalikan TreeMap <K, V> (sortByValues) baru; memecahkan masalah. Terima kasih Abhi
abhi
kurang lebih sama dengan jawaban user157196 dan Carter Page. Carter Page's berisi perbaikan LinkedHashMap
Kirby
Baris ke-4 dari solusinya adalah int compare = map.get (k1) .compareTo (map.get (k2)); jika Anda memerlukan pesanan naik
www.Decompiler.com
19

Untuk mencapai ini dengan fitur-fitur baru di 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());
}

Entri diurutkan berdasarkan nilainya menggunakan pembanding yang diberikan. Atau, jika nilai Anda setara satu sama lain, tidak diperlukan pembanding eksplisit:

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

Daftar yang dikembalikan adalah snapshot dari peta yang diberikan pada saat metode ini dipanggil, sehingga tidak akan mencerminkan perubahan berikutnya ke yang lain. Untuk tampilan peta yang dapat diputar langsung:

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

Iterable yang dikembalikan menciptakan snapshot segar dari peta yang diberikan setiap kali iterated, jadi kecuali modifikasi bersamaan, itu akan selalu mencerminkan keadaan peta saat ini.

gdejohn
sumber
Ini mengembalikan Daftar Entri daripada peta yang diurutkan berdasarkan nilai. Versi lain yang mengembalikan peta: stackoverflow.com/a/22132422/829571
assylias
17

Buat komparator khusus dan gunakan sambil membuat objek TreeMap baru.

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

    }
}

Gunakan kode di bawah ini di fungsi utama Anda

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

Keluaran:

{B=75, D=50, C=50, A=35}
Sujan Reddy A
sumber
Dalam hal nilainya sama, saya mengubah baris "return 1" untuk membandingkan kunci: "return ((String) o1) .compareTo ((String) o2);"
gjgjgj
14

Sementara saya setuju bahwa kebutuhan konstan untuk mengurutkan peta mungkin adalah bau, saya pikir kode berikut adalah cara termudah untuk melakukannya tanpa menggunakan struktur data yang berbeda.

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

}

Dan berikut ini adalah unit test yang memalukan:

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

}

Hasilnya adalah daftar Map.Entry objek yang diurutkan, dari mana Anda bisa mendapatkan kunci dan nilai.

Lyudmil
sumber
Metode ini jauh lebih mudah dan lebih intuitif daripada membuat objek Map <V, List <K>> dengan efek yang hampir sama. Nilai-nilai tidak benar-benar seharusnya menjadi kunci dalam objek Peta, apa yang sebenarnya Anda cari adalah daftar dalam situasi ini, IMHO.
Jeff Wu
Solusi ini tidak bekerja dengan banyak nilai, itu kacau dengan jumlah saya (nilai yang terkait dengan masing-masing kunci)
Sam Levin
1
Itu aneh. Bisakah Anda menguraikan? Apa output Anda dan apa output yang Anda harapkan?
Lyudmil
12

Gunakan pembanding generik seperti:

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));
    }
}
RoyalBigorno
sumber
11

Jawaban yang paling banyak dipilih tidak berfungsi ketika Anda memiliki 2 item yang setara. TreeMap meninggalkan nilai yang sama.

exmaple: map yang tidak disortir

kunci / nilai: D / 67.3
kunci / nilai: A / 99.5
kunci / nilai: B / 67.4
kunci / nilai: C / 67.5
kunci / nilai: E / 99.5

hasil

kunci / nilai: A / 99.5
kunci / nilai: C / 67.5
kunci / nilai: B / 67.4
kunci / nilai: D / 67.3

Jadi tinggalkan E !!

Bagi saya itu berfungsi dengan baik untuk menyesuaikan pembanding, jika sama dengan tidak mengembalikan 0 tetapi -1.

dalam contoh:

class ValueComparator mengimplementasikan Comparator {

Dasar peta; ValueComparator publik (Map base) {this.base = base; }

public int bandingkan (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;
}

}}

sekarang kembali:

peta yang tidak disortir:

kunci / nilai: D / 67.3
kunci / nilai: A / 99.5
kunci / nilai: B / 67.4
kunci / nilai: C / 67.5
kunci / nilai: E / 99.5

hasil:

kunci / nilai: A / 99.5
kunci / nilai: E / 99.5
kunci / nilai: C / 67.5
kunci / nilai: B / 67.4
kunci / nilai: D / 67.3

sebagai tanggapan terhadap Aliens (2011 nov. 22): Saya menggunakan solusi ini untuk peta Integer Id dan nama, tetapi idenya sama, jadi mungkin kode di atas tidak benar (saya akan menulisnya dalam ujian dan memberi Anda kode yang benar), ini adalah kode untuk pengurutan Peta, berdasarkan solusi di atas:

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


}

dan ini adalah kelas tes (saya baru saja mengujinya, dan ini berfungsi untuk Integer, String Map:

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

di sini adalah kode untuk Pembanding Peta:

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

dan ini adalah testcase untuk ini:

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

keberanian Anda dapat membuat ini jauh lebih umum, tetapi saya hanya membutuhkannya untuk 1 case (Peta)

michel.iamit
sumber
Anda benar, ada beberapa kesalahan pada kode yang saya berikan pada awalnya! Saya harap hasil edit terbaru saya akan membantu Anda.
michel.iamit
9

Alih-alih menggunakan Collections.sortkarena beberapa saya sarankan menggunakan Arrays.sort. Sebenarnya yang Collections.sortdilakukan adalah sesuatu seperti ini:

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

Itu hanya memanggil toArraydaftar dan kemudian menggunakan Arrays.sort. Dengan cara ini semua entri peta akan disalin tiga kali: sekali dari peta ke daftar sementara (baik itu LinkedList atau ArrayList), kemudian ke array sementara dan akhirnya ke peta baru.

Solusi saya membatalkan langkah ini karena tidak membuat LinkedList yang tidak perlu. Ini kodenya, ramah-generik dan kinerja-optimal:

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;
}
ciamej
sumber
8

Ini adalah variasi dari jawaban Anthony, yang tidak berfungsi jika ada nilai duplikat:

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

Perhatikan bahwa itu agak di udara bagaimana menangani null.

Satu keuntungan penting dari pendekatan ini adalah ia benar-benar mengembalikan Peta, tidak seperti beberapa solusi lain yang ditawarkan di sini.

Roger
sumber
Itu salah, metode saya berfungsi jika ada nilai duplikat. Saya telah menggunakannya dengan peta yang memiliki lebih dari 100 kunci dengan nilai "1".
Anthony
8

Pendekatan Terbaik

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

Keluaran

java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93
Nilesh Jadav
sumber
7

Masalah besar. Jika Anda menggunakan jawaban pertama (Google membawa Anda ke sini), ubah komparator untuk menambahkan klausa yang sama, jika tidak, Anda tidak bisa mendapatkan nilai dari sort_map dengan kunci:

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
    }
cuneyt
sumber
Sekarang ketika Anda menambahkan dua entri dengan nilai yang sama mereka akan digabung, Anda hanya harus mengembalikan 0 jika Anda yakin bahwa objeknya sama (sama)
Masood_mj
7

Sudah ada banyak jawaban untuk pertanyaan ini, tetapi tidak ada yang memberi saya apa yang saya cari, implementasi peta yang mengembalikan kunci dan entri yang diurutkan berdasarkan nilai yang terkait, dan mempertahankan properti ini karena kunci dan nilai dimodifikasi di peta. Dua pertanyaan lain menanyakan hal ini secara khusus.

Saya membuat contoh ramah umum yang memecahkan kasus penggunaan ini. Implementasi ini tidak menghormati semua kontrak antarmuka Peta, seperti mencerminkan perubahan nilai dan penghapusan dalam set yang kembali dari keySet () dan entrySet () di objek asli. Saya merasa solusi seperti itu akan terlalu besar untuk dimasukkan dalam jawaban Stack Overflow. Jika saya berhasil membuat implementasi yang lebih lengkap, mungkin saya akan mempostingnya ke Github dan kemudian menghubungkannya dalam versi terbaru dari jawaban ini.

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;
    }
}
David Bleckmann
sumber
Jika Sebanding dan Pembanding tidak diizinkan lalu bagaimana cara melakukannya?
Ved Prakash
Tidak yakin jika saya mengerti kasus penggunaan Anda, mungkin Anda bisa menguraikannya. Jika objek yang ingin Anda gunakan sebagai nilai tidak sebanding maka Anda perlu mengonversinya menjadi objek yang.
David Bleckmann
6

Entri Terlambat.

Dengan munculnya Java-8, kita dapat menggunakan stream untuk manipulasi data dengan cara yang sangat mudah / ringkas. Anda dapat menggunakan stream untuk mengurutkan entri peta berdasarkan nilai dan membuat LinkedHashMap yang mempertahankan iterasi urutan penyisipan .

Misalnya:

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

Untuk pemesanan terbalik, ganti:

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

dengan

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey).reversed()
Pankaj Singhal
sumber
Terima kasih untuk versi komentar ini. Satu pertanyaan: Apa perbedaan penggunaan Entry.comparingByValue()(seperti jawaban assylias di atas stackoverflow.com/a/22132422/1480587 ) atau comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)yang Anda gunakan? Saya mengerti Anda juga membandingkan kunci jika nilainya identik, bukan? Saya perhatikan bahwa pengurutan menjaga urutan elemen dengan nilai yang sama - jadi apakah pengurutan menurut kunci diperlukan jika kunci sudah diurutkan sebelumnya?
Peter T.
6

Peta yang Diberikan

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

Urutkan peta berdasarkan nilai dalam urutan menaik

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

Urutkan peta berdasarkan nilai dalam urutan menurun

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

Keluaran:

{perangkat lunak = 50, teknologi = 70, USA = 100, pekerjaan = 200, peluang = 200}

{jobs = 200, peluang = 200, USA = 100, teknologi = 70, perangkat lunak = 50}

Arpan Saini
sumber
Saya kira peta-mengurangi benar-benar "Mengurangi" itu .... solusi yang bagus.
ha9u63ar
terima kasih @ ha9u63ar
Arpan Saini
Ini bekerja tetapi saya tidak mengerti bagaimana urutan elemen berperan dalam HashMap?
Ali Tou
5

Bergantung pada konteksnya, menggunakan java.util.LinkedHashMap<T>yang mengingat urutan item ditempatkan ke dalam peta. Kalau tidak, jika Anda perlu mengurutkan nilai berdasarkan pemesanan alami mereka, saya akan merekomendasikan mempertahankan Daftar terpisah yang dapat diurutkan melalui Collections.sort().

Ryan Delucchi
sumber
Saya tidak mengerti mengapa ini -1, sejauh ini LinkedHashMap mungkin merupakan solusi terbaik bagi saya, saya hanya mencoba mencari tahu seberapa mahal untuk membuang dan membuat LinkedHashMap baru.
NobleUplift
5

Sejak TreeMap <> tidak berfungsi untuk nilai yang bisa sama, saya menggunakan ini:

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

Anda mungkin ingin meletakkan daftar di LinkedHashMap , tetapi jika Anda hanya akan mengulanginya segera, itu berlebihan ...

malix
sumber
itu benar tetapi pembanding Anda tidak menangani kasus nilai sama
Sebastien Lorber
5

Ini terlalu rumit. Peta tidak seharusnya melakukan pekerjaan seperti menyortir berdasarkan Nilai. Cara termudah adalah membuat Kelas Anda sendiri sehingga sesuai dengan kebutuhan Anda.

Dalam contoh yang lebih rendah, Anda seharusnya menambahkan TreeMap sebagai pembanding di tempat *. Tetapi dengan java API hanya memberikan kunci pembanding, bukan nilai. Semua contoh yang dinyatakan di sini didasarkan pada 2 Peta. Satu hash dan satu Tree baru. Aneh sekali.

Contoh:

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

Jadi ubah peta menjadi seperangkat dengan cara ini:

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

Anda akan membuat kelas 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;
    }
}

dan kelas pembanding:

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

Dengan cara ini Anda dapat dengan mudah menambahkan lebih banyak dependensi.

Dan sebagai poin terakhir saya akan menambahkan iterator sederhana:

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

Berdasarkan kode @devinmoore, metode penyortiran peta menggunakan obat generik dan mendukung pemesanan naik dan turun.

/**
 * 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
}
Maxim Veksler
sumber
Kemudian lagi mungkin solusi yang lebih baik adalah dengan hanya menggunakan peta penyortiran diri, dalam kasus ini gunakan org.apache.commons.collections.bidimap.TreeBidiMap
Maxim Veksler
4

Berikut ini adalah solusi OO (yaitu, tidak menggunakan staticmetode):

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

Dengan ini disumbangkan ke domain publik.

Dave Jarvis
sumber
4

Afaik cara yang paling bersih adalah menggunakan koleksi untuk mengurutkan peta berdasarkan nilai:

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

Beberapa perubahan sederhana untuk memiliki peta yang diurutkan dengan pasangan yang memiliki nilai duplikat. Dalam metode bandingkan (kelas ValueComparator) ketika nilainya sama, jangan mengembalikan 0 tetapi mengembalikan hasil membandingkan 2 kunci. Kunci berbeda di peta sehingga Anda berhasil menjaga nilai duplikat (yang diurutkan berdasarkan kunci dengan cara). Jadi contoh di atas dapat dimodifikasi seperti ini:

    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;
        }
      }
    }
dimkar
sumber
4

Tentu solusi Stephen benar-benar hebat, tetapi bagi mereka yang tidak bisa menggunakan Jambu:

Inilah solusi saya untuk mengurutkan berdasarkan nilai peta. Solusi ini menangani kasus di mana ada dua kali nilai yang sama dll ...

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

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

Hasil:

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

Semoga ini bisa membantu beberapa orang

Sebastien Lorber
sumber
3

Jika Anda memiliki kunci duplikat dan hanya kumpulan data kecil (<1000) dan kode Anda tidak kritis kinerja, Anda bisa melakukan hal berikut:

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 adalah input ke kode.

Variabel diurutkanOutputMap akan berisi data dalam urutan menurun ketika diulangi. Untuk mengubah urutan, ubah saja> ke <dalam pernyataan if.

Bukan jenis tercepat tetapi melakukan pekerjaan tanpa ketergantungan tambahan.

nibor
sumber