Câu hỏi Sắp xếp Bản đồ theo giá trị


Tôi tương đối mới với Java, và thường thấy rằng tôi cần phải sắp xếp Map<Key, Value> trên các giá trị.

Vì các giá trị không phải là duy nhất, tôi thấy mình đang chuyển đổi keySet vào một arrayvà phân loại mảng đó thông qua sắp xếp mảng với một -bộ so sánh tùy chỉnh sắp xếp trên giá trị được liên kết với khóa.

Có cách nào dễ hơn không?


1379


gốc


Bản đồ không có nghĩa là sắp xếp, nhưng được truy cập nhanh. Các giá trị bằng nhau của đối tượng phá vỡ ràng buộc của bản đồ. Sử dụng tập hợp mục nhập, như List<Map.Entry<...>> list =new LinkedList(map.entrySet()) và Collections.sort .... theo cách đó. - Hannes
Một trường hợp điều này có thể phát sinh khi chúng ta cố gắng sử dụng một Counter trong Java (Map <Object, Integer>). Sắp xếp theo số lần xuất hiện sau đó sẽ là một hoạt động phổ biến. Một ngôn ngữ như Python có cấu trúc dữ liệu Counter được tích hợp sẵn. Đối với một cách thực hiện thay thế trong Java, đây là một ví dụ - demongolem
Có rất nhiều trường hợp sử dụng cho các bản đồ được sắp xếp, đó là lý do tại sao bạn có TreeMap và ConcurrentSkipListMap trong jdk. - alobodzk


Các câu trả lời:


Đây là một phiên bản thân thiện với chung:

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

783



Rất vui được điều này. John, LinkedHashMap quan trọng đối với giải pháp vì nó cung cấp thứ tự lặp lại có thể dự đoán được. - Carter Page
@ buzz3791 Đúng vậy. Đó sẽ là trường hợp trong bất kỳ thuật toán sắp xếp nào. Thay đổi giá trị của các nút trong một cấu trúc trong một sắp xếp tạo ra kết quả không thể đoán trước (và gần như luôn luôn xấu). - Carter Page
@ Sheagorath Tôi đã thử nó trong Android và nó hoạt động quá. Nó không phải là một vấn đề nền tảng cụ thể, xem xét bạn đang sử dụng phiên bản Java 6. Bạn đã triển khai chưa Có thể so sánh chính xác trong đối tượng giá trị của bạn? - saiyancoder
Không nên sử dụng phiên bản Java 8 forEachOrdered thay vì forEach, vì tài liệu của forEach nói: "Hành vi của hoạt động này rõ ràng là không xác định." - rob
hoàn toàn tách ra điều này, nhưng đã ghi nhận @CarterPage trong các nhận xét (dù sao nó cũng nằm trong dự án nguồn mở). cám ơn rất nhiều. - Nathan Beach


Lưu ý quan trọng:

Mã này có thể bị hỏng theo nhiều cách. Nếu bạn có ý định sử dụng mã được cung cấp, hãy chắc chắn để đọc các ý kiến ​​cũng như nhận thức được ý nghĩa. Ví dụ: các giá trị không còn có thể được truy lục bằng khóa của chúng nữa. (get luôn luôn trả về null.)


Nó có vẻ dễ dàng hơn nhiều so với tất cả những điều đã nói ở trên. Sử dụng TreeMap như sau:

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

Đầu ra:

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}

405



Không còn nữa (stackoverflow.com/questions/109383/…). Ngoài ra, tại sao có một diễn viên để đôi? Không phải nó chỉ là return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))? - Stephen
@Stephen: Không. Trong trường hợp này tất cả các khóa bằng giá trị sẽ bị giảm (sự khác biệt giữa bằng và so sánh theo tham chiếu). Ngoài ra: Ngay cả mã này có vấn đề với trình tự sau map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d); - steffen
Bộ so sánh được sử dụng cho sơ đồ trang web không phù hợp với bằng (xem sơ đồ javadox sắp xếp). Điều này có nghĩa là việc nghỉ hưu các mục từ bản đồ cây sẽ không hoạt động. selected_map.get ("A") sẽ trả về null. Điều đó có nghĩa là việc sử dụng treemap bị hỏng. - mR_fr0g
Chỉ trong trường hợp nó không rõ ràng với mọi người: giải pháp này có thể sẽ không làm những gì bạn muốn nếu bạn có nhiều ánh xạ khóa với cùng một giá trị - chỉ một trong những khóa đó sẽ xuất hiện trong kết quả được sắp xếp. - Maxy-B
Louis Wasserman (vâng, một trong những người Guava của Google), thực sự không thích câu trả lời này một chút: "Nó phá vỡ một số cách rất khó hiểu nếu bạn thậm chí nhìn vào nó buồn cười. Nếu bản đồ sao lưu thay đổi, nó sẽ phá vỡ. Nếu bạn gọi vào một khóa không có trong bản đồ sao lưu, nó sẽ bị phá vỡ. Nếu bạn làm bất cứ điều gì có thể khiến một tra cứu xảy ra trên một khóa không có trong bản đồ - một cuộc gọi Map.equals, containsKey, bất cứ điều gì - nó sẽ phá vỡ với dấu vết ngăn xếp thực sự kỳ lạ. " plus.google.com/102216152814616302326/posts/bEQLDK712MJ - haylem


