27

我需要在 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-grams)。所以我需要建立一个地图,其中键是单词,值是这些单词的频率。但是,由于限制(如 RAM),我只想保留 X 最常用的词——但你不能事先知道哪些是最常用的词。因此,我认为它可能起作用的方式(作为近似值)是开始计算单词,当地图达到上限(如 1 百万个条目)时,将删除最不频繁的条目以保持地图的大小100万总是。

4

8 回答 8

4

保留2个数据结构:

  • 单词词典->计数。用普通的就行HashMap<String, Long>
  • 一个“数组”来跟踪顺序,这样就可以list[count]保存一个Set<String>具有该计数的单词。

    我写这个好像它是一个数组作为符号方便。事实上,您可能不知道出现次数的上限,因此您需要一个可调整大小的数据结构。使用Map<Long, Set<String>>. 或者,如果这使用太多内存,请使用 an 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);
于 2011-09-19T03:45:15.363 回答
2

如果 Long 值不同,如何使用附加索引或仅使用索引TreeMap<Long, TreeSet<String>>TreeMap<Long, String>

你也可以写一个Heap

于 2011-09-19T00:29:08.550 回答
1

尝试发布在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
于 2014-10-24T13:07:14.380 回答
1

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()); 
于 2011-09-19T02:10:37.830 回答
1

我发现需要一个类似的结构来保存按关联值排序的对象列表。根据 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 或类似的东西提供它。

于 2016-04-23T21:32:50.127 回答
0

更新:您不能按值对地图进行排序,抱歉。

您可以使用SortedMap实现,例如TreeMapComparator值定义顺序(而不是默认值 - 按键)。

或者,更好的是,您可以将元素放入具有预定义比较器的PriorityQueue值。与 TreeMap 相比,它应该更快并且占用更少的内存。

于 2011-09-19T00:30:31.620 回答
0

您可以参考java.util.LinkedHashMap. 基本思想是,使用内部链表来存储订单。以下是一些细节:

从 HashMap 扩展。在 HashMap 中,每个条目都有一个 key 和 value,这是基本的。您可以添加 next 和 prev 指针以按值顺序存储条目。以及用于获取第一个和最后一个条目的标头和尾指针。对于每次修改(添加、删除、更新),您可以添加自己的代码来更改列表顺序。它只不过是一个线性搜索和指针开关。

如果条目太多,添加/更新肯定会很慢,因为它是链表而不是数组。但是只要对列表进行排序,我相信有很多方法可以加快搜索速度。

所以这就是你得到的:一个在通过键检索条目时与 HashMap 具有相同速度的映射。按顺序存储条目的链表。

如果此解决方案满足您的要求,我们可以进一步讨论。


to jtahlborn:正如我所说,如果没有任何优化,它肯定很慢。由于我们现在谈论的是性能而不是 impl,所以可以做很多事情。

一种解决方案是使用树而不是链接列表,例如红黑树。然后迭代树而不是迭代地图。

关于最小值,它更容易。只需使用成员变量来存储最小值,添加或更新元素时​​,更新最小值。删除时,搜索最小的树(这非常快)

如果树太复杂,也可以使用另一个列表/数组来标记列表中的一些位置。例如,每个可能有 100 个元素。然后在搜索时,只需先搜索位置列表,然后搜索真实列表。这个列表也需要维护,重新计算位置列表的修改次数是合理的,可能是 100 次。

于 2011-09-19T03:13:52.173 回答
-1

如果您只需要“min”值,那么只需使用法线贴图并在修改时随时跟踪“min”值。

编辑:

所以,如果你真的需要价值排序并且你想使用开箱即用的解决方案,你基本上需要 2 个集合。一张法线贴图(例如HashMap)和一张SortedSet(例如TreeSet>)。您可以通过 TreeSet 遍历有序元素,并使用 HashMap 按键查找频率。

显然,您总是可以自己编写一些类似于 LinkedHashMap 的代码,其中元素可以通过键定位并按顺序遍历,但这几乎将完全是自定义代码(我怀疑任何已经存在的特定代码,但我可能是错误的)。

于 2011-09-19T01:08:03.477 回答