TreeMap diurutkan berdasarkan nilai


138

Saya ingin menulis sebuah komparator yang akan membiarkan saya mengurutkan TreeMap berdasarkan nilai alih-alih pengurutan alami default.

Saya mencoba sesuatu seperti ini, tetapi tidak dapat menemukan apa yang salah:

import java.util.*;

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

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

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

Saya kira apa yang saya tanyakan adalah: Bisakah saya Map.Entrylulus ke komparator?


mungkin ini akan membantu Anda stackoverflow.com/questions/109383/…
Inv3r53


Jawaban:


181

Anda tidak dapat TreeMapmengurutkan sendiri pada nilai, karena itu bertentangan dengan SortedMapspesifikasi:

A Mapyang selanjutnya memberikan pemesanan total pada kuncinya .

Namun, dengan menggunakan koleksi eksternal, Anda selalu dapat mengurutkan Map.entrySet()sesuka Anda, baik dengan kunci, nilai, atau bahkan kombinasi (!!) dari keduanya.

Berikut adalah metode umum yang mengembalikan SortedSetdari Map.Entry, Mapdengan nilai yang Comparable:

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

Sekarang Anda dapat melakukan hal berikut:

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

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

Perhatikan bahwa hal-hal yang funky akan terjadi jika Anda mencoba untuk memodifikasi baik SortedSetitu sendiri, atau di Map.Entrydalam, karena ini bukan lagi "tampilan" dari peta aslinya seperti entrySet()sekarang.

Secara umum, kebutuhan untuk mengurutkan entri peta berdasarkan nilainya tidak lazim.


Catatan ==untukInteger

Pembanding asli Anda membandingkan Integermenggunakan ==. Ini hampir selalu salah, karena ==dengan Integeroperan adalah persamaan referensi, bukan persamaan nilai.

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

Pertanyaan-pertanyaan Terkait


5
jika Anda menambahkan map.put ("D", 2); hasilnya masih "[C = 1, B = 2, A = 3]" dan bukan "[C = 1, B = 2, D = 2, A = 3]"
Igor Milla

3
Perbaiki: int res = e2.getValue (). BandingkanTo (e1.getValue ()); kembali res! = 0? res: 1;
beshkenadze

new Integer (0) == new Integer (0) Anda membandingkan referensi ke objek, dan Anda membuat dua objek! Keluarannya benar-benar benar dan dapat diprediksi.
Yuriy Chernyshov

2
"Secara umum, kebutuhan untuk mengurutkan entri peta berdasarkan nilainya adalah hal yang tidak biasa." Misalnya jika saya ingin mengambil 3 orang tertua di peta {"ana" = 37, "peter" = 43, "john" = 4, "mary" = 15, "Matt" = 78}
fartagaintuxedo

@igormilla Ada alasan sederhana, mengapa kode Anda berfungsi: Autoboxing menggunakan Integer.valueOf. Dan itu memiliki cache instance untuk -128 hingga 127!
pelawak

75

jawaban poligenelubricants hampir sempurna. Ini memiliki satu bug penting. Ini tidak akan menangani entri peta yang nilainya sama.

Kode ini: ...

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

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

Akan menghasilkan:

ape:1
frog:2
pig:3

Perhatikan bagaimana sapi kita menghilang karena berbagi nilai "1" dengan kera kita: O!

Modifikasi kode ini memecahkan masalah itu:

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

2
-1. AFASIK tidak ada Setimplementasi yang mengandung elemen lebih dari satu kali. Anda baru saja melanggar batasan itu. Dari SortedSetAPI: Perhatikan bahwa urutan dikelola oleh satu set diurutkan harus konsisten dengan equals ... . Solusinya adalah mengubah ke Listimplementasi.
dacwe

4
@dacwe nilai yang sama bukan kunci
bellum

1
@bellum: Kita berbicara tentang Sets di sini. Karena Comparatormelanggar Setkontrak Set.remove, Set.containsdll tidak berfungsi ! Lihat contoh ini di ideone .
dacwe

3
Sekadar catatan, jika Anda beralih int res= e1.getValue().compareTo(e2.getValue());ke int res= e2.getValue().compareTo(e1.getValue());, Anda memiliki urutan nilai menurun, bukan naik.
tugcem

6
Saya lebih suka kembali res != 0 ? res : e1.getKey().compareTo(e2.getKey())untuk mempertahankan urutan kunci dengan nilai yang sama.
Marco Lackovic

51

Di Jawa 8:

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

18

Sebuah TreeMapyang selalu diurutkan oleh kunci, hal lain adalah mustahil. A Comparatorhanya memungkinkan Anda untuk mengontrol bagaimana kunci diurutkan.