Java 8 cung cấp một câu trả lời mới: chuyển đổi các mục vào một dòng, và sử dụng bộ phối hợp so sánh từ Map.Entry:

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

Điều này sẽ cho phép bạn tiêu thụ các mục được sắp xếp theo thứ tự tăng dần của giá trị. Nếu bạn muốn giá trị giảm dần, chỉ cần đảo ngược trình so sánh:

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

Nếu các giá trị không thể so sánh được, bạn có thể vượt qua một trình so sánh rõ ràng:

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

Sau đó, bạn có thể tiếp tục sử dụng các hoạt động luồng khác để tiêu thụ dữ liệu. Ví dụ: nếu bạn muốn 10 vị trí hàng đầu trong bản đồ mới:

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

Hoặc in ra System.out:

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

215



Đẹp, nhưng về việc sử dụng parallelStream() trong trường hợp này ? - Benj
Nó sẽ làm việc song song, tuy nhiên, bạn có thể thấy rằng chi phí kết hợp các bản đồ để kết hợp các kết quả một phần là quá đắt và phiên bản song song có thể không hoạt động tốt như bạn mong muốn. Nhưng nó hoạt động và đưa ra câu trả lời đúng. - Brian Goetz
Cảm ơn lời khuyên hữu ích của bạn. Đó là chính xác những gì tôi đã tự hỏi, mặc dù nó phụ thuộc vào loại khóa bạn sử dụng và rất nhiều tham số ... Điều quan trọng là "nó hoạt động và tạo ra câu trả lời đúng". - Benj
Làm thế nào tôi có thể sắp xếp theo các giá trị đang được liệt kê với các kích cỡ khác nhau? Tôi muốn sắp xếp theo kích thước danh sách giảm dần. - Pete
don 't bạn phải sử dụng compareByValue trong ví dụ top10? - Leo


Ba câu trả lời 1 dòng ...

tôi sẽ dùng Bộ sưu tập của Google  Trái ổi để làm điều này - nếu giá trị của bạn là Comparable sau đó bạn có thể sử dụng

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

Mà sẽ tạo ra một chức năng (đối tượng) cho bản đồ [mà lấy bất kỳ các phím như đầu vào, trả về giá trị tương ứng], và sau đó áp dụng tự nhiên (so sánh) đặt hàng cho họ [các giá trị].

Nếu chúng không thể so sánh được, thì bạn sẽ cần phải làm điều gì đó dọc theo các dòng

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

Chúng có thể được áp dụng cho TreeMap (như Ordering kéo dài Comparator), hoặc một LinkedHashMap sau khi phân loại

NB: Nếu bạn định sử dụng TreeMap, hãy nhớ rằng nếu so sánh == 0, thì mục đó đã có trong danh sách (điều này sẽ xảy ra nếu bạn có nhiều giá trị so sánh giống nhau). Để giảm bớt điều này, bạn có thể thêm khóa của bạn vào bộ so sánh như vậy (giả sử rằng các khóa và giá trị của bạn là Comparable):

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

= Áp dụng thứ tự tự nhiên cho giá trị được ánh xạ bằng khóa và hợp chất với thứ tự tự nhiên của khóa

Lưu ý rằng điều này sẽ vẫn không hoạt động nếu các khóa của bạn so sánh với 0, nhưng điều này phải đủ cho hầu hết comparable các mục (như hashCode, equals và compareTo thường được đồng bộ hóa ...)

