如何根据条件从优先级队列中轮询值
Posted
技术标签:
【中文标题】如何根据条件从优先级队列中轮询值【英文标题】:How to do poll values from Priority queue based on a condition 【发布时间】:2021-08-18 05:47:29 【问题描述】:我有地图 Map
现在我的用例是,
-
如果地图的大小为
如果地图的大小 > 3,那么我需要根据分数从地图中获取 3 个值(1 个值/键)。
例如:
// output should be just Data(17.0, "five", "D"), Data(4.0, "two", "A"), Data(3.0, "three", "B") though there will be only 4 keys (A,B,C,D)
ArrayList<Data> dataList = new ArrayList<Data>();
dataList.add(new Data(1.0, "one", "A"));
dataList.add(new Data(4.0, "two", "A"));
dataList.add(new Data(3.0, "three", "B"));
dataList.add(new Data(2.0, "four", "C"));
dataList.add(new Data(7.0, "five", "D"));
dataList.add(new Data(17.0, "five", "D"));
// output should be just Data(5.0, "six", "A"), Data(3.14, "two", "B"), Data(3.14, "three", "C") as there will be only 3 keys (A,B,C)
ArrayList<Data> dataList2 = new ArrayList<Data>();
dataList2.add(new Data(3.0, "one", "A"));
dataList2.add(new Data(5.0, "six", "A"));
dataList2.add(new Data(3.14, "two", "B"));
dataList2.add(new Data(3.14, "three", "C"));
我尝试了以下方法,但在 Java 中是否有更好/更智能(优化)的方法?
// n = 3
public List<Data> getTopN(final List<Data> dataList, final int n)
private static final Comparator< Data > comparator = Comparator.comparing(Data::getScore).reversed();
Map<String, PriorityQueue<Data>> map = Maps.newHashMap();
for (Data data : dataList)
String key = data.getGroup();
if (key != null)
if (!map.containsKey(key))
map.put(key, new PriorityQueue<>(comparator));
map.get(key).add(data);
if (map.size <= n)
List<Data> result = new ArrayList<Data>();
for (Map.Entry<String, PriorityQueue<Data>> entrySet: map.entrySet())
PriorityQueue<Data> priorityQueue = entrySet.getValue();
result.add(priorityQueue.peek());
return result;
else if (map.size > n)
List<Data> result = new ArrayList<Data>();
for (Map.Entry<String, PriorityQueue<Data>> entrySet: map.entrySet())
PriorityQueue<Data> priorityQueue = entrySet.getValue();
result.add(priorityQueue.peek());
return result.stream()
.sorted(Comparator.comparingDouble(Data::getScore).reversed())
.limit(n)
.collect(Collectors.toList());
数据对象如下所示:
public class Data
double score;
String name;
String group;
public void setName(String name)
this.name = name;
public void setGroup(String group)
this.group = group;
public void setScore(double score)
this.score = score;
public String getName()
return name;
public String getGroup()
return group;
public double getScore()
return score;
【问题讨论】:
您说“我有地图Map<String, PriorityQueue>
”,但在您的示例代码中,起点是List<Data>
。
【参考方案1】:
由于您的起点是List<Data>
,因此当您只对每个键的一个值(即最大值)感兴趣时,将元素添加到Map<String, PriorityQueue<Data>>
没有多大意义。在这种情况下,您可以简单地存储最大值。
此外,值得考虑映射方法keySet()
、values()
和entrySet()
之间的差异。仅当您对循环体内的键和值都感兴趣时,使用后者才有用。否则,请使用keySet()
或values()
来简化操作。
仅当尝试从地图中获取前 n 个值时,使用 PriorityQueue
可能会提高性能:
private static final Comparator<Data> BY_SCORE = Comparator.comparing(Data::getScore);
private static final BinaryOperator<Data> MAX = BinaryOperator.maxBy(BY_SCORE);
public List<Data> getTopN(List<Data> dataList, int n)
Map<String, Data> map = new HashMap<>();
for(Data data: dataList)
String key = data.getGroup();
if(key != null) map.merge(key, data, MAX);
if(map.size() <= n)
return new ArrayList<>(map.values());
else
PriorityQueue<Data> top = new PriorityQueue<>(n, BY_SCORE);
for(Data d: map.values())
top.add(d);
if(top.size() > n) top.remove();
return new ArrayList<>(top);
请注意,BinaryOperator.maxBy(…)
使用升序作为基础,并且优先级队列现在需要升序,因为我们正在删除最小的元素,以便顶部 n 保留在排队等待结果。因此,reversed()
已从此处的Comparator
中删除。
如果 n 很小,则使用优先级队列会带来好处,尤其是与地图的大小相比。如果 n 相当大或预计接近地图的大小,则使用它可能更有效
List<Data> top = new ArrayList<>(map.values());
top.sort(BY_SCORE.reversed());
top.subList(n, top.size()).clear();
return top;
按降序对地图的所有值进行排序并删除多余的元素。这可以与处理map.size() <= n
场景的代码结合使用:
public List<Data> getTopN(List<Data> dataList, int n)
Map<String, Data> map = new HashMap<>();
for(Data data: dataList)
String key = data.getGroup();
if(key != null) map.merge(key, data, MAX);
List<Data> top = new ArrayList<>(map.values());
if(top.size() > n)
top.sort(BY_SCORE.reversed());
top.subList(n, top.size()).clear();
return top;
【讨论】:
谢谢你的详细解释。对大 o 或其他性能指标有任何想法吗? 有三个因素,源列表大小(我们称之为l
),限制n
,以及组的数量,即地图的大小,(我们称之为@987654338 @)。第一个有O(l + m × log n)
,第二个有O(l + m × log m)
(¹)。当您对这些值一无所知时,m
和 n
两者的上限是 l
,因此在最坏的情况下,您必须认为它们都是相同的,最终为O(n × log n)
,这是两种方法和正式正确答案都不可避免的最坏情况。在实践中,第二个具有更好的平均案例和低水平因素。
(¹) 现在您应该明白为什么当n
已知相当小时第一个更可取,而当n
可能很大时第二个更可取。跨度>
它返回的是优先队列解决方案的升序
优先队列解决方案专注于只提供 max n 个元素,而不是以任何特定顺序提供它们。看到升序只是巧合,表达式new ArrayList<>(top)
不保证任何顺序。请注意,在map.size() <= n
的情况下,它甚至会以未指定的哈希映射顺序返回元素。如果要对结果进行排序,请使用第二种方法。但请注意,答案中发布的版本在不需要时也不会排序。如果您希望始终对结果进行排序,则必须将 sort
调用移到 if
语句之前。以上是关于如何根据条件从优先级队列中轮询值的主要内容,如果未能解决你的问题,请参考以下文章