TreeMap จัดเรียงตามค่า


137

ฉันต้องการเขียนตัวเปรียบเทียบที่จะให้ฉันเรียงลำดับ TreeMap ตามค่าแทนการเรียงตามค่าเริ่มต้นตามธรรมชาติ

ฉันลองอะไรเช่นนี้ แต่ไม่สามารถหาสิ่งที่ผิดพลาดได้:

import java.util.*;

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

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

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

ฉันเดาว่าฉันกำลังถามอะไรอยู่: ฉันจะMap.Entryส่งผ่านไปยังตัวเปรียบเทียบได้หรือไม่


อาจจะช่วยให้คุณstackoverflow.com/questions/109383/…
Inv3r53


ดูเพิ่มเติมที่: stackoverflow.com/questions/30425836/ …
พอลแจ็คสัน

คำตอบ:


179

คุณไม่TreeMapสามารถเรียงลำดับค่าได้เองเนื่องจากมันเป็นไปตามSortedMapข้อกำหนด:

A Mapที่ให้การสั่งซื้อทั้งหมดบนกุญแจเพิ่มเติม

อย่างไรก็ตามเมื่อใช้คอลเล็กชันภายนอกคุณสามารถเรียงลำดับได้Map.entrySet()ตามต้องการทั้งคีย์ค่าหรือแม้แต่การรวมกัน (!!) ของทั้งสอง

นี่เป็นวิธีการทั่วไปที่ส่งกลับSortedSetของMap.Entryการให้Mapที่มีค่าเป็นComparable:

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

ตอนนี้คุณสามารถทำสิ่งต่อไปนี้:

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

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

โปรดทราบว่าสิ่งที่ขี้ขลาดจะเกิดขึ้นหากคุณพยายามปรับเปลี่ยนทั้งSortedSetตัวมันเองหรือMap.Entryภายในเพราะจะไม่เป็น "มุมมอง" ของแผนที่ดั้งเดิมเช่นentrySet()นั้นอีกต่อไป

โดยทั่วไปแล้วความต้องการในการเรียงลำดับรายการแผนที่ตามค่าของมันนั้นผิดปกติ


หมายเหตุ==สำหรับInteger

เปรียบเทียบเดิมของคุณเปรียบเทียบโดยใช้Integer ==นี้เกือบจะไม่ถูกต้องเสมอเนื่องจาก==มีIntegerตัวถูกดำเนินการเป็นความเสมอภาคอ้างอิงไม่คุ้มค่าเท่าเทียมกัน

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

คำถามที่เกี่ยวข้อง


5
หากคุณเพิ่ม map.put ("D", 2); ผลลัพธ์ยังคงเป็น "[C = 1, B = 2, A = 3]" และไม่ใช่ "[C = 1, B = 2, D = 2, A = 3]"
Igor Milla

3
แก้ไข: int res = e2.getValue (). comparTo (e1.getValue ()); ผลตอบแทน res! = 0? res: 1;
beshkenadze

new Integer (0) == new Integer (0) คุณทำการเปรียบเทียบการอ้างอิงไปยังวัตถุและคุณสร้างสองวัตถุ! การนำออกใช้นั้นถูกต้องและสามารถคาดเดาได้
Yuriy Chernyshov

2
"โดยทั่วไปการพูดความจำเป็นในการจัดเรียงรายการแผนที่ตามค่าของมันนั้นผิดปกติ" ฉันเป็นผู้เริ่มต้นที่นี่ แต่ฉันรู้สึกว่ามันควรจะเป็นเรื่องธรรมดามากที่จะทำ? ตัวอย่างเช่นถ้าฉันต้องการดึง 3 คนที่เก่าแก่ที่สุดในแผนที่ {"ana" = 37, "peter" = 43, "john" = 4, "mary" = 15, "Matt" = 78}
fartagaintuxedo

@igormilla มีเหตุผลง่ายๆคือทำไมโค้ดของคุณถึงใช้งานได้: Autoboxing ใช้ Integer.valueOf และนั่นก็คือแคชของอินสแตนซ์สำหรับ -128 ถึง 127!
jokster