Xem Ordering.onResultOf () và Functions.forMap ().

Thực hiện

Vì vậy, bây giờ mà chúng tôi đã có một so sánh mà làm những gì chúng ta muốn, chúng ta cần phải có được một kết quả từ nó.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Bây giờ điều này rất có thể sẽ hoạt động, nhưng:

  1. cần được thực hiện cho một bản đồ hoàn chỉnh
  2. Đừng thử so sánh ở trên trên TreeMap; không có điểm cố gắng so sánh khóa được chèn khi nó không có giá trị cho đến sau khi đặt, nghĩa là, nó sẽ phá vỡ rất nhanh

Điểm 1 là một chút của một đối phó-breaker cho tôi; bộ sưu tập google cực kỳ lười biếng (rất tốt: bạn có thể thực hiện khá nhiều thao tác ngay lập tức; công việc thực sự được thực hiện khi bạn bắt đầu sử dụng kết quả) và điều này yêu cầu sao chép toàn thể bản đồ!

Câu trả lời "Đầy đủ" / Bản đồ được sắp xếp trực tiếp theo giá trị

Đừng lo lắng; nếu bạn bị ám ảnh đủ với việc có một bản đồ "sống" được sắp xếp theo cách này, bạn có thể giải quyết không chỉ một mà cả (!) của các vấn đề trên với điều gì đó điên rồ như sau:

Lưu ý: Điều này đã thay đổi đáng kể vào tháng 6 năm 2012 - mã trước đó không bao giờ có thể hoạt động: HashMap nội bộ được yêu cầu để tra cứu các giá trị mà không tạo vòng lặp vô hạn giữa TreeMap.get() -> compare() và 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"));
    }
 }

Khi chúng tôi đặt, chúng tôi đảm bảo rằng bản đồ băm có giá trị cho bộ so sánh, và sau đó đặt vào TreeSet để sắp xếp. Nhưng trước đó chúng tôi kiểm tra bản đồ băm để thấy rằng khóa không thực sự là một bản sao. Ngoài ra, bộ so sánh mà chúng ta tạo cũng sẽ bao gồm khóa sao cho các giá trị trùng lặp không xóa các khóa không trùng lặp (do so sánh ==). 2 mục này là quan trọng để đảm bảo hợp đồng bản đồ được lưu giữ; nếu bạn nghĩ bạn không muốn điều đó, thì bạn gần như hoàn toàn đảo ngược toàn bộ bản đồ ( Map<V,K>).

Hàm khởi tạo sẽ cần được gọi là

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

207



Xin chào @Stephen, bạn có thể đưa ra ví dụ về cách sử dụng Đặt hàng không? Tôi nhìn vào mã nguồn của Ordering, và hoàn toàn không thể tìm ra cái gì .natural (). OnResultOf (...) trả về! Mã nguồn là "công <F> đặt hàng <F> onResultOf", tôi thậm chí không biết nó biên dịch như thế nào! Quan trọng nhất, làm thế nào để sử dụng "<F> Đặt hàng <F>" để sắp xếp một bản đồ? Nó là một bộ so sánh hay gì đó? Cảm ơn. - smallufo
Điều này thật tuyệt. Tôi muốn câu trả lời này được đặt dưới câu hỏi "Cách sắp xếp các khóa của bản đồ theo giá trị trong ổi", đó là những gì tôi đang tìm kiếm. Tôi đã điều chỉnh câu trả lời này thành: Danh sách cuối cùng <K> sortKeys = Ordering.natural (). OnResultOf (Functions.forMap (map)) .immutableSortedCopy (map.keySet ()); đó là những gì tôi cần. Cảm ơn! - John Lehmann
Nói về lớp ValueComparableMap, thực hiện câu trả lời khó nhất cho câu hỏi vì nó cung cấp một bản đồ được sắp xếp có thể thay đổi. Tôi không thấy làm thế nào điều này có thể làm việc. Các cuộc gọi đến super.get (key) sẽ tham khảo các so sánh cho cây traversal (đó là cách một cây được sắp xếp hoạt động, hoặc xem TreeMap mã nguồn). Và trình so sánh cần câu trả lời từ #get để trả về câu trả lời. Điều này sẽ kết thúc, dường như với tôi, trong một vòng lặp vô hạn. (Cũng có vấn đề là "điều này" không thể được nhắc tới trong hàm tạo, nhưng điều đó ít cơ bản hơn.) - Olivier Cailloux
@Oliver; tuyệt diệu! Tôi đã không nhận thấy rằng (đôi khi bạn thực sự cần phải phân tích trừu tượng ...). Tôi đã sửa cả hai vấn đề và thêm một số thử nghiệm nội tuyến. Câu hỏi tiếp theo là: làm cách nào tôi nhận được 48 phiếu bầu mà không có ai sử dụng mã? :) - Stephen
Cảm ơn vì điều này, tôi đã sử dụng giải pháp của bạn trong một dự án. Tôi nghĩ rằng có một vấn đề trong đặt mặc dù: để hành xử như một bản đồ nó cần phải trả lại giá trị trước đó liên kết với khóa, nếu nó tồn tại, nhưng như thế này nó sẽ không bao giờ làm. Giải pháp tôi đã sử dụng là trả về giá trị đã xóa nếu nó tồn tại. - alex


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

