按照值对 Map<Key, Value> 进行排序

1881
我需要根据值对一个Map进行排序。
由于值不是唯一的,我发现自己需要将keySet转换为数组,并通过使用自定义比较器对该数组进行排序,以便根据与键关联的值进行排序。
有没有更简单的方法?

32
地图的目的不是为了排序,而是为了快速访问。对象相等的值会违反地图的约束条件。使用entry set,例如 List<Map.Entry<...>> list =new LinkedList(map.entrySet())Collections.sort .... 进行排序。 - Hannes
2
一个可能出现这种情况的案例是当我们尝试在Java中使用计数器(Map<Object,Integer>)时。按出现次数排序将成为常见操作。像Python这样的语言具有内置的计数器数据结构。对于Java中的另一种实现方式,此处提供了一个示例。 - demongolem
14
有很多使用排序映射的情况,这就是为什么在jdk中有TreeMap和ConcurrentSkipListMap的原因。 - alobodzk
8
TreeMap和ConcurrentSkipListMap会根据键进行排序。问题是如何按值排序。 - Peter
3
根据您的使用情况,保留一个重复的TreeMap,将值映射到键可能是合理的。例如,您的常规map可能为"a"->5,"b"->7。而您的“排序”map可以有5->“a”,7->“b”。您只需在不同的地方使用适当的map,并努力始终同时修改这两个map。虽然存在许多警告和假设,但对于某些情况而言,与所有依赖于主动排序您的值的答案相比,这可能是一种简单而有效的答案。 - rococo
显示剩余2条评论
65个回答

6

在Java 8及以上版本中,对任意映射(Map)进行排序的简单方法

Map<String, Object> mapToSort = new HashMap<>();

List<Map.Entry<String, Object>> list = new LinkedList<>(mapToSort.entrySet());

Collections.sort(list, Comparator.comparing(o -> o.getValue().getAttribute()));

HashMap<String, Object> sortedMap = new LinkedHashMap<>();
for (Map.Entry<String, Object> map : list) {
   sortedMap.put(map.getKey(), map.getValue());
}

如果您正在使用Java 7及以下版本

Map<String, Object> mapToSort = new HashMap<>();

List<Map.Entry<String, Object>> list = new LinkedList<>(mapToSort.entrySet());

Collections.sort(list, new Comparator<Map.Entry<String, Object>>() {
    @Override
    public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
       return o1.getValue().getAttribute().compareTo(o2.getValue().getAttribute());      
    }
});

HashMap<String, Object> sortedMap = new LinkedHashMap<>();
for (Map.Entry<String, Object> map : list) {
   sortedMap.put(map.getKey(), map.getValue());
}

在原始映射中存在重复值会导致排序后的映射中出现重复键,因此这种方法不适用于处理重复值。 - C.B.

5

由于TreeMap<>无法处理可相等的值,因此我使用了以下代码:

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

你可能想把列表放在LinkedHashMap中,但如果你马上就要进行迭代,那么这是多余的...

没错,但是你的比较器没有处理相等的情况。 - Sebastien Lorber

5
据我所知,最干净的方法是利用集合来按值对映射进行排序:
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());
  }
}

5

主要问题。如果您使用第一个答案(Google带您来到此处),请将比较符更改为添加等号,否则无法通过键从sorted_map获取值:

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
    }

现在,当您添加两个具有相等值的条目时,它们将被合并。只有在确定对象相同(相等)时才应返回0。 - Masood_mj

5

使用Java 8可以轻松实现此目标。

public static LinkedHashMap<Integer, String> sortByValue(HashMap<Integer, String> map) {

        List<Map.Entry<Integer, String>> list = new ArrayList<>(map.entrySet());
        list.sort(Map.Entry.comparingByValue());
        LinkedHashMap<Integer, String> sortedMap = new LinkedHashMap<>();
        list.forEach(e -> sortedMap.put(e.getKey(), e.getValue()));
        return sortedMap;
    }

5

根据情境,可以使用java.util.LinkedHashMap<T>来记住项目放入映射的顺序。否则,如果需要根据自然排序对值进行排序,建议维护一个可通过Collections.sort()进行排序的单独列表。


我不明白为什么这个被评为-1,到目前为止LinkedHashMap可能是最好的解决方案,我只是在尝试弄清楚丢弃和创建新的LinkedHashMap有多昂贵。 - NobleUplift

5

这太复杂了。地图不应该做按值排序的工作。最简单的方法是创建自己的类以满足您的要求。

在下面的示例中,您应该在*处为TreeMap添加一个比较器。但是根据Java API,它只提供键的比较器,而不是值的比较器。所有这里列出的示例都基于两个映射。一个哈希和一个新的树形。这很奇怪。

示例:

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

所以将地图转换为集合的方法如下:

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

您将创建一个名为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;
    }
}

以及比较器类:

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

这样,您就可以轻松地添加更多依赖项。
最后,我将添加一个简单的迭代器:
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
一些简单的更改可以使具有重复值的配对的排序映射。在比较方法(ValueComparator类)中,当值相等时,不要返回0,而是返回比较2个键的结果。在映射中,键是不同的,所以您成功地保留了重复的值(顺便说一下,这些值按键排序)。因此,上面的示例可以修改为以下内容:
    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
这里提供一种面向对象的解决方案(即不使用静态方法):
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 );
  }
}

此处声明将其捐赠至公共域。


4

基于 @devinmoore 的代码,这是一个使用泛型的地图排序方法,支持升序和降序排序。

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

或许更好的解决方案是使用自排序映射,例如在这种情况下使用org.apache.commons.collections.bidimap.TreeBidiMap。 - Maxim Veksler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接