在 Java 中按值映射自动排序
Posted
技术标签:
【中文标题】在 Java 中按值映射自动排序【英文标题】:Automatically sorted by values map in Java 【发布时间】:2011-11-19 21:23:10 【问题描述】:我需要有一个 自动 在 Java 中按值排序的映射 - 以便在我添加新的键值对或更新现有的键值对,甚至删除一些条目。
还请记住,这张地图将会非常大(大小为 100 的数千,甚至是 10 的数百万条目)。
所以基本上我正在寻找以下功能:
假设我们有一个实现上述功能的“SortedByValuesMap”类 我们有以下代码:
SortedByValuesMap<String,Long> sorted_map = new SortedByValuesMap<String, Long>();
sorted_map.put("apples", 4);
sorted_map.put("oranges", 2);
sorted_map.put("bananas", 1);
sorted_map.put("lemons", 3);
sorted_map.put("bananas", 6);
for (String key : sorted_map.keySet())
System.out.println(key + ":" + sorted_map.get(key));
输出应该是:
bananas:6
apples:4
lemons:3
oranges:2
尤其是对我来说真正重要的是能够获得带有 任何时候的最低值 - 使用如下命令:
smallestItem = sorted_map.lastEntry();
这应该给我“橙子”条目
编辑:我是 Java 新手,所以请详细说明您的答案 - 谢谢
EDIT2:这可能会有所帮助:我正在使用它来计算巨大文本文件中的单词(对于那些熟悉的人:特别是 n-gram)。所以我需要建立一个地图,其中键是单词,值是这些单词的频率。但是,由于限制(如 RAM),我只想保留 X 最常用的词——但你不能事先知道哪些是最常用的词。因此,我认为它可能起作用的方式(作为近似值)是开始计算单词,当地图达到上限(如 1 百万个条目)时,将删除最不频繁的条目以保持地图的大小100 万。
【问题讨论】:
数以百万计的条目?为什么不使用数据库呢? 如果有两个具有相同最小值的键怎么办?lastEntry()
的预期行为应该是什么? (例如,limes
的另一个条目 -> 2
在地图中)
@Kru:数据库会让它变得非常慢
如果这只是英语,那么您高估了单词的数量,尤其是常用的单词。
@Dave Newton 你是对的 - 我提到了一些词,以免让不熟悉 n-gram 的人感到困惑,这正是我实际计算的。 N-grams,尤其是随着 N 的增加,可以变得非常多样化。可能的组合呈指数增长。
【参考方案1】:
保留2个数据结构:
单词字典 -> 计数。只需使用普通的HashMap<String, Long>
。
一个用于跟踪顺序的“数组”,这样list[count]
包含具有该计数的单词Set<String>
。
我写这个好像它是一个数组作为符号方便。事实上,您可能不知道出现次数的上限,因此您需要一个可调整大小的数据结构。使用Map<Long, Set<String>>
实现。或者,如果这占用了太多内存,请使用ArrayList<Set<String>>
(您必须测试count == size() - 1
,如果是,请使用add()
而不是set(count + 1)
)。
增加单词的出现次数(伪代码):
// assumes data structures are in instance variables dict and arr
public void tally(final String word)
final long count = this.dict.get(word) or 0 if absent;
this.dict.put(word, count + 1);
// move word up one place in arr
this.arr[count].remove(word); // This is why we use a Set: for fast deletion here.
this.arr[count + 1].add(word);
按顺序迭代单词(伪代码):
for(int count = 0; count < arr.size; count++)
for(final String word : this.arr[count])
process(word, count);
【讨论】:
【参考方案2】:如果 Long 值不同,如何使用附加索引或仅使用 TreeMap<Long, TreeSet<String>>
或 TreeMap<Long, String>
?
你也可以写Heap。
【讨论】:
长值不明显。两个不同的条目可能具有相同的 Long 值 - Long 值实际上代表频率 所以你可以使用TreeMap<Long, TreeSet<String>>
。
这可能行得通,但我担心它会使时间加倍,因为我们将地图操作加倍 - 就我而言,我有数百万个条目可能会产生巨大的影响
没那么多。只是常数因子会小幅上升。您还可以创建一些配对类,例如 Map.Entry<K,V>
并使用 TreeSet<Pair<Long, String>>
。
可以,但您可以同时保留TreeMap<Long,TreeSet<String>>
和Map<String,Long>
。我想在java中没有提供两种技巧的单一数据结构。在 SQL 表中,您希望在两列上有索引,所以我想您在 java 中还需要 2 个“索引”。【参考方案3】:
Guava BiMap解决办法:
//Prepare original data
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("apples" , 4);
biMap.put("oranges", 2);
biMap.put("bananas", 1);
biMap.put("lemons" , 3);
biMap.put("bananas", 6);
//Create a desc order SortedMap
SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>(new Comparator<Integer>()
@Override public int compare(Integer o1, Integer o2)
return o2-o1;
);
//Put inversed map
sortedMap.putAll(biMap.inverse());
for (Map.Entry<Integer, String> e: sortedMap.entrySet())
System.out.println(e);
System.out.println(sortedMap.lastKey());
【讨论】:
OP 说这些值不是唯一的,所以 BiMap 不起作用。【参考方案4】:试试http://paaloliver.wordpress.com/2006/01/24/sorting-maps-in-java/ 上发布的解决方案。您也可以灵活地进行升序或降序排序。
这就是他们所说的
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
public class MapValueSort
/** inner class to do soring of the map **/
private static class ValueComparer implements Comparator<String>
private Map<String, String> _data = null;
public ValueComparer (Map<String, String> data)
super();
_data = data;
public int compare(String o1, String o2)
String e1 = (String) _data.get(o1);
String e2 = (String) _data.get(o2);
return e1.compareTo(e2);
public static void main(String[] args)
Map<String, String> unsortedData = new HashMap<String, String>();
unsortedData.put("2", "DEF");
unsortedData.put("1", "ABC");
unsortedData.put("4", "ZXY");
unsortedData.put("3", "BCD");
SortedMap<String, String> sortedData = new TreeMap<String, String>(new MapValueSort.ValueComparer(unsortedData));
printMap(unsortedData);
sortedData.putAll(unsortedData);
System.out.println();
printMap(sortedData);
private static void printMap(Map<String, String> data)
for (Iterator<String> iter = data.keySet().iterator(); iter.hasNext();)
String key = (String) iter.next();
System.out.println("Value/key:"+data.get(key)+"/"+key);
输出
Value/key:BCD/3
Value/key:DEF/2
Value/key:ABC/1
Value/key:ZXY/4
Value/key:ABC/1
Value/key:BCD/3
Value/key:DEF/2
Value/key:ZXY/4
【讨论】:
【参考方案5】:我发现需要一个类似的结构来保存按关联值排序的对象列表。根据此线程中 Mechanical snail 的建议,我编写了此类地图的基本实现。随意使用。
import java.util.*;
/**
* A map where @link #keySet() and @link #entrySet() return sets ordered
* with ascending associated values with respect to the the comparator provided
* at constuction. 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;
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;
此实现不遵守 Map 接口的所有约定,例如在实际映射中返回的键集和条目集中反映值更改和删除,但这样的解决方案将有点大,包含在类似的论坛中这。也许我会研究一个并通过 github 或类似的东西提供它。
【讨论】:
【参考方案6】:更新:您不能按值对地图进行排序,抱歉。
您可以使用 SortedMap
实现,如 TreeMap
和 Comparator
按值定义顺序(而不是默认 - 按键)。
或者,更好的是,您可以将元素放入带有预定义比较器的PriorityQueue 值。与 TreeMap 相比,它应该更快并且占用更少的内存。
【讨论】:
你能提供一个例子来说明如何做到这一点吗? 我不认为你可以使用优先队列,因为文档说迭代器不能保证以任何特定的顺序遍历队列。 @Timothy Jones:这就是为什么我建议使用 PriorityQueue 作为替代方案(如果可能的话)。我没有说清楚。感谢您指出。 如果我使用按值对项目进行排序的 TreeMap,那么通过 Key 访问项目也会很快吗? 为了能够按值对 TreeMap 进行排序,您的键也需要包含这些值。在这种情况下,您将很难通过键查找值...【参考方案7】:你可以参考java.util.LinkedHashMap
的实现。
基本思想是,使用内部链表来存储订单。以下是一些细节:
从 HashMap 扩展而来。在 HashMap 中,每个条目都有一个 key 和 value,这是基本的。您可以添加 next 和 prev 指针以按值顺序存储条目。以及用于获取第一个和最后一个条目的标头和尾指针。对于每次修改(添加、删除、更新),您可以添加自己的代码来更改列表顺序。它只不过是一个线性搜索和指针开关。
如果条目太多,添加/更新肯定会很慢,因为它是链表而不是数组。但是只要对列表进行排序,我相信有很多方法可以加快搜索速度。
所以这就是你得到的:一个在通过键检索条目时与 HashMap 具有相同速度的映射。按顺序存储条目的链表。
如果此解决方案满足您的要求,我们可以进一步讨论。
致 jtahlborn: 正如我所说,如果没有任何优化,它肯定很慢。由于我们现在谈论的是性能而不是 impl,所以可以做很多事情。
一种解决方案是使用树而不是链表,例如红黑树。然后迭代树而不是迭代地图。
关于最小值,比较容易。只需使用成员变量来存储最小值,添加或更新元素时,更新最小值。删除时,搜索最小的树(这非常快)
如果树过于复杂,也可以使用另一个列表/数组来标记列表中的一些位置。例如,每个可能有 100 个元素。然后在搜索时,只需先搜索位置列表,然后搜索真实列表。这个列表也需要维护,重新计算位置列表的修改次数是合理的,可能是100次。
【讨论】:
OP 表示使用可能包含数千万条目的集合。用这么多条目更新“排序”链表会非常慢。【参考方案8】:如果您只需要“min”值,那么只需使用法线贴图并在修改时随时跟踪“min”值。
编辑:
所以,如果您真的需要价值排序并且想要使用开箱即用的解决方案,那么您基本上需要 2 个集合。一张法线贴图(例如 HashMap)和一张 SortedSet(例如 TreeSet>)。您可以通过 TreeSet 遍历有序元素,并使用 HashMap 按键查找频率。
显然,您总是可以自己编写一些类似于 LinkedHashMap 的代码,其中元素可以通过键定位并按顺序遍历,但这几乎完全是自定义代码(我怀疑任何特定的已经存在,但是我可能是错的)。
【讨论】:
因为在此过程中,有时我可能想删除具有最小值的项目。删除该项目后,我需要知道具有最小值的下一个项目。有点像最薄弱的环节。 为什么投反对票? @Timothy Jones 基本上把我的建议写成了选择的答案。以上是关于在 Java 中按值映射自动排序的主要内容,如果未能解决你的问题,请参考以下文章