171



Danh sách được sắp xếp là "LinkedList mới" ?? Gee. Thankfully Collections.sort () đổ danh sách vào một mảng đầu tiên, để tránh chính xác loại lỗi này (nhưng vẫn còn, việc đổ một ArrayList vào một mảng sẽ nhanh hơn làm tương tự cho một LinkedList). - Dimitris Andreou
không có generics? :-( - TraderJoeChicago
@ gg.kaspersky Tôi không nói "đó là xấu để sắp xếp một LinkedList", nhưng rằng LinkedList chính nó là một lựa chọn xấu ở đây, bất kể phân loại. Nhiều tốt hơn để sử dụng một ArrayList, và cho thêm điểm, kích thước nó tại map.size () chính xác. Cũng thấy code.google.com/p/memory-measurer/wiki/…  chi phí trung bình cho mỗi phần tử trong ArrayList: 5 byte chi phí trung bình cho mỗi phần tử trong LinkedList: 24 byte. Đối với một ArrayList có kích thước chính xác, chi phí trung bình sẽ là 4 byte. Đó là, LinkedList mất SÁU số lần bộ nhớ mà ArrayList cần. Nó chỉ sưng lên - Dimitris Andreou
sử dụng các giá trị trên đã được sắp xếp theo thứ tự tăng dần. Làm thế nào để sắp xếp theo thứ tự giảm dần? - ram
Thay thế o1 và o2 để sắp xếp giảm dần. - Soheil


Việc sắp xếp các khóa yêu cầu Trình so sánh tra cứu từng giá trị cho từng so sánh. Một giải pháp có khả năng mở rộng hơn sẽ sử dụng entrySet trực tiếp, vì sau đó giá trị sẽ có sẵn ngay lập tức cho mỗi lần so sánh (mặc dù tôi đã không hỗ trợ điều này theo số).

Đây là một phiên bản chung của một thứ như vậy:

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

Có nhiều cách để giảm kích thước bộ nhớ cho giải pháp trên. Ví dụ, ArrayList được tạo đầu tiên có thể được tái sử dụng như một giá trị trả về; điều này sẽ đòi hỏi sự đàn áp của một số cảnh báo generics, nhưng nó có thể là giá trị nó cho mã thư viện có thể tái sử dụng. Ngoài ra, Comparator không phải được phân bổ lại ở mọi lời gọi.

Đây là một phiên bản kém hiệu quả mặc dù ít hấp dẫn hơn:

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

Cuối cùng, nếu bạn cần liên tục truy cập vào thông tin được sắp xếp (thay vì chỉ phân loại nó một lần trong một thời gian), bạn có thể sử dụng một bản đồ đa bổ sung. Hãy cho tôi biết nếu như bạn cần thêm chị tiết...


28



Phiên bản thứ hai có thể ngắn gọn hơn nếu bạn trả về Danh sách <Map.Entry <K, V >> Điều này cũng giúp việc lặp lại và lấy cả khóa và giá trị trở nên dễ dàng hơn mà không cần phải thực hiện thêm nhiều thứ nữa cho bản đồ. Đây là tất cả giả định bạn là ok với mã này là thread-không an toàn. Nếu bản đồ sao lưu hoặc danh sách được sắp xếp được chia sẻ trong môi trường đa luồng, tất cả các phiên cược sẽ bị tắt. - Mike Miller


Với Java 8, bạn có thể sử dụng suối api để làm điều đó một cách tiết kiệm hơn một cách đáng kể:

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

26



Làm thế nào để sắp xếp nó theo thứ tự ngược lại? - Vlad Holubiev
@VladGolubev comparing(Entry::getValue).reversed() - assylias
tìm thấy một giải pháp - Collections.reverseOrder(comparing(Entry::getValue)) - Vlad Holubiev
xem mã ví dụ này - Vlad Holubiev
@JakeStokes Hoặc sử dụng một nhập khẩu tĩnh :-) - assylias