75

คำตอบของ polygenelubricants นั้นเกือบจะสมบูรณ์แบบ มันมีข้อผิดพลาดที่สำคัญอย่างหนึ่ง มันจะไม่จัดการรายการแผนที่ที่มีค่าเหมือนกัน

รหัสนี้: ...

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

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

จะส่งออก:

ape:1
frog:2
pig:3

สังเกตว่าวัวของเราหายตัวไปอย่างไรเมื่อมันแบ่งปันค่า "1" กับลิงของเรา: O!

การแก้ไขโค้ดนี้จะแก้ไขปัญหาที่เกิดขึ้น:

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

2
-1 AFASIK ไม่มีSetการนำไปใช้ประกอบด้วยองค์ประกอบมากกว่าหนึ่งครั้ง คุณเพิ่งละเมิดข้อ จำกัด นั้น จากSortedSetAPI: โปรดสังเกตว่าการสั่งซื้อการเก็บรักษาโดยชุดที่เรียงลำดับจะต้องสอดคล้องกับความเท่าเทียม ... การแก้ปัญหาคือการเปลี่ยนไปListใช้งาน
dacwe

4
@dacwe ค่าเท่ากันไม่ใช่คีย์
bellum

1
@bellum: เรากำลังพูดถึงเกี่ยวกับSetที่นี่ เนื่องจากComparatorละเมิดSetสัญญาSet.remove, Set.containsฯลฯไม่ทำงาน ! ตรวจสอบเรื่องนี้เช่นที่ ideone
dacwe

3
เพียงแค่ทราบถ้าคุณเปลี่ยนint res= e1.getValue().compareTo(e2.getValue());เป็นint res= e2.getValue().compareTo(e1.getValue());คุณมีลำดับค่าจากมากไปน้อยแทนที่จะขึ้นไป
tugcem

6
ฉันอยากกลับres != 0 ? res : e1.getKey().compareTo(e2.getKey())ไปรักษาลำดับของคีย์ด้วยค่าที่เท่ากัน
Marco Lackovic

46

ใน Java 8:

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

17

TreeMapถูกเสมอเรียงตามคีย์อะไรก็ได้ที่เป็นไปไม่ได้ A Comparatorช่วยให้คุณควบคุม วิธีการเรียงลำดับคีย์

หากคุณต้องการค่าที่เรียงคุณต้องแยกมันออกเป็นListและเรียงลำดับนั้น


10

สิ่งนี้ไม่สามารถทำได้โดยใช้ a Comparatorเนื่องจากจะได้รับคีย์ของแผนที่เพื่อเปรียบเทียบเสมอ TreeMapสามารถเรียงลำดับโดยคีย์เท่านั้น


