Sắp xếp Bản đồ <Khóa, Giá trị> theo các giá trị


1635

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

Vì các giá trị không phải là duy nhất, tôi thấy mình chuyển đổi keySetthành một arrayvà sắp xếp 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 theo giá trị được liên kết với khóa.

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


24
Một bản đồ không có nghĩa là được sắp xếp, nhưng truy cập nhanh. Các giá trị đối tượng bằng nhau phá vỡ các ràng buộc của bản đồ. Sử dụng bộ nhập, thích List<Map.Entry<...>> list =new LinkedList(map.entrySet())Collections.sort ....theo cách đó.
Hannes

1
Một trường hợp điều này có thể phát sinh khi chúng tôi cố gắng sử dụng Bộ đếm trong Java (Bản đồ <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. Đối với một cách triển khai thay thế trong Java, đây là một ví dụ
demongolem

7
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à ConcảnSkipListMap trong jdk.
alobodzk


1
Sắp xếp TreeMap và ConcurrencySkipListMap theo khóa. Câu hỏi là về sắp xếp theo giá trị.
Peter

Câu trả lời:


899

Đây là phiên bản thân thiện 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;
    }
}

10
Vui mừng điều này giúp. John, LinkedHashMap rất quan trọng đối với giải pháp vì nó cung cấp thứ tự lặp có thể dự đoán được.
Carter Trang

3
@ buzz3791 Đúng. Đó sẽ là trường hợp trong bất kỳ thuật toán sắp xếp. Thay đổi giá trị của các nút trong một cấu trúc trong khi sắp xếp sẽ tạo ra kết quả không thể đoán trước (và gần như luôn luôn xấu).
Carter Trang

3
@Sheagorath Tôi đã thử nó trong Android và nó cũng hoạt động. Đây không phải là vấn đề cụ thể về nền tảng, vì bạn đang sử dụng phiên bản Java 6. Bạn đã thực hiện So sánh chính xác trong đối tượng giá trị của bạn?
saiyancoder

6
Không nên sử dụng phiên bản Java 8 forEachOrderedthay vì forEach, vì các tài liệu của các forEachtrạng thái: "Hành vi của hoạt động này rõ ràng là không đặc biệt."?
cướp

1
hoàn toàn xé toạc điều này, nhưng đã ghi nhận @CarterPage trong các bình luận (dù sao nó cũng sẽ nằm trong một dự án nguồn mở). cám ơn rất nhiều.
Bãi biển Nathan

419

Lưu ý quan trọng:

Mã này có thể phá vỡ 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 bình luận cũng như nhận thức được các hàm ý. Ví dụ, các giá trị không còn có thể được lấy bằng khóa của chúng. ( getluôn luôn trả lại null.)


Có vẻ như 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}

18
Không còn nữa ( stackoverflow.com/questions/109383/ trên ). Ngoài ra, tại sao lại có một diễn viên thành Double? Không phải là nó nên return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))?
Stephen

12
@Stephen: Không. Trong trường hợp này, tất cả các khóa bằng giá trị đều bị loại bỏ (chênh lệch giữa bằng và so sánh theo tham chiếu). Ngoài ra: Ngay cả mã này cũng 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

43
Bộ so sánh được sử dụng cho treemap không phù hợp với bằng (xem javadox sortMap). Điều này có nghĩa là nghỉ hưu các mục từ bản đồ cây sẽ không hoạt động. sort_map.get ("A") sẽ trả về null. Điều đó có nghĩa là việc sử dụng treemap này bị hỏng.
mR_fr0g

87
Chỉ trong trường hợp 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 khóa ánh xạ tới cùng một giá trị - chỉ một trong số các khóa đó sẽ xuất hiện trong kết quả được sắp xếp.
Maxy-B