Thư viện bộ sưu tập commons chứa một giải pháp có tên TreeBidiMap. Hoặc, bạn có thể xem API Google Collections. Nó có TreeMultimap mà bạn có thể sử dụng.

Và nếu bạn không muốn sử dụng các khuôn khổ này ... chúng đi kèm với mã nguồn.


25



Bạn không phải sử dụng bộ sưu tập commons. Java đi kèm với java.util.TreeMap của riêng nó. - yoliho
có, nhưng TreeMap là ít linh hoạt hơn khi phân loại trên giá trị một phần của mapentries. - p3t0r
Vấn đề với BidiMap là nó thêm ràng buộc quan hệ 1: 1 giữa các khóa và giá trị để làm cho mối quan hệ không thể đảo ngược (nghĩa là cả khóa và giá trị cần phải là duy nhất). Điều này có nghĩa là bạn không thể sử dụng điều này để lưu trữ một cái gì đó giống như một đối tượng đếm từ vì nhiều từ sẽ có cùng số lượng. - Doug


Tôi đã xem xét các câu trả lời nhất định, nhưng rất nhiều trong số chúng phức tạp hơn cần thiết hoặc loại bỏ các phần tử bản đồ khi một số khóa có cùng giá trị.

Đây là một giải pháp mà tôi nghĩ phù hợp hơn:

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

Lưu ý rằng bản đồ được sắp xếp từ giá trị cao nhất đến thấp nhất.


23



VẤN ĐỀ: nếu bạn muốn sử dụng bản đồ được trả về sau, ví dụ để kiểm tra xem nó có chứa một phần tử nào đó hay không, bạn sẽ luôn nhận được sai, vì bộ so sánh tùy chỉnh của bạn! Một giải pháp có thể có: thay thế dòng cuối cùng bằng cách: trả về LinkedHashMap mới <K, V> (được sắp xếpByValues); - Erel Segal-Halevi
Điều này có vẻ là một giải pháp sạch cho tôi, ngoại trừ thực tế là @ErelSegalHalevi đã chỉ ra, kiểm tra xem các giá trị tồn tại trong Bản đồ sẽ không thể thực hiện được khi bạn chỉ định trình so sánh. map.put ("1", "Một"); map.put ("2", "Two"); map.put ("3", "Three"); map.put ("4", "Four"); map.put ("5", "Five"); map.containsKey ("1") sẽ luôn trả về false, nếu bạn trả về đối tượng mới trong hàm sortByValues ​​() như trả về TreeMap mới <K, V> (sortByValues); giải quyết vấn đề. Cảm ơn Abhi - abhi
khá giống với câu trả lời của user157196 và Carter Page. Trang Carter chứa bản sửa lỗi LinkedHashMap - Kirby
Dòng thứ 4 của giải pháp nên được so sánh int = map.get (k1) .compareTo (map.get (k2)); nếu bạn cần thứ tự tăng dần - cosmolev


Để thực hiện điều này với các tính năng mới trong 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());
}

Các mục được sắp xếp theo giá trị của chúng bằng cách sử dụng bộ so sánh đã cho. Ngoài ra, nếu giá trị của bạn có thể so sánh lẫn nhau, không cần so sánh rõ ràng:

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

Danh sách trả về là một ảnh chụp nhanh của bản đồ đã cho tại thời điểm phương thức này được gọi, do đó, sẽ không phản ánh các thay đổi tiếp theo cho phương thức khác. Để có chế độ xem trực tiếp có thể lặp lại của bản đồ:

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

Trả về iterable tạo ra một bản chụp mới của bản đồ đã cho mỗi lần nó được lặp lại, do đó, việc chặn sửa đổi đồng thời, nó sẽ luôn luôn phản ánh trạng thái hiện tại của bản đồ.


17



Điều này trả về một danh sách các mục nhập chứ không phải là một bản đồ được sắp xếp theo giá trị. Phiên bản khác trả về bản đồ: stackoverflow.com/a/22132422/829571 - assylias