refactor linq operations to avoid duplicate similar code - c#

I'm trying to simplify this piece of code where the only difference is that, if the referenceDate.Hour it's greater than 20, we should only take items larger than today. Otherwise, we take larger or equal items.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
//todays date is 07/02
//I have the daysToCut 10, 15, 20, 25
//if my referenceDate.Hour is bigger than 8pm, than i want the next day to cut other than today
//but if my referenceDate.Hour is less than 8pm, i want the next day to cut (regardless if it's today or not)
int nextDayToTake = 0;
var daysToCut = new List<int>{10, 15, 20, 25};
var referenceDate = DateTime.Now; //output is 10 bc today is 07/02
//var referenceDate = new DateTime(2023, 02, 10, 20, 00, 00); //output is 15 bc the date's day is 10 and hour equals 20pm
if (referenceDate.Hour >= 20)
{
nextDayToTake = daysToCut.Any(x => x > referenceDate.Day) ? daysToCut.First(x => x > referenceDate.Day) : daysToCut.Min();
}
else
{
nextDayToTake = daysToCut.Any(x => x >= referenceDate.Day) ? daysToCut.First(x => x >= referenceDate.Day) : daysToCut.Min();
}
Console.WriteLine(nextDayToTake);
}
}
I tried it with ternary operators only, but I still found it too complex to understand. Could you help me to refactor in a more elegant way?
var daysToCut = new List<int>{10, 15, 20, 25};
var referenceDate = DateTime.Now;
//var referenceDate = new DateTime(2023, 02, 10, 20, 00, 00);
var nextDayToTake = referenceDate.Hour >= 20 ?
daysToCut.Any(x => x > referenceDate.Day) ? daysToCut.First(x => x > referenceDate.Day) : daysToCut.Min()
: daysToCut.Any(x => x >= referenceDate.Day) ? daysToCut.First(x => x >= referenceDate.Day) : daysToCut.Min();
Console.WriteLine(nextDayToTake);

I would aim for isolating the most basic expressions that actually differ from each other inside of each if loop, and creating logic based on those expressions.
Inside your if/else,
if (referenceDate.Hour >= 20)
{
nextDayToTake = daysToCut.Any(x => x > referenceDate.Day)
? daysToCut.First(x => x > referenceDate.Day)
: daysToCut.Min();
}
else
{
nextDayToTake = daysToCut.Any(x => x >= referenceDate.Day)
? daysToCut.First(x => x >= referenceDate.Day)
: daysToCut.Min();
}
the differing expressions are the following emphasized ones:
nextDayToTake = daysToCut.Any(x => x > referenceDate.Day)
       ? daysToCut.First(x => x > referenceDate.Day)
       : daysToCut.Min();
nextDayToTake = daysToCut.Any(x => x >= referenceDate.Day)
       ? daysToCut.First(x => x >= referenceDate.Day)
       : daysToCut.Min();
(The > and >= operators are the only actually differing parts in these expressions, but I will work with the whole expressions.)
I will name these expressions predicates, seeing as predicate is an established name of the input parameter of the Enumerable.Any() overload you are using.
To generalize a little, we could name the first predicate predicate A, and the second predicate predicate B:
nextDayToTake = daysToCut.Any(predicate A)
       ? daysToCut.First(predicate A)
       : daysToCut.Min();
nextDayToTake = daysToCut.Any(predicate B)
       ? daysToCut.First(predicate B)
       : daysToCut.Min();