63
Louis Wasserman (vâng, một trong những người của Google Guava), thực sự không thích câu trả lời này khá nhiều: "Nó phá vỡ theo một số cách thực sự khó hiểu nếu bạn thậm chí nhìn nó buồn cười. Nếu bản đồ sao lưu thay đổi, nó sẽ bị hỏng. Nếu nhiều phím ánh xạ tới cùng một giá trị, nó sẽ bị hỏng. Nếu bạn gọi get on key không có trong bản đồ sao lưu, nó sẽ bị hỏng. Nếu bạn làm bất cứ điều gì có thể khiến việc tra cứu xảy ra với một khóa không có trong bản đồ - một cuộc gọi Map.equals, chứaKey, bất cứ điều gì - nó sẽ bị phá vỡ với dấu vết ngăn xếp thực sự kỳ lạ. " plus.google.com/102216152814616302326/posts/bEQLDK712MJ
haylem

339

Java 8 cung cấp một câu trả lời mới: chuyển đổi các mục nhập thành một luồng và sử dụng các bộ kết 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 bộ 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 bộ 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ến hành 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 top 10 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);

Đẹp, nhưng những gì về việc sử dụng parallelStream()trong trường hợp này?
Benj

11
Tuy nhiên, nó sẽ hoạt động song song, bạn có thể thấy rằng chi phí hợp nhất các bản đồ để kết hợp một phần kết quả là quá đắt và phiên bản song song có thể không hoạt động tốt như bạn hy vọng. Nhưng nó làm việc và tạo ra câu trả lời chính xác.
Brian Goetz

Cảm ơn lời khuyên hữu ích của bạn. Đó chính xác là 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 chính xác".
Benj

2
Bạn không phải sử dụng so sánhByValue trong ví dụ top10?
Leo

1
@Benj nó sẽ hoạt động trong việc trích xuất top 10, nhưng bản đồ kết quả sẽ không còn được đặt hàng.
OrangeDog

211

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

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

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

Việc này sẽ tạo một hàm (đối tượng) cho bản đồ [lấy bất kỳ khóa nào làm đầu vào, trả về giá trị tương ứng], sau đó áp dụng thứ tự tự nhiên (có thể so sánh) cho chúng [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 gì đó dọc theo dòng

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

Chúng có thể được áp dụng cho TreeMap (dưới dạng Orderingmở rộng Comparator) hoặc LinkedHashMap sau khi sắp xếp

Lưu ý : 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 mình 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ởi khóa và kết hợp 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 với 0, nhưng điều này là đủ cho hầu hết comparablecác mục (như hashCode, equalscompareTothường được đồng bộ hóa ...)

Xem Ordering.onResultOf ()Hàm.forMap () .

Thực hiện

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

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Bây giờ điều này rất có thể sẽ làm việc, nhưng:

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

Điểm 1 là một chút phá vỡ đối với tôi; bộ sưu tập google cực kỳ lười biếng (điều này 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 đòi hỏi phải sao chép toàn bộ bản đồ!

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

Đừng lo lắng mặc dù; nếu bạn bị ám ảnh đủ với việc sắp xếp một bản đồ "sống" theo cách này, bạn có thể giải quyết không chỉ một mà cả hai (!) các vấn đề trên với một thứ đ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()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 tôi tạo cũng sẽ bao gồm khóa để 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). Hai mục này rất quan trọng để đảm bảo giữ hợp đồng bản đồ; nếu bạn nghĩ rằng bạn không muốn điều đó, thì bạn gần như hoàn toàn đảo ngược bản đồ (sang Map<V,K>).

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

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

Xin chào @Stephen, bạn có thể cho một ví dụ về cách sử dụng Đặt hàng không? Tôi xem xét 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 khai <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 bản đồ? Nó là một so sánh hay cái gì đó? Cảm ơn.
smallufo

Orderingchỉ đơn giản là một người giàu có Comparator. Tôi đã thử bình luận từng ví dụ (chữ in nghiêng bên dưới mỗi ví dụ). "tự nhiên" chỉ ra rằng các đối tượng là Comparable; nó giống như so sánh chung của apache. onResultOfáp dụng một chức năng cho các mục được so sánh. Vì vậy, nếu bạn có một hàm đã thêm 1 vào một số nguyên, thì natural().onResultOf(add1Function).compare(1,2)cuối cùng sẽ thực hiện2.compareTo(3)
Stephen

ImmutableSorticMap.copyOf ném IllegalArgumentException nếu có các giá trị trùng lặp trong bản đồ gốc.
lbalazscs

@Ibalazscs Có, nó sẽ - Bạn sẽ có thể sử dụng ImmutableSetMultiMaphoặc ImmutableListMultiMapchứa bộ sưu tập các biến trùng lặp.
Stephen

1
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 đề được đặt ra: để hành xử như một bản đồ, nó cần trả về giá trị được liên kết trước đó với khóa, nếu nó tồn tại, nhưng như thế này thì nó sẽ không bao giờ làm được. Giải pháp tôi đã sử dụng là trả về giá trị bị loại bỏ nếu nó tồn tại.
alex

185

Từ http://www.programmersheaven.com/doad/49349/doad.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;
}

