如何解决C#中是否存在“第一个最佳匹配”模式或谓词后备模式?
如果没有满足第一个条件,是否存在一种模式来选择Enumerable
中的一个条件匹配的条件,或者使用另一个条件选择下一个最佳元素?
说我有一个IEnumberable<Meal>
,我想选择第一个Meal
和最符合我的偏好的MealType
。
public enum MealType
{
BurgerMeal,PizzaMeal,FriedChickenMeal,}
public class Meal
{
public MealType Type { get; set; }
// extra details elided
}
如果我的偏好顺序从最喜欢到最不喜欢是
burger,pizza,chicken
然后给定[ burger,chicken,pizza ]
,我应该选择burger
,给定[ chicken,pizza ]
,我应该选择pizza
,给定[ chicken ]
,我应该选择chicken
:
[ burger,pizza ] => burger # since burger is the most preferred
[ chicken,pizza ] => chicken
[ pizza ] => pizza
一个不太通用的迭代代码解决方案可能看起来像这样。不幸的是,倒转的循环并不能立即清除。进餐应该是重点,而不是偏爱。
public static Meal GetBestMealOrDefault(IEnumerable<Meal> meals,IEnumerable<MealType> preferences)
{
foreach (var preference in preferences)
{
foreach (var meal in meals)
{
if (meal.Type == preference)
{
return meal;
}
}
}
return null;
}
我考虑过类似meals.OrderBy(meal => meal.type,new MealTypePreferenceComparer()).First()
的地方,其中MealTypePreferenceComparer : IComparer<MealType>
有点笨拙。
第一个匹配对问题很重要,meals
已经有一个基于其他优先级的订单。
是否有任何现成的算法(或构成它们的方式)可以实现这一目标?
解决方法
好吧,您可以尝试将 map 偏好设置映射到int
(0,1,2,3...
),即
burger,chicken,pizza
我们将拥有
map = {
{ burger,0 },{ chicken,1 },{ pizza,2 },}
,然后按OrderBy
乘以计算出的整数。
代码:
using System.Linq;
...
public static Meal GetBestMealOrDefault(IEnumerable<Meal> meals,IEnumerable<MealType> preferences) {
if (null == meals)
throw new ArgumentNullException(nameof(meals));
else if (null == preferences)
throw new ArgumentNullException(nameof(preferences));
var map = preferences
.Distinct() // to be on the safe side
.Select((item,index) => new {item,index})
.ToDictionary(pair => pair.item,pair.index);
return meals
.OrderBy(item => map.TryGetValue(item.Type,out int order)
? order
: int.MaxValue)
.FirstOrDefault();
}
编辑:如果您拥有meals
的 long 个收藏夹,而最喜欢的商品通常位于该收藏夹中,则可以提高性能 >(请参见Dialecticus的评论),通过循环执行:
public static Meal GetBestMealOrDefault(IEnumerable<Meal> meals,pair.index);
Meal result = default(Meal);
int bestScore = int.MaxValue;
foreach (Meal meal in meals) {
int score = map.TryGetValue(meal.Type,out int order)
? order
: int.MaxValue);
if (default(Meal) == result || bestScore > score) {
bestScore = score;
result = meal;
if (bestScore == 0) // the best meal found,no more looping
break;
}
}
return result;
}
,
您可以使用一些LINQ来做到这一点,如下所示:
var meals = new List<Meal>();
// var selectedPreferences = new List<MealType>();
var bestMatch = meals
// .Where(x => selectedPreferences
// .Any(p => p == x.Type))
.GroupBy(x => x.Type)
.OrderBy(x => x.Key)
.SelectMany(x => x)
.FirstOrDefault();
if (bestMatch is null)
{
// handle
}
// go ahead
,饭菜的分类不受影响。或仅通过以下方式完成对餐点的查询:
.GroupBy(x => x.Type)
.OrderBy(x => x.Key)
.SelectMany(x => x)
.FirstOrDefault();
,
我的实际问题比MVCE的通用性稍差,我只需要处理小型数据集。 Dmitry's answer使用MealType
到int
的映射器使我处在正确的轨道上,这使我不必编写单一用途的IComparer<MealType>
。
我的解决方案最终成为了
// elided …
static int GetPriority(MealType type)
{
return type switch
{
MealType.BurgerMeal => 0,MealType.PizzaMeal => 1,MealType.FriedChickenMeal => 2,_ => int.MaxValue,};
}
return meals
.OrderBy(meal => meal.OtherCriteria)
.ThenBy(meal => GetPriority(meal.Type))
.FirstOrDefault();
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。