2
ถ้า TreeMap จะส่งคีย์ไปที่ Comparator จะเป็นไปได้หรือไม่ถ้าฉันทำการอ้างอิงของ TreeMap ใน Constructor ของ Comparator จากนั้นใช้คีย์เพื่อรับค่าสิ่งนี้ (ไม่แน่ใจว่าจะผ่านการอ้างอิงได้อย่างไร): class byValue ใช้เครื่องมือเปรียบเทียบ {TreeMap theTree; สาธารณะโดยค่า (TreeMap theTree) {this.theTree = theTree; } การเปรียบเทียบ int สาธารณะ (String k1, String k2) {// ใช้วิธี getKey ของ TreeMap กับค่า}}
vito huang

1
@vito: ไม่เพราะโดยปกติแล้วหนึ่งในสองปุ่มจะยังไม่ได้อยู่ในแผนที่และคุณจะไม่สามารถรับค่าได้
Joachim Sauer

นี่ไม่ใช่ตัวอย่างของการทำสิ่งนี้ภายใน Comparator of TreeMap ใช่ไหม (แม้ว่าดังกล่าวข้างต้นสิ่งนี้จะทำลายข้อกำหนดSortedMapที่ระบุการเรียงลำดับตามคีย์) startsbook.com/2014/07/…
Marcus

@Marcus: ที่Comparatorใช้แผนที่ที่มีอยู่เพื่อรับค่าเรียงตาม กล่าวอีกนัยหนึ่งมันไม่สามารถจัดเรียงค่าตามอำเภอใจใส่ไว้ในTreeMapภายหลังเฉพาะค่าที่มีอยู่แล้วในแผนที่เดิม
โจอาคิมซาวเออร์

5

คำตอบโอลอฟเป็นสิ่งที่ดี แต่ต้องเป็นหนึ่งในสิ่งที่มากขึ้นก่อนที่มันจะสมบูรณ์แบบ ในความคิดเห็นด้านล่างคำตอบของเขา dacwe (ถูกต้อง) ชี้ให้เห็นว่าการใช้งานของเขาละเมิดสัญญาเปรียบเทียบ / เท่ากับสำหรับชุด หากคุณพยายามโทรมีหรือลบรายการที่อยู่ในชุดอย่างชัดเจนชุดจะไม่รู้จักเนื่องจากรหัสที่อนุญาตให้วางรายการที่มีค่าเท่ากันในชุด ดังนั้นเพื่อแก้ไขปัญหานี้เราจำเป็นต้องทดสอบความเท่าเทียมกันระหว่างปุ่มต่างๆ:

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

"โปรดทราบว่าการเรียงลำดับที่บำรุงรักษาโดยชุดเรียง (ไม่ว่าจะมีการเปรียบเทียบอย่างชัดเจนหรือไม่ก็ตาม) จะต้องสอดคล้องกับเท่ากับหากชุดเรียงลำดับเพื่อนำอินเตอร์เฟส Set ไปใช้อย่างถูกต้อง ... ชุดอินเตอร์เฟสถูกกำหนดในแง่ของการดำเนินการเท่ากับ แต่ชุดที่เรียงลำดับดำเนินการเปรียบเทียบองค์ประกอบทั้งหมดโดยใช้การเปรียบเทียบ (หรือเปรียบเทียบ) วิธีดังนั้นสององค์ประกอบที่ถือว่าเท่าเทียมกันโดยวิธีนี้คือจากมุมมองของชุดการเรียงที่เท่ากัน " ( http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html )

เนื่องจากเดิมเรามองข้ามความเสมอภาคเพื่อบังคับให้ชุดเพิ่มรายการที่มีคุณค่าเท่ากันตอนนี้เราต้องทดสอบความเท่าเทียมกันในคีย์เพื่อให้ชุดส่งคืนรายการที่คุณกำลังมองหา นี่เป็นเรื่องที่ค่อนข้างยุ่งและไม่แน่นอนว่าชุดจะถูกนำมาใช้อย่างไร - แต่มันก็ใช้ได้


3

ฉันรู้ว่าโพสต์นี้ขอเฉพาะการเรียงลำดับ TreeMap ตามค่า แต่สำหรับพวกเราที่ไม่สนใจเกี่ยวกับการนำไปใช้จริง ๆ แต่ต้องการโซลูชันที่ช่วยให้การรวบรวมเรียงลำดับตามองค์ประกอบที่เพิ่มเข้ามาฉันขอขอบคุณข้อเสนอแนะจาก TreeSet นี้ สารละลาย. สำหรับหนึ่งองค์ประกอบจะไม่สามารถเรียกคืนได้อย่างง่ายดายด้วยกุญแจ แต่สำหรับกรณีการใช้งานที่ฉันมีอยู่ในมือ (การค้นหาปุ่ม n ด้วยค่าต่ำสุด) นี่ไม่ใช่ข้อกำหนด

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

2

ผู้คนจำนวนมากได้ยินคำแนะนำให้ใช้ List และฉันก็ชอบที่จะใช้เช่นกัน

นี่คือสองวิธีที่คุณต้องเรียงลำดับรายการของแผนที่ตามค่าของพวกเขา

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

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

            List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
            Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
            return entries;
        }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.