16
Danh sách được sắp xếp là "Danh sách liên kết mới" ?? Trời ạ. Rất may Collections.sort () kết xuất danh sách vào một mảng trước, để tránh chính xác loại lỗi này (nhưng vẫn, việc bỏ một ArrayList vào một mảng sẽ nhanh hơn so với thực hiện tương tự đối với LinkedList).
Dimitris Andreou

không thể chuyển đổi từ Iterator sang TernaryTree.Iterator
lisak

4
@ gg.kaspersky Tôi không nói "thật tệ khi sắp xếp một LinkedList", nhưng chính LinkedList là một lựa chọn tồi ở đây, bất kể việc sắp xếp. Nhiều tốt hơn để sử dụng một ArrayList, và cho các điểm phụ, kích thước nó ở chính xác map.size (). Đồng thời xem code.google.com/p/memory-measurer/wiki/ Chi phí trung bình cho mỗi phần tử trong ArrayList: chi phí trung bình 5 byte 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 lần so với dung lượng bộ nhớ mà ArrayList cần. Nó chỉ phình to
Dimitris Andreou

2
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 giảm dần?
ram

1
Thay o1 và o2 để sắp xếp giảm dần.
Soheil

68

Với Java 8, bạn có thể sử dụng các luồng api để thực hiện theo cách ít dài dòng hơn đáng kể:

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

Làm thế nào để sắp xếp nó theo thứ tự ngược lại?
Vlad Holubiev

6
tìm thấy một giải pháp -Collections.reverseOrder(comparing(Entry::getValue))
Vlad Holubiev

1
Tôi nghĩ rằng tôi thấy một lỗi đánh máy ở đó - không nên gọi "toMap" là "Collector.toMap ()"?
Jake Stokes

1
@JakeStokes Hoặc sử dụng nhập tĩnh :-)
assylias

6
Cách tốt hơn để sắp xếp theo giá trị mục nhập theo thứ tự ngược lại là:Entry.comparingByValue(Comparator.reverseOrder())
Gediminas Rimsa

31

Sắp xếp các khóa yêu cầu Bộ so sánh tìm kiếm từng giá trị cho mỗi so sánh. Một giải pháp có khả năng mở rộng hơn sẽ sử dụng trực tiếp mục Set, từ đó giá trị sẽ có sẵn ngay lập tức cho mỗi so sánh (mặc dù tôi không ủng hộ điều này bằng số).

Đây là một phiên bản chung của một điều 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 vòng quay bộ nhớ cho giải pháp trên. Ví dụ, ArrayList đầu tiên được tạo có thể được sử dụng lại làm giá trị trả về; điều này đòi hỏi phải loại bỏ một số cảnh báo chung chung, nhưng nó có thể đáng giá cho mã thư viện có thể sử dụng lại. Ngoài ra, Bộ so sánh không phải được phân bổ lại ở mỗi lần gọi.

