【问题标题】:Automatically sorted by values map in Java在 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 的增加,可以变得非常多样化。可能的组合呈指数增长。

标签: java data-structures collections associative-array sorted


【解决方案1】:

保留2个数据结构:

  • 单词字典 -> 计数。只需使用普通的HashMap&lt;String, Long&gt;
  • 一个用于跟踪顺序的“数组”,这样list[count] 包含具有该计数的单词Set&lt;String&gt;

    我写这个好像它是一个数组作为符号方便。事实上,您可能不知道出现次数的上限,因此您需要一个可调整大小的数据结构。使用Map&lt;Long, Set&lt;String&gt;&gt; 实现。或者,如果这占用了太多内存,请使用ArrayList&lt;Set&lt;String&gt;&gt;(您必须测试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&lt;Long, TreeSet&lt;String&gt;&gt;TreeMap&lt;Long, String&gt;

    你也可以写Heap

    【讨论】:

    • 长值不明显。两个不同的条目可能具有相同的 Long 值 - Long 值实际上代表频率
    • 所以你可以使用TreeMap&lt;Long, TreeSet&lt;String&gt;&gt;
    • 这可能行得通,但我担心它会使时间加倍,因为我们将地图操作加倍 - 就我而言,我有数百万个条目可能会产生巨大的影响
    • 没那么多。只是常数因子会小幅上升。您还可以创建一些配对类,例如 Map.Entry&lt;K,V&gt; 并使用 TreeSet&lt;Pair&lt;Long, String&gt;&gt;
    • 可以,但您可以同时保留TreeMap&lt;Long,TreeSet&lt;String&gt;&gt;Map&lt;String,Long&gt;。我想在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 实现,如 TreeMapComparator 按值定义顺序(而不是默认 - 按键)。

        或者,更好的是,您可以将元素放入带有预定义比较器的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 基本上把我的建议写成了选择的答案。
        猜你喜欢
        • 2019-04-29
        • 2021-03-01
        • 2013-02-01
        • 1970-01-01
        • 2021-12-23
        • 2015-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多