Conditional GroupBy () in LINQ

I work with a matrix filled with similarities between elements. I save them as a list of objects in my database. The similarity object is as follows:

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 } 

The user can view these items. I want to get a list of items that are "similar" to those items that the user has viewed. The problem is that I cannot say for sure whether the element identifier is in FirstIndex or SecondIndex . I wrote code that does what I want, but I want to know if this is possible in 1 expression.

 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()); 

Where itemsReviewed is a list of lists of items that the user has viewed, and where Similarities is a list of all items that are similar to items that the user has viewed. I retrieve this list using this function:

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

where itemIds is a list of lists of items that the user has viewed.

Is there a way to group either the first or second index based on the Where clause?

Please let me know if I need to clarify!

+5
source share
3 answers

In my opinion, you have a Similarity list, which is guaranteed to contain items with FirstIndex or SecondIndex contained in the itemsReviewed Guid list. And you need to take elements (if any) with the index not contained in itemsReviewed (this can be only one of them due to the first restriction) and the groups at this index.

A direct LINQ translation of the above would be:

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

But it does contain repeated checks on itemsReviewed.Contains , which adversely affect performance.

So the best option would be to introduce an intermediate variable, and the easiest way to do this is with the query syntax and let clause:

 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(); 
+3
source

I would like to change the way the source list is:

 _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(); 

Thus, you only need to group by the compliance index.

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

You can convert after a dynamic object to your affinity class or just change the class to include the corresponding index.

You can convert them to your type of affinity:

 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(); 
+1
source

What about

 (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() 

The let clause introduces a new temporal variable that preserves a logical value. You can, of course, enable another function:

 (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() 

If you want to use non-LINQ syntax, you will probably need to introduce some anonymous types:

 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 ? asSecondIndex : asFirstIndex, a => ax) //edit: to get same semantics, you of course also need the element selector .ToList() 
0
source

Source: https://habr.com/ru/post/1260268/


All Articles