Đây là một phiên bản hiệu quả hơn 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 truy cập liên tục vào thông tin đã sắp xếp (thay vì chỉ sắp xếp 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...


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 dễ dàng hơn và nhận được cả khóa và giá trị mà không phải thực hiện thêm nhiều bản đồ. Đây là tất cả giả sử bạn ổn với mã này là 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 cược sẽ bị tắt.
Mike Miller

26

Thư viện bộ sưu tập commons chứa một giải pháp gọi là TreeBidiMap . Hoặc, bạn có thể xem API Bộ sưu tập của Google. 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 khung này ... chúng đi kèm với mã nguồn.


Bạn không phải sử dụng bộ sưu tập chung. Java đi kèm với java.util.TreeMap của riêng nó.
yoliho

2
có, nhưng TreeMap kém linh hoạt hơn khi sắp xếp trên phần giá trị của các bản đồ.
p3t0r

9
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ì đó như một đối tượng đếm từ vì nhiều từ sẽ có cùng số đếm.
Doug

26

Tôi đã xem xét các câu trả lời đã cho, nhưng rất nhiều trong số chúng phức tạp hơn mức cần thiết hoặc loại bỏ các yếu 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.


6
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 yếu tố nào đó 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 khả thi: thay thế dòng cuối cùng bằng: return LinkedHashMap <K, V> mới (sortByValues);
Erel Segal-Halevi

Đây có vẻ là một giải pháp rõ ràng 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 hay không khi bạn chỉ định bộ so sánh. map.put ("1", "Một"); map.put ("2", "Hai"); map.put ("3", "Ba"); map.put ("4", "Bốn"); map.put ("5", "Năm"); 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 người dùng 157196 và Carter Page. Carter Page chứa bản sửa lỗi LinkedHashMap
Kirby

Dòng thứ 4 của giải pháp nên là int so sánh = map.get (k1) .compareTo (map.get (k2)); nếu bạn cần thứ tự tăng dần
www.Decompiler.com

19

Để 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 các 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 được 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 đối với bản đồ khác. Để xem trực tiếp bản đồ:

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

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


Điều này trả về một Danh sách các mục thay vì 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

17

Tạo bộ so sánh tùy chỉnh và sử dụng nó trong khi tạo đối tượng TreeMap mới.

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

    }
}

Sử dụng mã dưới đây trong func chính của bạn

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

Đầu ra:

{B=75, D=50, C=50, A=35}

Trong trường hợp các giá trị bằng nhau, tôi đã thay đổi dòng "return 1" để so sánh các khóa: "return ((String) o1) .compareTo ((String) o2);"
gjgjgj 17/03/19

14

Mặc dù tôi đồng ý rằng nhu cầu liên tục để sắp xếp bản đồ có thể là một mùi, tôi nghĩ rằng đoạn mã sau đây là cách dễ nhất để làm điều đó mà không cần sử dụng cấu trúc dữ liệu khác.

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

}

Và đây là một bài kiểm tra đơn vị không hoàn chỉnh đáng xấu hổ:

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

}

Kết quả là một danh sách được sắp xếp của các đối tượng Map.Entry, từ đó bạn có thể nhận được các khóa và giá trị.


Phương pháp này dễ dàng và trực quan hơn nhiều so với việc tạo một đối tượng Map <V, List <K >> với hiệu ứng tương tự. Các giá trị không thực sự được coi là khóa trong đối tượng Bản đồ, thứ bạn thực sự tìm kiếm là một danh sách trong tình huống này, IMHO.
Jeff Wu

Giải pháp này không hoạt động với nhiều giá trị, nó vặn với số đếm của tôi (giá trị được liên kết với mỗi khóa)
Sam Levin

1
Điều đó thật lạ. Bạn có thể giải thích? Đầu ra của bạn là gì và đầu ra bạn mong đợi là gì?
Lyudmil

12

Sử dụng một bộ so sánh chung chung như:

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

11

Câu trả lời được bình chọn nhiều nhất không hoạt động khi bạn có 2 mục bằng nhau. TreeMap bỏ các giá trị bằng nhau.

bản đồ: bản đồ chưa sắp xếp