Now, predicate A and predicate B are both of the same data type; they are both a function that takes an int (x) as an input parameter and returns a bool value (based on x being compared to referenceDate.Day). The data type of both predicates is hence Func<int, bool>.
The predicates can be defined as follows:
Func<int, bool> predicateA = x => x > referenceDate.Day;
Func<int, bool> predicateB = x => x >= referenceDate.Day;
(For clarity, I'd prefer to name the input variable something more descriptive:)
Func<int, bool> predicateA = dayInMonth => dayInMonth > referenceDate.Day;
Func<int, bool> predicateB = dayInMonth => dayInMonth >= referenceDate.Day;
Seeing as predicate A and predicate B share data type, we could create one single predicate variable and assign whichever of predicate A and predicate B is relevant. The single predicate would then be used in the exact same manner:
nextDayToTake = daysToCut.Any(predicate)
       ? daysToCut.First(predicate)
       : daysToCut.Min();
The condition for assigning the correct predicate is the one you have provided in your original code:
Func<int, bool> predicate = referenceDate.Hour >= 20 ? predicateA : predicateB;
(You would possibly want to create a helper variable, for readability:)
var conditionForA = referenceDate.Hour >= 20;
Func<int, bool> predicate = conditionForA ? predicateA : predicateB;
Now, you can simply use your predicate in your calculation of nextDayToTake. No if loop or code duplication needed.
var nextDayToTake = daysToCut.Any(predicate)
? daysToCut.First(predicate)
: daysToCut.Min();
Putting everything together, the implementation could look something along the lines of:
var daysToCut = new List<int> { 10, 15, 20, 25 };
var referenceDate = DateTime.Now;
var conditionForA = referenceDate.Hour >= 20;
Func<int, bool> predicateA = dayInMonth => dayInMonth > referenceDate.Day;
Func<int, bool> predicateB = dayInMonth => dayInMonth >= referenceDate.Day;
Func<int, bool> predicate = conditionForA ? predicateA : predicateB;
var nextDayToTake = daysToCut.Any(predicate) ? daysToCut.First(predicate) : daysToCut.Min();
Note:
I would strongly suggest that you rename conditionForA, predicateA and predicateB to something more descriptive. I don't quite understand the logic in your code, so I have not made an attempt to create suitable variable names myself.
Example fiddle here.

use
var referenceDate = DateTime.Now.AddHours(4).Day;
take items larger than or equal to.

Related

How to move code to data, becoming more Data Driven Approach?

For reference, please see article: https://gameprogrammingpatterns.com/bytecode.html#data-%3E-code
Hello, I'm currently looking at a lot of repetition in my game code in C#,
as for example:
public void SomeAction() {
DoSomething();
DoSomething();
DoAnotherThing();
}
I have managed to convert this code to data driven approach, using command pattern:
public List<Command> commands;
public void DoAction() {
foreach (cmd in commands) {
cmd.Execute();
}
}
I have realized that Data driven approach is beautifully designed and it is the right approach for me.
But then I stumbled into this one:
private float[] data;
public IEnumerable<float> ExampleQuery() {
return data
.Select(x => x + 2)
.Where(x => x < 50)
.Select(x => x * 3)
.Select(x => x * 10)
.Select(x => x > 999 ? 999 : x);
}
how to convert to this:
private float[] data;
public List<Rules> rules;
public IEnumerable<float> Query() {
// should I foreach here?
}
As you can see, this one should be data driven, so that if I want to make more rules for data query, I don't have to recompile and can just add more rules into the list.
From the reference:
We want them to be easy to modify, easy to reload, and physically separate from the rest of the executable.
I don’t know about you, but to me that sounds a lot like data. If we can define our behavior in separate data files that the game engine loads and “executes” in some way, we can achieve all of our goals. We just need to figure out what “execute” means for data.
I don't need to be all-out using bytecode pattern, I just want my rules to be hierarchy data, that is modeled using class/object, such as command pattern.
*Any good reference article will be a big help also.
You could do:
public List<Rules> rules;
var floats = data.Select(x => x + 2)
.Where(x => x < 50)
.Select(x => x * 3)
.Select(x => x * 10)
.Select(x => x > 999 ? 999 : x);
public List<Rules> ExampleQuery()
{
foreach(var d in floats)
{
rules.Add(d);
}
return rules;
}
Just use the a list of functions like this:
List<( Func<float,bool> where, Func<float,float> select )> rules;
public IEnumerable<float> Query ( IEnumerable<float> original ) {
IEnumerable<float> result = original;
foreach ( var i in rules ) {
result = result.Where( i.where ).Select( i.select );
}
return result;
}
rules.AddRange( new[] {
( (x => true), (x => x + 2) ),
( (x => x < 50), (x => x) ),
( (x => true), (x => x * 3) ),
( (x => true), (x => x * 10) ),
( (x => true), (x => ( x > 999 ) ? 999 : x) )
} );
or even better:
List<Func<IEnumerable<float>,IEnumerable<float>>> rules;
public IEnumerable Query ( IEnumerable<float> original ) {
foreach ( var i in rules ) original = i.Invoke( original );
return original;
}
rules.AddRange( new[] {
( x => x.Select( y => y + 2 ) ),
( x => x.Where( y => y < 50 ) ),
( x => x.Select( y => y * 3 ) ),
( x => x.Select( y => y * 10 ) ),
( x => x.Select( y => ( y > 999 ) ? 999 : y ) ),
} );

Count users where subscription end within month Linq c#

I just want to count users where their subscription end date is within the coming month:
int Count = oUsers
.Where(x =>
0 < x.SubscriptionEnddate.Value.Subtract(DateTime.Now).Days < 30)
.Count();
But its not working.
What I want to do is 0 < Value < 30.
Use &&. Period. It is where it is designed for.
You can circumvent this by creating a Between extension method, or concatenate two Where clauses, but really, why trade that over &&. But if you insist:
int c = oUsers
.Select(x=> x.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days)
.Where(d => 0 < d)
.Where(d => d < 30)
.Count();
you can try something like this
int count = oUsers.Where(x=> x.Days > 0).Count(x => x.Days < 30);
I would do it this way to keep it readable:
int Count =
oUsers
.Select(u => new
{
u,
days = u.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days,
})
.Where(x => x.days > 0 && x.days < 30)
.Select(x => x.u)
.Count();
This obviously uses && but it eliminates the code duplication which I think is what you're really trying to avoid.
The use of .Count() at the end removes the need to keep track of the original value, so, as Patrick has already posted, this suffices:
int Count =
oUsers
.Select(u => u.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days)
.Where(x => x > 0 && x < 30)
.Count();
Not always the shorter why is better.
When someone else will read your code it will be much easier for him to understand when you calling SomeRange method
I think the best why is when the code more readable so you can do method for returning if your value is match the start and end ranges and call it.
example for some int range but the same why you can check dates
public static bool SomeRange(int value,int start,int end)
{
return (value > start && value < end);
}
static void Main()
{
List<int> valueList = new List<int> { 1,65,3,76,34,23,11,5,665,334};
var result = valueList.Where(x => SomeRange(x, 0, 30)).Count();
}
Yes Solve it with Enumerable.Range:
oUsers.Where(x => x.SubscriptionEnddate != null && Enumerable.Range(0, 30).Contains(x.SubscriptionEnddate.Value.Subtract(DateTime.Now).Days)).Count();
I'd say, precompute the range start and end before executing the count, then compare for start && end:
// some test data
var oUsers = Enumerable.Range(0, 100).Select(x => new { SubscriptionEnddate = (DateTime?)DateTime.Now.AddDays(x) });
var rangeStart = DateTime.Now;
var rangeEnd = rangeStart.AddDays(30);
// Conditional count... can also be done as .Where(condition).Count()
int Count = oUsers.Count(x => x.SubscriptionEnddate >= rangeStart && x.SubscriptionEnddate < rangeEnd);
Possibly use DateTime.Today instead since you are only interested in days.
You could use nested if statements, if you're really opposed to using &&
if x.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days > 0 {
if x.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days < 30 {
int count = oUsers;
}
}
This seems like an incredibly obtuse way to go about this though. This is why we have the && operator.

How can I compare two lists of uints and calculate the difference in any values?

I have two lists of uint type called firstReadOfMachineTotals and secondReadOfMachineTotals
I'm completely new to C# programming.
I would like to compare both lists and, if any of the values in the second list are higher than the first list, calculate by how much.
For instance...
firstReadOfMachineTotals = 10, 20, 4000, 554
secondReadOfMachineTotals = 10, 40, 4000, 554
I want to return '20' (based on the second item being 20 more than the equivalent in the first list).
Thanks
PS. There will never be more than one number different in the second list.
You can use a combination of Zip, Where:
var firstReadOfMachineTotals = new[]{ 10, 20, 4000, 554 };
var secondReadOfMachineTotals = new[]{ 10, 40, 4000, 554};
var result = firstReadOfMachineTotals.Zip(secondReadOfMachineTotals, (a,b) => b > a ? b - a : 0)
.Where(x => x > 0)
.OrderByDescending(x => x)
.FirstOrDefault();
Console.WriteLine(result); // output = 20
This method will default to 0 when all values are the same. If instead you wanted control of this default you could also do:
var firstReadOfMachineTotals = new[]{ 10, 20, 4000, 554 };
var secondReadOfMachineTotals = new[]{ 10, 40, 4000, 554};
var result = firstReadOfMachineTotals.Zip(secondReadOfMachineTotals, (a,b) => b > a ? b - a : 0)
.Where(x => x>0)
.DefaultIfEmpty(int.MinValue) // or whatever default you desire
.Max();
Console.WriteLine(result); // output = 20
You can index into the lists and simply take the difference of each element at the specified index then sum the difference to retrieve the result.
int result = Enumerable.Range(0, Math.Min(list1.Count, list2.Count))
.Select(i => list2[i] - list1[i] <= 0 ? 0 : list2[i] - list1[i]).Sum();
Use Zip:
var result = firstReadOfMachineTotals.Zip(secondReadOfMachineTotals,
(f, s) => s > f ? s - f : 0).Where(f => f > 0).DefaultIfEmpty(-1).Max();
The simplest solution will be to sort both the arrays
array1.sort()
array2.sort()
and compare each indexes and take action
for(int i=0;i<array1.lenght;i++)
{
if(array1[i] < array2[i])
{
// Specify your action.
}
}
Another way to do this is the following:
int difference = arr1
.Zip(arr2, (a, b) => (int?)Math.Max(b - a, 0))
.SingleOrDefault(d => d != 0) ?? 0;
It returns the difference if there is an element in the second collection which is larger than its corresponding element from the first collection.
If there isn't any, it returns zero.
Information to read:
LINQ Zip
LINQ FirstOrDefault
?? Operator
Nullable Types (int?)

Couting items and grouping them by levels

I am using Entity Framework 6 and I have the following Linq Query:
IDictionary<BodyMassIndexLevel, Int32> bmistats =
context.Evaluations
// Get all evaluations where Height and Weight measures were done
.Where(x => x.Height != null && x.Weight != null)
// Select the date of the evaluation, the worker id and calculate BMI
.Select(x => new { Date = x.Date, Worker = x.Worker.Id, BMI = x.Weight.Value / Math.Pow(x.Height.Value / 100, 2) })
// Group by worker
.GroupBy(x => x.Worker)
// Get the most recent evaluation for each worker and so the most recent BMI
.Select(x => x.OrderByDescending(y => y.Date).Select(y => new { BMI = y.BMI }).FirstOrDefault())
// Cache the result in memory
.ToList()
// Count the number of BMIS in each level
.With(z =>
new Dictionary<BodyMassIndexLevel, Int32> {
{ BodyMassIndexLevel.SevereThinness, z.Count(w => w.BMI < 16) },
{ BodyMassIndexLevel.MildThinness, z.Count(w => w.BMI >= 16 && w.BMI < 17) },
{ BodyMassIndexLevel.ModerateThinness, z.Count(w => w.BMI >= 17 && w.BMI < 18.5) },
{ BodyMassIndexLevel.Normal, z.Count(w => w.BMI >= 18.5 && w.BMI < 25) },
{ BodyMassIndexLevel.PreObese, z.Count(w => w.BMI >= 25 && w.BMI < 30) },
{ BodyMassIndexLevel.ObeseClassI, z.Count(w => w.BMI >= 30 && w.BMI < 35) },
{ BodyMassIndexLevel.ObeseClassII, z.Count(w => w.BMI >= 35 && w.BMI < 40) },
{ BodyMassIndexLevel.ObeseClassIII, z.Count(w => w.BMI >= 40) }
}
);
I have two questions:
Is is possible to improve the performance of this query?
Can I move the Count part in levels to the query and so having not ToList()?
For example
Make something like Truncate for BMI after
// Select the date of the evaluation, the worker id and calculate BMI
Create BMILevel table with columns (BMILevelName | BMIValues) containing rows like (BodyMassIndexLevel.ModerateThinness, 17), (BodyMassIndexLevel.PreObese, 25), (BodyMassIndexLevel.PreObese, 26), etc.
JOIN your select query with *BMILevel* table on query.BMI = BMILevel.BMIValue, than GroupBy BMILevel.BMILevelName and finally Count for all groups.
Alternatively you may define BMILevel with columns (BMILevelName | BMIValueBeginInterval, BMIValueEndInterval) containing rows like (BodyMassIndexLevel.ModerateThinness, 17, 18), (BodyMassIndexLevel.PreObese, 25, 30).
And thus perform
query JOIN BMILevel ON query.BMI BETWEEN BMILevel.BMIValueBeginInterval AND BMILevel.BMIValueEndInterval
I consider EF can transform '<', '&&', '>' within .Where() (or Join) call properly
UPDATE:
If you don't want to create another one table, you may try create in-memory list of objects of sample type
class BMILevel {
public BMILevelEnum BMILevelName {get;set;}
public double BMILevelValueBeginInterval {get;set;}
public double BMILevelValueEndInterval {get;set;}
}
than create in-memory collection:
var bmiLevels = new List<BMILevel> { new BMILevel {...}, ... }
and use it in the way I describe it above.
I don't know how good EF 6 is, but old versions were unable to handle operations with non-entities (it couldn't translate expressions to proper SQL) and thus it results in inefficient querying or errors.
The only way to perform your query faster is to delegate it to SQL server. You can use EF abilities and thus it's possible that it requires of the creation of a new table. Another way - use ADO.NET (SqlCommand, SqlConnection, etc) and do it bypassing EF.

