【问题标题】:Map sorted on size of value collection [duplicate]地图按价值集合的大小排序[重复]
【发布时间】:2015-12-28 17:17:42
【问题描述】:

我正在尝试创建一个排序映射 Map<Integer,Set<Integer>>,它根据值集的 size() 对元素进行排序。

实际上,这是一个节点到连接到该节点的其他节点的映射。我想快速(O(logn))访问具有最多边缘的节点不必每次都排序

例如顺序应该是:

3 => {1,2,4,5}
12 => {1,2,3}
14 => {3,2,3}
65 => {3,8}
6 => {2}
2 => {5}

由于TreeMap 不会这样做,因为我无法根据值进行排序,所以我可能需要滚动一些自定义内容。

编辑: Set 的大小确实可能会发生变化,这可能会使事情变得更加复杂

实现这一目标的最简单方法是什么?

【问题讨论】:

  • 你是什么意思TreeMap 不会做?您可以使用自定义比较器。
  • @markspace 比较器只允许我按键排序(除非我想采用另一个丑陋/不可靠的黑客/解决方案)
  • 反转键和值。你说你要按尺寸查找对吧?
  • 所以使用两张地图。一个按 Set#size 排序,一个按节点 ID 索引。这是最简单的方法。
  • @KonstantinosChalkias 是的,现在我考虑到它确实可能会改变并且可能使事情变得更加复杂

标签: java dictionary java-7 treemap sortedmap


【解决方案1】:

这是一个排序示例,如何为此使用两个集合。一组按 Set::size 排序,另一组只是一个带有整数索引的普通 Map。要使用它,您必须在两个映射中保持相同的键/值对。

我不确定是否建议尝试用它制作一张地图。它有两个查找,按索引和按大小,所以它不像常规地图那样工作。这取决于您的使用模式。

package quicktest;

import static java.util.Comparator.comparing;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;

public class TreeMapTest
{
   public static void main(String[] args) {
      TreeMap<Integer,Set<Integer>> index = new TreeMap<>();
      TreeMap<Set<Integer>,Integer> size = new TreeMap<>( comparing( Set::size ) );

      for( int i = 0; i < 5; i++ ) { 
         Set<Integer> set = new HashSet<>();
         for( int val = 0; val <= i; val++ ) {
            set.add( val );
         }
         index.put( i, set );
         size.put( set, i );
      }
      System.out.println( size.lastEntry() ); // largest set size
      System.out.println( index.get( 2 ) );  // random index
   }
}

【讨论】:

  • 这只有在我们插入后不更改 Set.size 时才有效,因为排序键(大小)是可变的。从每个集合中添加/删除元素时,您需要适当的控制。
  • 或者在插入之前复制集合,以便以后的更改不会影响已经存储的集合。我倾向于走这条路。
  • 但是OP想在插入后更新indexMap;所以获取,更改(添加/删除元素)然后存储回来
  • 这基本上就是我所说的。您的意思是不需要获取/更改/放置周期?我没有办法绕过它。如果您尝试向 HashSet 添加“控制”,则将 O(1) 算法更改为 O(log n),其中 n 是存储在系统中的集合总数。这似乎很糟糕,而且不灵活。
  • 不,我说这是需要的(我解释了它需要做什么)。我不明白插入前的克隆如何提供帮助
【解决方案2】:

这个呢?

public class MapAndPriority {
    Map<Integer, Set<Integer>> sets = new HashMap<Integer, Set<Integer>>();
    PriorityQueue<Set<Integer>> byLength = new PriorityQueue<Set<Integer>>(1, new Comparator<Set<Integer>>() {
        @Override
        public int compare(Set<Integer> o1, Set<Integer> o2) {
            // Compare in the reverse order!
            return o2.size() - o1.size();
        }
    });

    public void add(int i, Set<Integer> set) {
        sets.put(i, set);
        byLength.offer(set); // or Add, depending on the behavior you want
    }

    public Set<Integer> get(int i) {
        return sets.get(i);
    }

    public Set<Integer> mostNodes() {
        return byLength.peek();
    }

    public void remove(int i) {
        // sets.remove will return the removed set, so that will be removed from byLength. 
        // Need to handle case when i does not exist as a key in sets
        byLength.remove(sets.remove(i));

    }
}

如果我明白你想要什么,那么这将是:

  1. 在 o(nlog(n)) 中添加新集合
  2. 常规地图get()
  3. 将在 o(log(n)) 中获得最大的集合 (mostNodes())

我所做的是将所有集合放在优先级队列中(沿着地图),然后为优先级队列提供一个比较器,该比较器根据大小进行比较,因此较小的大小是“较大的”。这样,当您调用peek() 时,它将返回优先级队列中的“最小值”值,由于我们的比较器,它将是最长的集合。 我没有处理各种边缘情况(比如空时删除)。

您可以查看documentation 了解更多详细信息和复杂性。

【讨论】:

  • 在我的实现中发现了一个小错误。如果您在插入后更改集合,则调用 mostNodes 将不起作用。如果您希望能够在插入后更改集合,您可以向MapAndPriority 添加另一种方法,该方法从集合中添加/删除元素。然后它需要从优先级队列中删除该集合,对其进行更改,然后重新添加它。
  • 只有当您确定没有其他人可以访问任何集合并更改其大小而不调用您建议的添加/删除方法时,您的修复建议才会起作用。我想观察者模式会为这个问题提供更合适的解决方案。
  • 好吧,在不了解大局的情况下,我无法为我的建议辩护。但一般来说,您可以只公开这个类,并让 getter 返回只读副本。
猜你喜欢
  • 2012-12-04
  • 1970-01-01
  • 2011-06-13
  • 2015-08-31
  • 2012-11-11
  • 2018-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多