Lambda Max and Max and Max - c#

Quick and probably easy Lambda question:
I have a restaurant with reviews.
I want to query for the one with the:
Max(AverageRating)
And the Max(ReviewCount)
And the Max(NewestReviewDate)
And the Min(DistanceAway)
Something like this:
var _Result = AllRestaurants
.Max(x => x.AverageRating)
.AndMax(x => x.ReviewCount)
.AndMax(x => x.NewestReviewDate)
.AndMin(x => x.DistanceAway);
Now, I know that is pseudo code. But it describes it perfectly!
Of course, in multiple statements, this is simple.
Just wondering if this is possible in one statement without killing the readability.
Thank you in advance. I know some of you love the query questions!

You can't have multiple maxes or mins, that doesn't make sense.
You'll need some kind of heuristic like:
.Max(x => x.AverageRating * x.ReviewCount - x.DaysSinceLastReview - x.DistanceAway)

Perhaps this would do?
var bestRestaurant = AllRestaurants
.OrderByDescending(r => r.AverageRating)
.ThenByDescending(r => r.ReviewCount)
.ThenByDescending(r => r.NewestReviewCount)
.ThenBy(r => r.DistanceAway)
.FirstOrDefault();
You'd need to change the order of the statements to reflect which is the most important.

An alternative to having some weighted heuristic is to order by AverageRating, then ReviewCount, then ...
Something like this should work:
var _Result = AllRestaurants
.OrderByDescending(x => x.AverageRating)
.ThenByDescending(x => x.ReviewCount)
.ThenByDescending(x => x.NewestReviewDate)
.ThenByDescending(x => x.DistanceAway);
// using *Descending so you get the higer-valued ones first

Consider something like this...
List<RestaurantRecord> _Restaurants;
public RestaurantRecord Best()
{
return _Restaurants.Where(
x =>
x.AverageRating >= _BestRating &&
x.ReviewCount >= _MinReviews &&
x.Distance <= _MaxDistance)
.GetFirstOrDefault();
}
That being said, using a lambda in this case will have maintainability consequences down the road. It would be a good idea to refactor this, such that if other criteria appear in the future (e.g.: smartphone access? Cuisine type?), your app can be more easily modified to adapt to those.
On that note, a slightly better implementation might be something like:
public RestaurantRecord Best()
{
IQueryable temp = _Restaurants.Clone();
temp = temp.Where( x => x.AverageRating >= _BestRating );
temp = temp.Where( x => x.ReviewCount >= _MinReviews );
// ...snip...
return temp.GetFirstOrDefault();
}
I hope this sets you on the right track. :)

If I'm understanding your question, I think the best approach is going to be writing individual statements as you mention...
var HighestRating = AllRestaurants.Max(x => x.AverageRating);
var HighestReviewCount = AllRestaurants.Max(x => x.ReviewCount);
var LatestReviewDate = AllRestaurants.Max(x => x.NewestReviewDate);
var ShortestDistanceAway = AllRestaurants.Min(x => x.DistanceAway);
Retrieving various maxes and mins from a single Linq query would get pretty messy and I'm not sure there'd be any advantage with efficiency, either.

Related

Looping through fields to get sum

I know there are probably 100 much easier ways to do this but until I see it I can't comprehend how to go about it. I'm using linqpad for this. Before I go to phase 2 I need to get this part to work!
I've connected to an SQL database.
I'm running a query to retrieve some desired records.
var DesiredSym = (from r in Symptoms
where r.Status.Equals(1) && r.Create_Date < TimespanSecs
select r).Take(5);
So, in this example, I retrieve 5 'records' essentially in my DesiredSym variable as iQueryable (linqpad tells me this)
The DesiredSym contains a large number of fields including a number feilds that hold a int of Month1_Used, Month2_Used, Month3_Used .... Month12_Use.
So I want to loop through the DesiredSym and basically get the sum of all the Monthx_Used fields.
foreach (var MonthUse in DesiredSym)
{
// get sum of all fields where they start with MonthX_Used;
}
This is where I'm not clear on how to proceed or even if I'm on the right track. Thanks for getting me on the right track.
Since you've got a static number of fields, I'd recommend this:
var DesiredSym =
(from r in Symptoms
where r.Status.Equals(1) && r.Create_Date < TimespanSecs
select retireMe)
.Take(5);
var sum = DesiredSym.Sum(s => s.Month1_Use + s.Month2_Use + ... + s.Month12_Use);
You could use reflection, but that would be significantly slower and require more resources, since you'd need to pull the whole result set into memory first. But just for the sake of argument, it would look something like this:
var t = DesiredSym.GetType().GenericTypeArguments[0];
var props = t.GetProperties().Where(p => p.Name.StartsWith("Month"));
var sum = DesiredSym.AsEnumerable()
.Sum(s => props.Sum(p => (int)p.GetValue(s, null)));
Or this, which is a more complicated use of reflection, but it has the benefit of still being executed on the database:
var t = DesiredSym.GetType().GenericTypeArguments[0];
var param = Expression.Parameter(t);
var exp = t.GetProperties()
.Where(p => p.Name.StartsWith("Month"))
.Select(p => (Expression)Expression.Property(param, p))
.Aggregate((x, y) => Expression.Add(x, y));
var lambda = Expression.Lambda(exp, param);
var sum = DesiredSym.Sum(lambda);
Now, to these methods (except the third) to calculate the sum in batches of 5, you can use MoreLINQ's Batch method (also available on NuGet):
var DesiredSym =
from r in Symptoms
where r.Status.Equals(1) && r.Create_Date < TimespanSecs
select retireMe;
// first method
var batchSums = DesiredSym.Batch(5, b => b.Sum(s => s.Month1_Use ...));
// second method
var t = DesiredSym.GetType().GenericTypeArguments[0];
var props = t.GetProperties().Where(p => p.Name.StartsWith("Month"));
var batchSums = DesiredSym.Batch(5, b => b.Sum(s => props.Sum(p => (int)p.GetValue(s, null))));
Both these methods will be a bit slower and use more resources since all the processing has to be don in memory. For the same reason the third method will not work, since MoreLinq does not support the IQueryable interface.

