对对象进行排序并根据自定义优先级进行选择



我有一个自定义优先级数组。

int[] priorities = { 866, 663, 855, 853, 854};

我目前有这段代码来对我的列表进行排序并根据标准选择第一个。

var target = (from people in GetNearbyPeopleList()
where people.DistanceToTravel > 0 && people.ReachedDestination == false
orderby //not sure what to do here
select people).FirstOrDefault();

所以我想按我的自定义优先级排序。其中 people.currentlocation 按我的优先级数组排序。

我做 - ?

orderby priorities.Contains(people.currentlocation)

这就是我能想到的,但它没有按照我的自定义优先级顺序正确排序。我希望它完全遵循以下顺序:

int[] priorities = { 866, 663, 855, 853, 854};

因此,如果位置是 866,请选择该位置。我只想选择一个,我想根据该优先级列表选择第一个。如果当前位置 == 866 不存在,则选择 663,依此类推。

如果你的值在编译时是固定的...避免数组并写入:

orderby
people.currentLocation == 866 ? 1 :
people.currentLocation == 663 ? 2 :
people.currentLocation == 855 ? 3 :
people.currentLocation == 853 ? 4 :
people.currentLocation == 854 ? 5 :
6

如果您的值在运行时更改,但值的数量具有某个固定的最大值,则编写:

Person FindPriorityPerson(IQueryable<Person> query,
int p1 = 0, int p2 = 0, int p3 = 0, int p4 = 0,
int p5 = 0, int p6 = 0, int p7 = 0, int p8 = 0)
{
return query.OrderBy(person =>
person.currentLocation == p1 ? 1 :
person.currentLocation == p2 ? 2 :
person.currentLocation == p3 ? 3 :
person.currentLocation == p4 ? 4 :
person.currentLocation == p5 ? 5 :
person.currentLocation == p6 ? 6 :
person.currentLocation == p7 ? 7 :
person.currentLocation == p8 ? 8 :
9).FirstOrDefault();
}

我认为这会做到。 这为您提供了基于数组顺序的排序。

必须添加逻辑,以便 -1(未找到)位于末尾

orderby Array.IndexOf(priorities, people.currentLocation) == -1 ? Integer.MaxValue : Array.IndexOf(priorities, people.currentLocation);

Ctznkan525 的回答给了我使用"使用索引选择"按顺序或优先级对数组进行排序的想法。

因此,您希望所有具有优先级的人都在列表或优先级中排在第一位。 第二是列表中提到的具有优先级第二的人,第三是列表中具有优先级第三的人,依此类推。

您是否注意到我提到了索引来识别订单?因此,如果我将索引添加到您的优先级列表中,我可以按此索引排序。

如果 GetNearByPeopleList 返回一个 IEnumerable,则此方法有效。

var indexedPriorities = priorities.Select( (priority, index) => new
{
Priority = priority,
OrderIndex = index,
});
var result = GetNearbyPeopleList()
.Where(...)                          // take only people you want
.Join(indexedPriorities,             // join with indexedPriorities
people => people.CurrentLocation,    // from each people take the CurrentLocation
indexedPrio => indexedPrio.Priority, // from each indexedPriority take the priority
(people, prio) => new                // when they match, make a new object
{
Index = prio.Index,              // containing the index of the matching priority
People = people,                 // and the matching data
})
.OrderBy(item => item.Index)         // order by ascending index
.Select(item => item.People);        // keep only the People

此代码会省略所有具有当前位置且不在优先级列表中的人员。如果你想要它们,你应该将它们连接在你想要的地方,可能在最后。

唉,linq-to-entity不支持"使用索引选择"。您可以尝试先添加索引,将列表设置为可查询,然后执行联接:

var indexedPriorities =  = priorities.Select( (priority, index) => new
{
Priority = priority,
OrderIndex = index,
})
.AsQueryable();
var result = GetNearbyPeopleList()
.Where(...)       
.Join(indexedPriorities, 
people => people.CurrentLocation, CurrentLocation
indexedPrio => indexedPrio.Priority, 
...

唉,这不起作用,因为您只能传输要在联接中使用的简单类型 AsQueryable。

但是,如果数据是远程的,并且您确实需要它,则可以将优先级和索引转换为小数:

var indexedPriorities =  = priorities.Select( (x, i) => (decimal)x + (decimal)i/1000M)
.AsQueryable();

小数点前的数字是优先级,小数点后是排序索引:(.001 在 .002 之前,依此类推

IQueryable<People> nearbyPeople = GetNearbyPeopleList()
.Where(...);
var result = nearbyPeople.Join(indexedPriorities,
people => people.CurrentLocation,
prio => (int)Math.Floor(prio),
(people, prio) => new
{
OrderIndex =(prio - Math.Truncate(prio),
People = people,
})
.OrderBy(item => item.OrderIndex)
.Select(item => item.People);

首先,我将原始优先级列表:{ 866, 663, 855, 853, 854} 更改为 {866.000, 663.001, 855.002, 853.003, 等等}。假设您的优先级列表没有 1000 个元素。

然后我与 866.000、663.001 等的 Math.Floor 做一个连接。匹配时,我将小数点后的部分保留为 OrderIndex:.000、.001、.002 等(不需要,但如果需要:乘以 1000)

订购后,我摆脱了订单索引。

这不能这样完成。* Linq 不能按照您需要的方式使用您自己的数组。但是我能想到其他几种方法可以做到这一点

  • 重新设计应用程序,以便在服务器上的表中具有优先级。
  • 使用表值参数 (TVP)。这是将数组发送到要在查询中使用的服务器的正确方法,它可以毫无问题地扩展到数千个项目。它需要支持该功能的服务器(如Microsoft SQL Server),并且查询必须在 linq 外部完成。Linq 不支持这一点。
  • 使用Contains查询所有项目,在客户端上选择第一个。Contains可以使用自己的数组,但请注意,它会将其转换为参数,一个值表示一个参数。如果数组中有太多项目 (>100),请不要使用它,否则查询会很慢,并且会崩溃超过 2000 个项目(取决于服务器类型)。
  • 循环数组并一次查询一个项目。例如,您可以将其与包含结合使用并一次查询 10 个项目以加快速度。

*艾米B找到了一种方法,我的坏。但要注意限制。它为每个值使用不是一个参数,而是两个参数,使其更加受约束。这可能会导致表扫描,这可能更昂贵,具体取决于表大小和数组大小。在 20M 表上,它需要大约 25000 倍的数据库资源,而不仅仅是使用 Contains 查询所有 5 行。您可能应该将其与包含过滤器结合使用以避免表扫描,但这意味着每个项目三个参数......

最新更新