EF 6: The result of a query cannot be enumerated more than once

Is there any way to avoid "The result of a query cannot be enumerated more than once" exception without using ToList()?
Here is a simplified version of my code:
var entities = _db.Trades.Where(t => t.A > 10);
int totalCount = entities.Count();
entities = entities.Where(t => t.B > 10);
int totalCountAfterAdditionalFilter = entities.Count();
I cannot call ToList() due to performance considerations. I know that I could just generate one more IQueryable, but that seems wrong (I have more filters like this). Could I somehow preserve/duplicate the IQueryable after my first call of Count()?
Thanks!
No, you can't achieve your desired result like that. As an alternative you can, however, save your filters to variables and then compose them as needed:
Func<Trade, bool> filter1 = t => t.A > 10;
Func<Trade, bool> filter2 = t => t => t.B > 10;
Func<Trade, bool> compositeFilter = t => filter1(t) && filter2(t);
int totalCount = _db.Trades.Count(filter1);
int totalCountAfterAdditionalFilter = _db.Trades.Count(compositeFilter);
//Need more?
compositeFilter = t => compositeFilter(t) && t.C > 100;
int totalAfterMoreFilters = _db.Trades.Count(compositeFilter);
may be:
Trades.Where(x => x.A > 10).Select(x => new { i = 1, j = x.B > 10 ? 1 : 0}).
GroupBy(y => y.i).
Select(g => new { c = g.Count(), = g.Sum(z => z.j)})
That's give you the 2 informations in one query.

Categories