khóa / giá trị: D / 67.3
khóa / giá trị: A / 99,5
khóa / giá trị: B / 67.4
khóa / giá trị: C / 67,5
khóa / giá trị: E / 99,5

các kết quả

khóa / giá trị: A / 99,5
khóa / giá trị: C / 67,5
khóa / giá trị: B / 67.4
khóa / giá trị: D / 67.3

Vì vậy, bỏ E !!

Đối với tôi, nó hoạt động tốt để điều chỉnh bộ so sánh, nếu nó bằng không trả về 0 mà là -1.

trong ví dụ:

lớp ValueComparator thực hiện Trình so sánh {

Bản đồ cơ sở; công khai ValueComparator (Cơ sở bản đồ) {this.base = base; }

công khai so sánh (Đối tượng a, Đối tượng 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;
}

}}

bây giờ nó trở lại:

bản đồ chưa sắp xếp:

khóa / giá trị: D / 67.3
khóa / giá trị: A / 99,5
khóa / giá trị: B / 67.4
khóa / giá trị: C / 67,5
khóa / giá trị: E / 99,5

các kết quả:

khóa / giá trị: A / 99,5
khóa / giá trị: E / 99,5
khóa / giá trị: C / 67,5
khóa / giá trị: B / 67.4
khóa / giá trị: D / 67.3

như một phản hồi cho Người ngoài hành tinh (tiểu thuyết năm 2011 22): Tôi đang sử dụng giải pháp này cho bản đồ và tên của Integer Id, nhưng ý tưởng là như nhau, vì vậy có thể mã ở trên không chính xác và cung cấp cho bạn mã chính xác), đây là mã để sắp xếp Bản đồ, dựa trên giải pháp trên:

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


}

và đây là lớp thử nghiệm (tôi vừa thử nó và nó hoạt động cho 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);
    }
}

đây là mã cho Bộ so sánh bản đồ:

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

và đây là bản thử nghiệm cho điều này:

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

về nguồn, bạn có thể làm cho nó chung chung hơn rất nhiều, nhưng tôi chỉ cần nó cho 1 trường hợp (Bản đồ)


bạn đã đúng, có một số lỗi trong mã tôi đã đưa ra lúc đầu! Tôi hy vọng chỉnh sửa gần đây của tôi sẽ giúp bạn.
michel.iamit

9

Thay vì sử dụng Collections.sortnhư một số người tôi khuyên bạn nên sử dụng Arrays.sort. Trên thực tế những gì Collections.sortlàm như thế này là:

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

Nó chỉ gọi toArraytrong danh sách và sau đó sử dụng Arrays.sort. Bằng cách này, tất cả các mục bản đồ sẽ được sao chép ba lần: một lần từ bản đồ vào danh sách tạm thời (có thể là LinkedList hoặc ArrayList), sau đó đến mảng tạm thời và cuối cùng là bản đồ mới.

Giải pháp của tôi đề xuất một bước này vì nó không tạo LinkedList không cần thiết. Đây là mã, thân thiện chung và hiệu suất tối ưu:

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

8

Đây là một biến thể của câu trả lời của Anthony, không hoạt động nếu có các giá trị trùng lặp:

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

Lưu ý rằng không nên xử lý null.

Một lợi thế quan trọng của phương pháp này là nó thực sự trả về một Bản đồ, không giống như một số giải pháp khác được cung cấp ở đây.


Không chính xác, phương thức của tôi hoạt động nếu có các giá trị trùng lặp. Tôi đã sử dụng nó với các bản đồ có hơn 100 khóa với giá trị "1".
Anthony

8

Cách tiếp cận tốt nhất

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

Đầu ra

java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93

7

Vấn đề lớn. Nếu bạn sử dụng câu trả lời đầu tiên (Google đưa bạn đến đây), hãy thay đổi bộ so sánh để thêm một mệnh đề bằng nhau, nếu không bạn không thể lấy các giá trị từ sort_map bằng các khóa:

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
    }

