.NET6 中的 PriorityQueue

.NET6 中的 PriorityQueue

Intro

.NET 6 中引入了一个新的集合类型 PriorityQueue,正如它的名字那样,在普通的 Queue 基础之上增加了优先级的支持,接下来就一起来看一下怎么使用,以及一些常用的使用场景介绍。

Get Started

来看一个简单的使用示例:

var priorityQueue = new PriorityQueue<string, int>();
priorityQueue.Enqueue("Alice", 100);
priorityQueue.EnqueueRange(Enumerable.Range(1, 5)
    .Select(x => ($"X_{x}", 100 - x))
);

while (priorityQueue.TryDequeue(out var element, out var priority))
{
    Console.WriteLine($"Element:{element}, {priority}");
}

输出示例:

可以看到输出的顺序和我们添加的顺序是相反的, PriorityQueueDequeue 的时候是从优先级最小开始的,值越小优先级越高,优先级越高越优先输出,如果我们希望最大先输出是不是可以呢,答案是肯定的,只是我们需要指定我们自己的优先级比较规则就可以了,可以参考后面的示例

Scenes

借助带优先级的自动排序,我们可以做一些自动排序的需求的时候可以考虑使用 PriorityQueue

Message Queue

借助 PriorityQueue 可以来实现一个优先级消息队列,允许用户在发消息的时候指定一个消息的优先级,在消费的时候就会按优先级

var random = new Random();

var queue = new PriorityQueue<string, int>();
for (var i = 1; i <= 10; i++)
{
    queue.Enqueue($"Message_{i}", random.Next(10, 100));
}

while (queue.TryDequeue(out var message, out _))
{
    Console.WriteLine(message);
}

在上面的示例里我们默认指定了一个 int 作为 Priority 的类型,并入队了一些消息,但是往往会有很多的消息,可能会出现优先级相同的情况,我们可以使用时间和int 作为一个联合的 Priority 类型,可以参考下面的示例:

var random = new Random();

var queue = new PriorityQueue<string, (DateTime time, int priority)>(new DateTimePriorityComparer());

var time = DateTime.UtcNow;
Thread.Sleep(1000);
for (var k = 0; k < 3; k++)
{
    for (var i = 1; i <= 3; i++)
    {
        queue.Enqueue($"Message_{i}_{k}", (i > 2 ? time : DateTime.UtcNow, random.Next(5, 10)));
    }
}

while (queue.TryDequeue(out var message, out var priority))
{
    Console.WriteLine($"{message}, {priority.priority}, {priority.time}");
}

输出示例如下:

由上面的结果可以看出来,在 priority 一样的情况下,我们会先处理时间较小的消息,也可以根据自己的需要进行定制排序方式,自定义 Priority 比较逻辑即可,上面的自定义优先级比较代码如下:
internal class DateTimePriorityComparer : IComparer<(DateTime time, int priority)>
{
    public int Compare((DateTime time, int priority) x, (DateTime time, int priority) y)
    {
        var priorityComparison = x.priority.CompareTo(y.priority);
        if (priorityComparison != 0) return priorityComparison;
        return x.time.CompareTo(y.time);
    }
}

Rank

在很多做排行榜的应用中也可以使用 PriorityQueue 来实现,比如我们来做一个学生成绩的排名

来看下面的示例代码:

var list = new List<(string name, int score)>
{
    ("Mike", 99),
    ("Ming", 100),
    ("Jane", 96),
    ("Yi", 94),
    ("Harry", 90),
};

var priorityQueue = new PriorityQueue<string, int>();
priorityQueue.EnqueueRange(list);

Console.WriteLine("--Unordered:");
foreach (var item in priorityQueue.UnorderedItems)
{
    Console.WriteLine($"Name:{item.Element}, score:{item.Priority}");
}

Console.WriteLine("--Ordered:");
while (priorityQueue.TryDequeue(out var name, out var score))
{
    Console.WriteLine($"Name:{name}, score:{score}");
}

Console.WriteLine("-----TOP 3");
priorityQueue = new PriorityQueue<string, int>(new High2LowComparer());
priorityQueue.EnqueueRange(list);
var rank = 0;
while (rank++ < 3 && priorityQueue.TryDequeue(out var name, out var score))
{
    Console.WriteLine($"Rank({rank}):Name:{name}, score:{score}");
}

上面的 list 就是一个成绩清单,随便写了几个测试数据,通过 PriorityQueueUnorderedItems 我们可以拿到排序之前的数据,也是我们加入队列(Enqueue)的顺序,默认的比较是小的在前,也就是成绩低的在前,那我们想按从大到小排序的话就需要自定义比较方式。

上面的 High2LowComparer 就是一个自定义的比较,其实就是把比较的结果取了一个反,代码如下:

internal class High2LowComparer : IComparer<int>
{
    public int Compare(int x, int y)
    {
        return y.CompareTo(x);
    }
}

上面的输出结果如下:

More

Redis 里有一个 zset(sortedSet) 类型的数据也可以做类似的事情,但是 zset 和 PriorityQueue 还是有一些不同的,zset 是一个 set,是一个自动去重的集合,而 PriorityQueue 还是一个 Queue 不会去重,zset 可以修改对应元素的 priority(score),但是 PriorityQueue 目前不支持修改元素对应的优先级

PriorityQueue  可以解决我们的一些问题,但是有一些使用要注意的地方:

  1. 首先如果 Priority 是一样的话,输出的顺序是有可能不一样的,这是由它内部的实现算法决定的,不能严格的保证顺序

  2. PriorityQueue 是非线程安全的,需要注意线程安全问题

  3. PriorityQueue 中的 Peek 方法只会获取 queue 里即将出队的元素,但是不会从队列中移除这个元素

References

  • https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-2/

  • https://github.com/dotnet/runtime/blob/v6.0.0-preview.2.21154.6/src/libraries/System.Collections/src/System/Collections/Generic/PriorityQueue.cs

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/net6sample/PriorityQueueSample/Program.cs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在 C# ,`PriorityQueue` 是 .NET Framework 的一个内置数据结构,它是一种基于优先级的队列,其元素的添加顺序取决于它们的优先级。当你需要按照特定的顺序处理任务或元素时,`PriorityQueue` 可以非常有用,例如在调度、事件处理或其他需要按优先级处理任务的场景。 下面是 `PriorityQueue` 在 C# 的基本用法: 1. 创建 `PriorityQueue`:你可以使用泛型指定要存储的数据类型和默认的比较器(如果需要自定义排序)。 ```csharp PriorityQueue<int> queue = new PriorityQueue<int>(); ``` 2. 添加元素:使用 `Enqueue` 方法添加元素,`PriorityQueue` 会自动根据元素的默认或自定义比较器进行排序。 ```csharp queue.Enqueue(10); queue.Enqueue(5); queue.Enqueue(15); ``` 3. 删除并获取最高优先级元素:使用 `Peek` 方法查看当前队列的最高优先级元素,但不删除它;`Dequeue` 方法则用于获取并移除最高优先级的元素。 ```csharp int highestPriority = queue.Peek(); // 返回 5 int removedElement = queue.Dequeue(); // 返回 5,现在队列里只剩 10 和 15 ``` 4. 自定义比较器:如果你想要用自定义的比较逻辑,可以在创建 `PriorityQueue` 时提供一个实现了 `IComparer<T>` 接口的类实例。 ```csharp class CustomComparator : IComparer<int> { // 实现 Compare 方法 } PriorityQueue<int, CustomComparator> customQueue = new PriorityQueue<int, CustomComparator>(new CustomComparator()); ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值