TreeMap sắp xếp theo giá trị


137

Tôi muốn viết một trình so sánh cho phép tôi sắp xếp TreeMap theo giá trị thay vì thứ tự tự nhiên mặc định.

Tôi đã thử một cái gì đó như thế này, nhưng không thể tìm ra những gì đã sai:

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

Tôi đoán những gì tôi hỏi là: Tôi có thể được Map.Entrythông qua bộ so sánh không?


có lẽ điều này sẽ giúp bạn stackoverflow.com/questions/109383/ trên
Inv3r53


Câu trả lời:


179

Bạn không thể TreeMaptự sắp xếp các giá trị, vì điều đó bất chấp SortedMapđặc tả:

Một Mapcung cấp thêm tổng số thứ tự trên các khóa của nó .

Tuy nhiên, bằng cách sử dụng bộ sưu tập bên ngoài, bạn luôn có thể sắp xếp theo bất kỳ cách nào Map.entrySet()bạn muốn, theo khóa, giá trị hoặc thậm chí là kết hợp (!!) của cả hai.

Dưới đây là một phương pháp chung mà trả về một SortedSetsố Map.Entry, một cho Mapcó giá trị là 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;
}

Bây giờ bạn có thể làm như sau:

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

Lưu ý rằng những thứ thú vị sẽ xảy ra nếu bạn cố gắng sửa đổi SortedSetchính nó hoặc Map.Entrybên trong, bởi vì đây không còn là "chế độ xem" của bản đồ gốc entrySet().

Nói chung, nhu cầu sắp xếp các mục của bản đồ theo các giá trị của nó là không điển hình.


Lưu ý về ==choInteger

So sánh ban đầu của bạn so sánh Integerbằng cách sử dụng ==. Điều này hầu như luôn luôn sai, vì ==với Integertoán hạng là một đẳng thức tham chiếu, không phải là đẳng thức giá trị.

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

Câu hỏi liên quan


5
nếu bạn thêm map.put ("D", 2); kết quả vẫn là "[C = 1, B = 2, A = 3]" chứ không phải "[C = 1, B = 2, D = 2, A = 3]"
Igor Milla

3
Khắc phục: int res = e2.getValue (). So sánhTo (e1.getValue ()); trả lại độ phân giải! = 0? độ phân giải: 1;
beshkenadze

new Integer (0) == new Integer (0) Bạn so sánh một tham chiếu với một đối tượng và Bạn tạo hai đối tượng! Việc đưa ra là hoàn toàn chính xác và có thể dự đoán.
Yuriy Chernyshov

2
"Nói chung, nhu cầu sắp xếp các mục của bản đồ theo các giá trị của nó là không điển hình" Tôi là người mới bắt đầu ở đây nhưng tôi cảm thấy như đó là một việc rất phổ biến phải làm? Ví dụ: nếu tôi muốn lấy 3 người già nhất trong bản đồ {"ana" = 37, "peter" = 43, "john" = 4, "mary" = 15, "Matt" = 78}
fartagaintuxedo

@igormilla Có một lý do đơn giản, tại sao mã của bạn hoạt động: Autoboxing sử dụng Integer.valueOf. Và nó có bộ đệm của các phiên bản cho -128 đến 127!
jokster

75

câu trả lời polygenelubricants là gần như hoàn hảo. Nó có một lỗi quan trọng mặc dù. Nó sẽ không xử lý các mục bản đồ trong đó các giá trị là như nhau.

Mã này: ...

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

Sẽ xuất:

ape:1
frog:2
pig:3

Lưu ý cách con bò của chúng tôi biến mất khi nó chia sẻ giá trị "1" với con vượn của chúng tôi: O!

Việc sửa đổi mã này giải quyết vấn đề đó:

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 không có Settriển khai nào chứa một phần tử nhiều lần. Bạn vừa vi phạm ràng buộc đó. Từ SortedSetAPI: Lưu ý rằng thứ tự được duy trì bởi một bộ sắp xếp phải phù hợp với bằng ... . Giải pháp sẽ là thay đổi để Listthực hiện.
dacwe

4
@dacwe giá trị bằng nhau không phải là khóa
bellum

1
@bellum: Chúng ta đang nói về Sets ở đây. Vì Comparatorvi phạm Sethợp đồng Set.remove, Set.containsvv không hoạt động ! Kiểm tra ví dụ này tại ideone .
dacwe

3
Chỉ cần một lưu ý, nếu bạn chuyển int res= e1.getValue().compareTo(e2.getValue());sang int res= e2.getValue().compareTo(e1.getValue());, bạn có thứ tự giá trị giảm dần thay vì tăng dần.
tugcem

6
Tôi thà quay lại res != 0 ? res : e1.getKey().compareTo(e2.getKey())để giữ thứ tự của các khóa có giá trị bằng nhau.
Marco Lackovic