Bây giờ khi bạn thêm hai mục có giá trị bằng nhau, chúng sẽ được hợp nhất, bạn chỉ nên trả về 0 nếu bạn chắc chắn rằng các đối tượng là như nhau (bằng nhau)
Masood_mj

7

Đã có rất nhiều câu trả lời cho câu hỏi này, nhưng không có câu trả lời nào cho tôi những gì tôi đang tìm kiếm, việc triển khai bản đồ trả về các khóa và mục được sắp xếp theo giá trị liên quan và duy trì thuộc tính này khi các khóa và giá trị được sửa đổi trong bản đồ. Hai câu hỏi khác yêu cầu cụ thể.

Tôi đã đưa ra một ví dụ thân thiện chung để giải quyết trường hợp sử dụng này. Việc triển khai này không tôn trọng tất cả các hợp đồng của giao diện Bản đồ, chẳng hạn như phản ánh các thay đổi và loại bỏ giá trị trong các bộ trả về từ keyset () và entryset () trong đối tượng ban đầu. Tôi cảm thấy một giải pháp như vậy sẽ quá lớn để đưa vào câu trả lời Stack Overflow. Nếu tôi quản lý để tạo ra một triển khai hoàn chỉnh hơn, có lẽ tôi sẽ đăng nó lên Github và sau đó liên kết với nó trong phiên bản cập nhật của câu trả lời này.

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

Nếu so sánh và so sánh không được phép thì làm thế nào để làm điều đó?
Ved Prakash

Không chắc chắn nếu tôi hiểu trường hợp sử dụng của bạn, có lẽ bạn có thể giải thích. Nếu đối tượng bạn muốn sử dụng làm giá trị không thể so sánh được thì bạn cần chuyển đổi nó thành đối tượng.
David Bleckmann

6

Vào trễ.

Với sự ra đời của Java-8, chúng ta có thể sử dụng các luồng để thao tác dữ liệu theo cách rất dễ dàng / cô đọng. Bạn có thể sử dụng các luồng để sắp xếp các mục bản đồ theo giá trị và tạo một LinkedHashMap để duy trì việc lặp theo thứ tự chèn .

Ví dụ:

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

Để đặt hàng ngược, thay thế:

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

với

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

Cảm ơn cho phiên bản bình luận này. Một câu hỏi: sự khác biệt của việc sử dụng Entry.comparingByValue()(như câu trả lời của assylias ở trên stackoverflow.com/a/22132422/1480587 ) hoặc comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)bạn đã sử dụng là gì? Tôi hiểu bạn cũng so sánh các khóa nếu các giá trị là giống hệt nhau, phải không? Tôi nhận thấy rằng việc sắp xếp giữ thứ tự các phần tử có cùng giá trị - vậy việc sắp xếp theo các khóa có cần thiết nếu các khóa xảy ra đã được sắp xếp trước không?
Peter T.

6

Cho bản đồ

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

Sắp xếp bản đồ dựa trên giá trị theo thứ tự tăng dần

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

Sắp xếp bản đồ dựa trên giá trị theo thứ tự mong muốn

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

Đầu ra:

{phần mềm = 50, công nghệ = 70, Hoa Kỳ = 100, việc làm = 200, cơ hội = 200}

{jobs = 200, cơ hội = 200, Hoa Kỳ = 100, công nghệ = 70, phần mềm = 50}


Tôi đoán map-less thực sự "Giảm" nó .... giải pháp tốt.
ha9u63ar

cảm ơn @ ha9u63ar
Arpan Saini

Nó hoạt động nhưng tôi không hiểu thứ tự các yếu tố xuất hiện trong HashMap như thế nào?
Ali Tou

5

Tùy thuộc vào ngữ cảnh, sử dụng java.util.LinkedHashMap<T>ghi nhớ thứ tự các mục được đặt vào bản đồ. Mặt khác, nếu bạn cần sắp xếp các giá trị dựa trên thứ tự tự nhiên của chúng, tôi khuyên bạn nên duy trì một Danh sách riêng có thể được sắp xếp thông qua Collections.sort().


