LINQ 中的条件 GroupBy()

Conditional GroupBy() in LINQ

我正在处理一个充满项目之间相似性的矩阵。我将这些保存为数据库中的对象列表。相似度对象如下所示:

public class Similarity
{
    public virtual Guid MatrixId { get; set; } //The id of the matrix the similarity is in
    public virtual Guid FirstIndex { get; set; } //The id of the item of the left side of the matrix
    public virtual Guid SecondIndex { get; set; } //The id of the item of the top side of the matrix
    public virtual double Similarity { get; set; } //The similarity
}

用户可以查看这些项目。我想检索一个项目列表,这些项目是 'similar' 到用户已评论的项目。问题是我无法确定该项目的 id 是在 FirstIndex 还是 SecondIndex 中。我已经编写了一些代码来执行我想要的操作,但我想知道这是否可以在 1 条语句中实现。

var itemsNotReviewed = Similarities.Where(x => !itemsReviewed.Contains(x.SecondIndex))
    .GroupBy(x => x.SecondIndex)
    .ToList();
itemsNotReviewed.AddRange(Similarities.Where(x => !itemsReviewed.Contains(x.FirstIndex))
     .GroupBy(x => x.FirstIndex)
     .ToList());

其中 itemsReviewed 是用户评论过的项目的 guid 列表,Similarities 是与用户评论过的项目相似的所有项目的列表。我使用此函数检索该列表:

return (from Row in _context.SimilarityMatrix
        where itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex)
        select Row)
        .Distinct()
        .ToList();

其中 itemIds 是用户评论过的项目的 GUID 列表。

有没有办法根据 Where 子句按第一个或第二个索引分组?

如果我需要详细说明,请告诉我!

我会改变你获取原始列表的方式:

_context.SimilarityMatrix.Where(Row => itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex))
     .Select(r => new { r.MatrixId, r.FirstIndex, r.SecondIndex, r.Similarity, MatchingIndex = itemIds.Contains(r.FirstIndex) ? r.FirstIndex : r.SecondIndex })
     .Distinct()
     .ToList();

这样你只需要按匹配索引分组。

var itemsNotReviewed = Similarities.
.GroupBy(x => x.MatchingIndex)
.ToList();

您可能希望在动态对象之后转换为您的相似度 class 或者只是更改 class 以包含匹配索引。

您可以通过以下方式将它们转换为您的相似度类型:

var itemsNotReviewed = Similarities.
.GroupBy(x => x.MatchingIndex)
.Select(g => new { g.Key, Values = g.Values.Select(d => new Similarity { MatrixId = d.MatrixId, FirstIndex = d.FirstIndex, SecondIndex = d.SecondIndex, Similarity = d.Similarity }).ToList() })
.ToList();

怎么样

(from x in Similarities
 let b2 = !itemsReviewed.Contains(x.SecondIndex)
 let b1 = !itemsReviewed.Contains(x.FirstIndex)
 where b1 || b2
 groupby b2 ? x.SecondIndex : x.FirstIndex into grp
 select grp)
.ToList()

let 语句引入了一个新的临时变量来存储布尔值。您当然也可以内联其他函数:

(from x in (from Row in _context.SimilarityMatrix
            where itemIds.Contains(Row.FirstIndex) || itemIds.Contains(Row.SecondIndex)
            select Row)
           .Distinct()
           .ToList()
 let b2 = !itemsReviewed.Contains(x.SecondIndex)
 let b1 = !itemsReviewed.Contains(x.FirstIndex)
 where b1 || b2
 groupby b2 ? x.SecondIndex : x.FirstIndex into group
 select group)
.ToList()

如果您想使用非 LINQ 语法,您可能需要引入一些匿名类型:

Similarities
.Select(s => new 
{
    b2 = !itemsReviewed.Contains(x.SecondIndex),
    b1 = !itemsReviewed.Contains(x.FirstIndex),
    s
})
.Where(a => a.b1 || a.b2)
.GroupBy(a => a.b2 ? a.s.SecondIndex : a.s.FirstIndex, a => a.x) //edit: to get same semantics, you of course also need the element selector
.ToList()

据我了解,您有一个 Similarity 的列表,其中保证包含 FirstIndexSecondIndex 包含在 itemsReviewed 的 [=16] 列表​​中的项目=].并且您需要获取 not 中包含的任一索引的元素(如果有的话) itemsReviewed (由于第一个约束,它可能只是其中一个)并按该元素分组指数.

上面的直接 LINQ 翻译是这样的:

var itemsNotReviewed = Similarities
    .Where(item => !itemsReviewed.Contains(item.FirstIndex) || !itemsReviewed.Contains(item.SecondIndex))
    .GroupBy(item => !itemsReviewed.Contains(item.FirstIndex) ? item.FirstIndex : item.SecondIndex)
    .ToList();

但它包含重复的 itemsReviewed.Contains 检查,这会对性能产生负面影响。

所以更好的变体是引入中间变量,最简单的方法是查询语法和 let 子句:

var itemsNotReviewed =
    (from item in Similarities
     let index = !itemsReviewed.Contains(item.FirstIndex) ? 1 :
            !itemsReviewed.Contains(item.SecondIndex) ? 2 : 0
     where index != 0
     group item by index == 1 ? item.FirstIndex : item.SecondIndex)
    .ToList();