Jika Anda menginginkan nilai yang diurutkan, Anda harus mengekstraknya menjadi Listdan mengurutkan itu.


10

Ini tidak dapat dilakukan dengan menggunakan a Comparator, karena akan selalu mendapatkan kunci peta untuk dibandingkan. TreeMaphanya dapat mengurutkan berdasarkan kunci.


2
jika TreeMap hanya akan meneruskan kunci ke Comparator, apakah layak jika saya membuat referensi TreeMap di konstruktor pembanding, kemudian menggunakan kunci untuk mendapatkan nilainya, seperti ini (tidak yakin bagaimana cara meneruskan dengan referensi): class byValue mengimplementasikan Comparator {TreeMap theTree; public byValue (TreeMap theTree) {this.theTree = theTree; } public int bandingkan (String k1, String k2) {// gunakan metode getKey dari TreeMap ke nilai}}
vito huang

1
@vito: tidak, karena biasanya salah satu dari dua kunci tersebut belum ada di peta dan Anda tidak akan bisa mendapatkan nilainya.
Joachim Sauer

Bukankah ini contoh melakukan ini dalam Comparator of TreeMap? (meskipun, seperti yang disebutkan di atas, ini merusak spesifikasi SortedMapyang menentukan pengurutan berdasarkan kunci) beginnersbook.com/2014/07/…
Marcus

@ Marcus: yang Comparatormenggunakan peta yang ada untuk mendapatkan nilai yang akan diurutkan. Dengan kata lain, ini tidak dapat mengurutkan nilai arbitrer yang dimasukkan ke dalam TreeMapnanti, hanya nilai yang sudah ada di Peta asli.
Joachim Sauer

5

Jawaban Olof bagus, tapi butuh satu hal lagi sebelum sempurna. Dalam komentar di bawah jawabannya, dacwe (dengan benar) menunjukkan bahwa implementasinya melanggar kontrak Compare / Equals for Sets. Jika Anda mencoba memanggil berisi atau menghapus entri yang jelas di set, set tidak akan mengenalinya karena kode yang memungkinkan entri dengan nilai yang sama untuk ditempatkan di set. Jadi, untuk memperbaikinya, kita perlu menguji kesetaraan antara kunci:

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

"Perhatikan bahwa pengurutan yang dipelihara oleh himpunan yang diurutkan (apakah ada atau tidak pembanding eksplisit disediakan) harus konsisten dengan sama jika himpunan yang diurutkan adalah untuk mengimplementasikan antarmuka Set dengan benar ... antarmuka Set didefinisikan dalam istilah operasi yang sama , tetapi set yang diurutkan melakukan semua perbandingan elemen menggunakan metode bandingkanTo (atau bandingkan), jadi dua elemen yang dianggap sama dengan metode ini, dari sudut pandang set yang diurutkan, adalah sama . " ( http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html )

Karena awalnya kita mengabaikan persamaan untuk memaksa himpunan menambahkan entri bernilai sama, sekarang kita harus menguji kesetaraan di kunci agar himpunan benar-benar mengembalikan entri yang Anda cari. Ini agak berantakan dan jelas bukan set yang dimaksudkan untuk digunakan - tetapi berhasil.


3

Saya tahu posting ini secara khusus meminta penyortiran TreeMap berdasarkan nilai, tetapi bagi kita yang tidak terlalu peduli dengan implementasi tetapi menginginkan solusi yang membuat koleksi tetap diurutkan saat elemen ditambahkan, saya akan menghargai umpan balik pada TreeSet berbasis larutan. Untuk satu, elemen tidak mudah diambil oleh kunci, tetapi untuk kasus penggunaan yang saya miliki (menemukan kunci n dengan nilai terendah), ini bukan persyaratan.

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

2

Banyak orang mendengar anjuran untuk menggunakan List dan saya lebih suka menggunakannya juga

berikut adalah dua metode yang Anda perlukan untuk mengurutkan entri Peta menurut nilainya.

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

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

            List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
            Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
            return entries;
        }

0
import java.util.*;

public class Main {

public static void main(String[] args) {
    TreeMap<String, Integer> initTree = new TreeMap();
    initTree.put("D", 0);
    initTree.put("C", -3);
    initTree.put("A", 43);
    initTree.put("B", 32);
    System.out.println("Sorted by keys:");
    System.out.println(initTree);
    List list = new ArrayList(initTree.entrySet());
    Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
        @Override
        public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
            return e1.getValue().compareTo(e2.getValue());
        }
    });
    System.out.println("Sorted by values:");
    System.out.println(list);
}
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.