Tôi không hiểu tại sao đây là -1, cho đến nay LinkedHashMap có lẽ là giải pháp tốt nhất cho tôi, tôi chỉ đang cố gắng tìm ra mức độ đắt đỏ của việc vứt bỏ và tạo một LinkedHashMap mới.
NobleUplift

5

TreeMap <> không hoạt động cho các giá trị có thể bằng nhau, tôi đã sử dụng:

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

Bạn có thể muốn đưa danh sách vào LinkedHashMap , nhưng nếu bạn chỉ lặp đi lặp lại nó ngay lập tức, thì đó là thừa ...


Điều đó đúng nhưng bộ so sánh của bạn không xử lý bằng giá trị trường hợp
Sebastien Lorber

5

Điều này là quá phức tạp. Bản đồ không được phép thực hiện công việc đó như sắp xếp chúng theo Giá trị. Cách dễ nhất là tạo Class của riêng bạn để nó phù hợp với yêu cầu của bạn.

Trong ví dụ thấp hơn, bạn phải thêm TreeMap một bộ so sánh tại vị trí *. Nhưng bằng java API, nó chỉ cung cấp các khóa so sánh, không phải các giá trị. Tất cả các ví dụ được nêu ở đây dựa trên 2 Bản đồ. Một Hash và một Cây mới. Đó là số lẻ.

Ví dụ:

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

Vì vậy, thay đổi bản đồ thành một bộ theo cách này:

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

Bạn sẽ tạo lớp 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;
    }
}

và lớp So sánh:

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

Bằng cách này bạn có thể dễ dàng thêm nhiều phụ thuộc hơn.

Và như điểm cuối cùng tôi sẽ thêm trình lặp đơn giản:

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

4

Dựa trên mã @devinmoore, một phương pháp sắp xếp bản đồ bằng cách sử dụng tổng quát và hỗ trợ cả thứ tự tăng dần và giảm dần.

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

Sau đó, một lần nữa có lẽ một giải pháp tốt hơn là chỉ sử dụng bản đồ tự sắp xếp, trong trường hợp sử dụng org.apache.commons.collections.bidimap.TreeBidiMap
Maxim Veksler

4

Đây là một giải pháp OO (nghĩa là không sử dụng staticcác phương thức):

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

Dưới đây quyên góp cho phạm vi công cộng.


4

Afaik cách sạch nhất là sử dụng các bộ sưu tập để sắp xếp bản đồ theo giá trị:

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

4

Một số thay đổi đơn giản để có bản đồ được sắp xếp với các cặp có giá trị trùng lặp. Trong phương thức so sánh (lớp ValueComparator) khi các giá trị bằng nhau không trả về 0 mà trả về kết quả so sánh 2 khóa. Các khóa là khác biệt trong bản đồ để bạn thành công trong việc giữ các giá trị trùng lặp (được sắp xếp theo các khóa theo cách này). Vì vậy, ví dụ trên có thể được sửa đổi như thế này:

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

4

Chắc chắn giải pháp của Stephen thực sự tuyệt vời, nhưng với những người không thể sử dụng Guava:

Đây là giải pháp của tôi để sắp xếp theo giá trị bản đồ. Giải pháp này xử lý trường hợp có hai giá trị giống nhau, v.v ...

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

Người thực hiện: http://www.ideone.com/dq3Lu

Đầu ra:

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

Hy vọng nó sẽ giúp một số người


3

Nếu bạn có các khóa trùng lặp và chỉ một bộ dữ liệu nhỏ (<1000) và mã của bạn không có hiệu suất quan trọng, bạn có thể thực hiện các thao tác sau:

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

inputUnsorticMap là đầu vào cho mã.

Biến sortOutputMap sẽ chứa dữ liệu theo thứ tự quyết định khi lặp lại. Để thay đổi thứ tự, chỉ cần thay đổi> thành một <trong câu lệnh if.

Không phải là loại nhanh nhất nhưng thực hiện công việc mà không có bất kỳ phụ thuộc bổ sung.

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.