How do I write this in LINQ?

Im sure there is some way to write this code in Linq. But I'm new to LINQ and don't know how to do it?
Here is the code:
List<IEntityMITARBEITER> leiter = new List<IEntityMITARBEITER>();
foreach (IEntityMITARBEITER mitarbeiter in mit)
{
foreach (IEntityREF_SCHULLUNG refs in refSchullung)
{
if (refs.Id_person == mitarbeiter.Id_mit)
{
leiter.Add(mitarbeiter);
}
}
}
leiter = mit.Where(x => refSchullung.Any(y => y.Id_person == x.Id_mit)).ToList();
(in case the co-worker doesn't appear in more courses.)
var selectedMitarbeiter = mit
.Where(m => refSchulung.Any(s => s.Id_person == m.Id_mit));
leiter.AddRange(selectedMitarbeiter.ToList());
why do you want re-write it using LINQ? What you have done is ok:it is very readable and much faster than it would be in LINQ
If you really want to use LINQ I can suggest you to install ReSharper which will convert it for you.
http://blogs.jetbrains.com/dotnet/2009/12/resharper-50-preview-loops-2-linq/
Using Join() would make a lot of sense in this case:
var joinQuery = mit.Join(refSchullung, x => x.Id_mit, x => x.Id_Person, (x, y) => x);
leiter.AddRange(joinQuery.ToList());
This will match up each element of the two sequences where the keys match, and select a single item for each case.

Lambda Expression

Can I simplify this statement with a lambda expression?
var project = from a in accounts
from ap in a.AccountProjects
where ap.AccountProjectID == accountProjectId
select ap;
var project = accounts.SelectMany(a => a.AccountProjects)
.Where(x => x.AccountProjectID == accountProjectId);
Whether this is actually simpler is a matter of taste.
Honestly, it looks pretty clear to me. I think that a lambda in this case may be less readable, i.e., something like Brandon posted below.
(Stolen from Brandon's post)
var project = accounts.Select(a => a.AccountProjects)
.Where(x => x.AccountProjectID == accountProjectId);
As far as readability is concerned, I think that a couple of loops is preferable to the lambda solution, and I think that your solution is preferable to the loops.
I agree with Ed Swangren. This looks concise and readable enough.
Actually the answer to your question depends on 3 things:
What you want to achieve - better readability? better performance? etc.
The type of 'accounts'
How the resulting collection is going to be used.
If you want better performance, and in case 'accounts' is a List, and the resulting collection will be iterated or passed to another method for iterating soon enough after these lines of code, I would do something like that:
List<Account> filteredAccounts = new List<Account>();
accounts.ForEach(a => { if (a.AccountProjectID == accountProjectId) filteredAccounts.Add(a); });
Surely it's less readable then your LINQ statement, but I would use these 2 lines rather than accounts.Select.......
And surely it's much better optimized for performance, which is always important I believe.
accounts
.SelectMany (
a => AccountProjects,
(a, ct) =>
new
{
a = a,
ap = ap
}
)
.Where (t => (t.ap.AccountProjectID == t.a.accountProjectId))
.Select (t => t.ap)

Lambda expression to grab all values inside a Dictionary

I have a LINQ query which returns all the values inside a Dictionary, conditional upon something:
var apps =
from entry in shape.Decorators
where entry.Value == DecoratorLayoutStyle.App
select entry.Key;
shape.Decorators is a
Dictionary<Shape, DecoratorLayoutStyle>
Is there something terser, and/or can I use a combination of lambdas or something?
var apps = shape.Decorators
.Where(x=>x.Value == DecoratorLayoutStyle.App)
.Select(x=>x.Key);
I think yours is just as fine.
That looks plenty terse to me, I guess you could use the extension functions instead of the from/select linq syntax, but that wouldn't be too different.
More imporantly, I'm not sure you want terser. The current format is very readable, and clearly documents exactly what you're trying to do.
var apps = shape.Decorators
.Where(e => e.Value == DecoratorLayoutStyle.App)
.Select(e => e.Key);
Do you think this is terser?
Personally I prefer the query syntax when I have more than one LINQ operator all the operators I use can be translated to it.
var apps = Shape.Decorators.Where(x => x.Value == DecoratorLayoutStyle.App)
.Select(x => x.Key);
Just to be different.
var apps = shape.Decorators.Keys
.Where(k => shape.Decorators[k] == DecoratorLayoutStyle.App);

Need help with adding Where clauses in loop with Linq

Below is a rough and simplified code example of what I am trying to do and it is not working as expected. I am using Linq to Entity in case that matters.
Basically in the first loop with the DateClause's ends up returning nothing if I have two date clauses, works great with one date clause. In the TextClause's loop it seems to ignore every text clause after the first one.
My expectation is that the clauses would be ANDed together, so that if I do a text clause and then do another they should be added and I would get a more focused results, especially with using the Contains method.
Expectations for the dates are that I would expect to be able to do a date range with this and but if there are two dates it always returns nothing, even though I know there are records with the right dates in the range.
I am sure I am doing something wrong or plain stupid, but I can't see it.
enum Operators
{
Contains,
DoesNotContain,
GreaterThan,
LessThan
}
public void DoSomething(List<DateClauses> dateClauses, List<TextClauses> textClauses)
{
var query = from t in context.Table
where t.Enabled = true
select new
{
title = t.title
date = t.date
}
foreach (DateClause clause in dateClauses)
{
switch (clause.Operator)
{
case Operator.GreaterThan:
query = query.Where(l => l.date > clause.Date);
break;
case Operator.LessThan
query = query.Where(l => l.date < clause.Date);
break;
}
}
foreach (TextClause clause in textClauses)
{
switch (clause.Operator)
{
case Operator.Contains:
query = query.Where(l => l.title.Contains(clause.Text));
break;
case Operator.DoesNotContain
query = query.Where(l => !l.title.Contains(clause.Text));
break;
}
}
}
EDIT: Updated example code to show use of enum in the process. When I extrapolated the use of the enum in to some of the very nice solutions (that otherwise would work with a bool) cause an exception shown in a comment from me in Joel's response.
I want to say I like what I have seen in responses so far, and I have learned a few new Linq tricks, I apologize for the example as I didn't think bool vs enum would matter much with Linq. I hope the changes will help find a solution that can work for me. Thanks again for the great responses to date.
Just a wild guess, but could it be that changing your loops to the following has different results?
foreach (DateClause clause in dateClauses)
{
var capturedClause = clause;
switch (clause.Operator)
{
case Operator.GreaterThan:
query = query.Where(l => l.date > capturedClause.Date);
break;
case Operator.LessThan
query = query.Where(l => l.date < capturedClause.Date);
break;
}
}
You're creating Where criteria capturing the loop variable, not the value of the loop variable for each iteration. So in the end, each Where will be the same, using the value for the last iteration of the loop, as the query is executed after the last iteration. By introducing a temp variable you'll capture the variable for each iteration.
See also Eric Lippert's blog about this subject.
var query = context.Table
.Where(t => t.Enabled
&& textClauses.Where(c => c.Contains).All(c => t.title.Contains(c.Text) )
&& textClauses.Where(c => c.DoesNotContain).All(c => !t.title.Contains(c.Text) )
&& dateClauses.Where(c => c.GreaterThan).All(c => t.date > c.Date) )
&& dateClauses.Where(c => c.LesserThan).All(c => t.date < c.Date) )
).Select(t => new {
title = t.title
date = t.date
});
The main thing here is that each of your current foreach loops can be refactored to use .All() instead. Or, rather, each if condition inside the loop can be. If you really want to you can still break out each of the where's:
var query = context.Table.Where(t => t.Enabled).Select(t => new {
title = t.title
date = t.date
});
query = query.Where(t => textClauses.Where(c => c.Contains).All(c => t.title.Contains(c.Text) );
query = query.Where(t => textClauses.Where(c => c.DoesNotContain).All(c => !t.title.Contains(c.Text) );
query = query.Where(t => dateClauses.Where(c => c.GreaterThan).All(c => t.date > c.Date) );
query = query.Where(t => dateClauses.Where(c => c.LesserThan).All(c => t.date < c.Date) );
I've done this before with no issues- perhaps you should try moving the .Select to the end (after the Wheres are done). Also, I use PredicateBuilder heavily for added flexibility with dynamic query stuff- right now you're getting "and" semantics for your appended wheres, where I'd guess you want "or" semantics for at least some of them. PredicateBuilder lets you do that easily (and it's free!).

Categories