45

Trong 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

Một TreeMapluôn luôn được sắp xếp theo các phím, bất cứ thứ gì là không thể. A Comparatorchỉ cho phép bạn kiểm soát cách sắp xếp các phím.

Nếu bạn muốn các giá trị được sắp xếp, bạn phải trích xuất chúng thành một Listvà sắp xếp nó.


10

Điều này không thể được thực hiện bằng cách sử dụng một Comparator, vì nó sẽ luôn lấy chìa khóa của bản đồ để so sánh. TreeMapchỉ có thể sắp xếp theo chìa khóa.


2
nếu TreeMap sẽ chỉ chuyển khóa cho Trình so sánh, thì có khả thi không nếu tôi tạo tham chiếu của TreeMap trong hàm tạo của trình so sánh, sau đó sử dụng khóa để lấy giá trị, đại loại như thế này (không chắc chắn làm thế nào để chuyển qua tham chiếu): class byValue thực hiện Trình so sánh {TreeMap theTree; công khai bởiValue (TreeMap theTree) {this.theTree = theTree; } so sánh int int (Chuỗi k1, Chuỗi k2) {// sử dụng phương thức getKey của TreeMap với giá trị}}
vito huang

1
@vito: không, vì thường thì một trong hai khóa sẽ không có trên bản đồ và bạn sẽ không thể nhận được giá trị của nó.
Joachim Sauer

Đây không phải là một ví dụ về việc này trong Bộ so sánh của TreeMap sao? (mặc dù, như đã đề cập ở trên, điều này phá vỡ các đặc điểm kỹ thuật cho SortedMapđó quy định cụ thể sắp xếp theo phím) beginnersbook.com/2014/07/...
Marcus

@Marcus: Comparatorsử dụng bản đồ hiện có để lấy các giá trị sắp xếp theo. Nói cách khác, nó không thể sắp xếp các giá trị tùy ý đưa vào TreeMapsau này, chỉ các giá trị đã có trong Bản đồ gốc.
Joachim Sauer

5

Câu trả lời của Olof là tốt, nhưng nó cần một điều nữa trước khi nó hoàn hảo. Trong các bình luận bên dưới câu trả lời của mình, dacwe (chính xác) chỉ ra rằng việc triển khai của anh ta vi phạm hợp đồng So sánh / Bằng cho Bộ. Nếu bạn cố gắng gọi chứa hoặc xóa trên một mục nhập rõ ràng trong tập hợp, tập hợp sẽ không nhận ra nó vì mã cho phép các mục có giá trị bằng nhau được đặt trong tập hợp. Vì vậy, để khắc phục điều này, chúng ta cần kiểm tra sự bằng nhau giữa các khóa:

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

"Lưu ý rằng thứ tự được duy trì bởi một bộ được sắp xếp (có cung cấp bộ so sánh rõ ràng hay không) phải phù hợp với bằng nếu bộ được sắp xếp là để thực hiện đúng giao diện Set ... giao diện Set được xác định theo thuật ngữ của hoạt động bằng , nhưng một tập hợp được sắp xếp thực hiện tất cả các so sánh phần tử bằng phương thức so sánh (hoặc so sánh) của nó, vì vậy hai phần tử được coi là bằng nhau của phương thức này, từ quan điểm của tập đã sắp xếp, bằng nhau . " ( http://docs.oracle.com/javase/6/docs/api/java/util/Sortset.html )

Vì ban đầu chúng tôi đã bỏ qua sự bằng nhau để buộc tập hợp thêm các mục có giá trị bằng nhau, nên bây giờ chúng tôi phải kiểm tra sự bằng nhau trong các khóa để tập hợp thực sự trả về mục bạn đang tìm kiếm. Điều này hơi lộn xộn và chắc chắn không phải là cách các bộ được dự định sẽ được sử dụng - nhưng nó hoạt động.


3

Tôi biết bài đăng này đặc biệt yêu cầu sắp xếp TreeMap theo các giá trị, nhưng đối với những người trong chúng ta không thực sự quan tâm đến việc triển khai nhưng muốn có một giải pháp giúp bộ sưu tập được sắp xếp như các yếu tố được thêm vào, tôi sẽ đánh giá cao phản hồi về dựa trên TreeSet này giải pháp. Đối với một, các phần tử không dễ dàng được truy xuất bằng khóa, nhưng đối với trường hợp sử dụng mà tôi có trong tay (tìm các khóa n có giá trị thấp nhất), đây không phải là một yêu cầu.

  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

Rất nhiều người nghe được khuyến khích sử dụng Danh sách và tôi cũng thích sử dụng nó

Đây là hai phương pháp bạn cần sắp xếp các mục của Bản đồ theo các giá trị của chúng.

    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;
        }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.