Linq Group By while avoiding Div/0 - c#

I have a model, where I'm trying to calculate the weighted average:
public class ObScore
{
public int ObScoreId { get; set; }
public int AnalystId { get; set; }
public DateTime Date { get; set; }
public int PossScore { get; set; }
public int Weight { get; set; }
}
I can do this, using the following code:
//
// GET: /Test/
public ActionResult Test()
{
var scores = db.ObScores.ToList();
double weightedValueSum = scores.Sum(x=>x.PossScore * x.Weight);
double weightSum = scores.Sum(x => x.Weight);
if (weightSum != 0)
{
ViewBag.wa2 = weightedValueSum / weightSum;
}
return View();
}
However, I want to be able to add a GroupBy clause, so I can group by Date/AnalystId etc.
Is it possible to combine the two LINQ statements, so that I can do this, while still avoiding a DIV/0 should the weightSum = 0?
Thanks, Mark

Use an expression like:
div = weightSum != 0 ? weightedValueSum / weightSum : -1
In the end it should be something like:
var res = scores.GroupBy(p => p.Date.Year)
.Select(p => new {
Year = p.Key,
weightedValueSum = p.Sum(x => x.PossScore * x.Weight),
weightSum = p.Sum(x => x.Weight)
})
.Select(p => new {
Year = p.Year,
wa2 = p.weightSum != 0 ? p.weightedValueSum / p.weightSum : -1
})
.ToArray();
That should be more or less equivalent to this in full LINQ syntax
var res2 = (from p in scores
group p by p.Date.Year into p
let Year = p.Key
let weightedValueSum = p.Sum(x => x.PossScore * x.Weight)
let weightSum = p.Sum(x => x.Weight)
select new {
Year = Year,
wa2 = weightSum != 0 ? weightedValueSum / weightSum : -1
}).ToArray();

Related

How to select students with repeated low scores?

Scores are considered low if they are less than or equal to 5. I want to select students with repeated low scores.
The expected result is:
Andy
Bobby
Cindy
As each of them has repeated low scores.
Question
I got stuck in completing the last expression GroupBy in the Where clause.
Could you make it done?
class Student
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public List<int> Scores { get; set; } = new List<int>();
public static List<Student> GetStudents()
{
return new List<Student>()
{
new Student
{
Id = 1,
Name="Andy",
Scores={1,1,2,2,3,4,5,6,7,8}
},
new Student
{
Id = 2,
Name="Bobby",
Scores={3,3,3,3,4,5}
},
new Student
{
Id = 3,
Name="Cindy",
Scores={1,1,2,2,3,4,5}
},
new Student
{
Id = 4,
Name="Dave",
Scores={1,2,3,4,5,6,7,8,9,10}
}
};
}
}
class Program
{
static void Main()
{
var query = Student.GetStudents()
.Where(s => s.Scores.GroupBy(i => i).????);
foreach (var x in query)
Console.WriteLine(x.Name);
Console.ReadLine();
}
}
I'd do something like this:
var query = Student.GetStudents()
.Where(s => s.Scores
.Where(x => x <= 5)
.GroupBy(i => i)
.Any(x => x.Count() > 1));
Try following :
var query = Student.GetStudents()
.Select(x => new { student = x.Name, scores = x.Scores.GroupBy(y => y).Select(y => new { score = y.Key, count = y.Count() }).ToList() }).ToList();
var lowScore = query.Where(x => x.scores.Any(y => (y.count > 1) && (y.score <= 5))).ToList();

Calculations on grouped rows using lambda functions in c#

I am trying to calculate Best and Worst body measurement changes for people who go on a fitness trip.
I have a database full of before and after body composition measurements for several people who go on various trips. Every participant and every trip has an Id. There are 3 types of readings, B(efore), M(iddle) and A(fter). Here is an example of the data:
ParticipantId TripId Type Weight BodyFatPct
1 2 B 195 22.8
1 2 B 189.6 24.1
1 2 A 186.6 21.2
1 2 A 187.6 23.8
2 3 B 199.2 23.7
2 3 B 198.4 25.1
2 3 A 193 22.4
Here is the class I'm using to represent the data:
public partial class Detail
{
public int ParticipantId { get; set; }
public int TripId { get; set; }
public string Type { get; set; }
public double? Weight { get; set; }
public double? BodyFatPct { get; set; }
}
Here is my highly inefficient C# code to calculate best and worst for weight and body fat.
List<Detail> result = new List<Detail>();
var _result = result.GroupBy(x => new { x.ParticipantId, x.TripId });
foreach(var res in _result)
{
var beforeHighWeight = res.Where(x => x.Type == "B").Max(x => x.Weight);
var beforeLowWeight = res.Where(x => x.Type == "B").Min(x => x.Weight);
var afterWeight = res.Where(x => x.Type == "A").Min(x => x.Weight);
var beforeHighFat = res.Where(x => x.Type == "B").Max(x => x.BodyFatPct);
var beforeLowFat = res.Where(x => x.Type == "B").Min(x => x.BodyFatPct);
var afterFat = res.Where(x => x.Type == "A").Min(x => x.BodyFatPct);
var BestWeightDiff = BeforeHighWeight - afterWeight;
var WorstWeightDiff = BeforeLowWeight - afterWeight;
var BestFatDiff = BeforeHighFat - afterFat;
var WorstFatDiff = BeforeLowFat - afterFat;
}
In actuality, I have about 15 fields to calculate, not just two. Is there a lambda function that does row-wise calculations on grouped data? Any help appreciated.
Performance optimizations normally come with the cost of less maintainable code. So if performance is almost sufficient you should probably follow the hint as commented by Prasad Telkikar and avoid filtering res more than once with the same predicate, i.e. you should assign the filtered lists and work with them:
var resA = res.Where(x => x.Type == "A")
var resB = res.Where(x => x.Type == "B")
If performance is an issue and affords work, you can enumerate the list once and maybe stream the values from the database by using Aggregate. You could e.g. create a class that holds all the values you want to calculate and has a method to update the values given a new entry. You could e.g. create the classes
public class Statistic
{
public int BeforeHighWeight { get; private set; }
public int BeforeLowWeight { get; private set; }
// Add the dimensions you are interested in
public Statistic(Detail detail)
{
// initialize with given Detail
}
public Statistic AddDetail(Detail detail)
{
// update Statistic with given Detail
return this;
}
}
public class Statistics
{
private readonly ConcurrentDictionary<(int, int), Statistic> _statistics = new ConcurrentDictionary<(int, int), Statistic>();
public Statistics AddDetail(Detail detail)
{
_statistics.AddOrUpdate(
(detail.ParticipantId, detail.TripId),
key => new Statistic(detail),
(key, statistic) => statistic.AddDetail(detail)
);
return this;
}
}
Then you could aggregate your values like so:
var rand = new Random();
var result = Enumerable.Range(1, 1000000)
.Select(i => new Detail {ParticipantId = i % 10000, TripId = rand.Next(100000) /*, ...*/})
.Aggregate(
new Statistics(),
(statistics, detail) => statistics.AddDetail(detail)
);

merge 2 lists into a new list that contains both data from list 1 and list 2 - where the unique key is "VariantId"

I have two lists of data (they are using the same class "SaleNumber").
Each list contains a list of sale numbers. The first list is taken from the danish "DK" database and the other from the swedish database.
Right now I am looping through the danish list For each item I loop through I find the item with the same variant id in the swedish list and then I join the data into a new list called saleNumbers.
The problem with this is that because I loop through the danish list then if the danish list doesn't have salenumbers for that variant id then it won't loop through this variant. If this happens then the swedish list item won't be added either - and therefore the salenumbers item won't be created - even though it should - it should have a 0 in salenumbers.totalsalesDK and the actual salenumber for the salenumbers.totalsalesSE.
How do I merge the two together into salenumbers without missing any variants?
I still want the structure retained - so that for instance I have the SaleNumbers.TotalSales showing sum of totalsales for both dk and se together. And the SaleNumbers.TotalSalesDK showing DK sales and SaleNumbers.TotalSalesSE showing SE sales for that item. The primary unique key is always the variantId. Here is my current code:
private List<SaleNumber> ConvertDataTableToSaleNumbers(DataTable dt)
{
List<SaleNumber> saleNumbers = new List<SaleNumber>();
foreach (DataRow dr in dt.Rows)
{
saleNumbers.Add(new SaleNumber() { ProductId = int.Parse(dr["productid"].ToString()), TotalSales = int.Parse(dr["totalsales"].ToString()), VariantId = int.Parse(dr["variantid"].ToString()) });
}
return saleNumbers;
}
DataTable dtDK = new Shoply.Data.DLOrderDetail().GetNumberOfSalesSinceOrderId(constDaysAgo,
Shoply.Data.DLBasis.GetTheConnectionToTheLanguage("dk"));
DataTable dtSE = new Shoply.Data.DLOrderDetail().GetNumberOfSalesSinceOrderId(constDaysAgo,
Shoply.Data.DLBasis.GetTheConnectionToTheLanguage("se"));
List<SaleNumber> saleNumbersDK = ConvertDataTableToSaleNumbers(dtDK);
List<SaleNumber> saleNumbersSE = ConvertDataTableToSaleNumbers(dtSE);
var saleNumbers = saleNumbersDK.SelectMany
(
foo => saleNumbersSE.Where(bar => foo.VariantId == bar.VariantId).DefaultIfEmpty(),
(foo, bar) => new SaleNumber
{
VariantId = foo.VariantId,
ProductId = foo.ProductId,
TotalSales = foo.TotalSales + (bar == null ? 0 : bar.TotalSales),
TotalSalesDK = foo.TotalSales,
TotalSalesSE = (bar == null ? 0 : bar.TotalSales)
}
);
EDIT:
Code updated to perform outerjoin
How about using Join in Linq.
Simple dotnetfiddle can be seen here : Dotnetfiddle link
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
List<SaleNumber> saleNumbersDK = new List<SaleNumber> {
new SaleNumber() { VariantId="a",ProductId="A",TotalSales=10 },
new SaleNumber() { VariantId="b",ProductId="B",TotalSales=20 }
};
List<SaleNumber> saleNumbersSE = new List<SaleNumber> {
new SaleNumber() { VariantId="a",ProductId="A",TotalSales=10 },
new SaleNumber() { VariantId="c",ProductId="c",TotalSales=30 }
};
var innerjoin = saleNumbersDK.Join(saleNumbersSE, d => d.VariantId, s => s.VariantId, (d, s) =>
{
return new SaleNumber()
{
VariantId = d.VariantId,
ProductId = d.ProductId,
TotalSales = d.TotalSales+ (s == null ? 0 : s.TotalSales),
TotalSalesDK = d.TotalSales,
TotalSalesSE = (d == null ? 0 : d.TotalSales)
};
});
var pendingright= saleNumbersSE.Except(innerjoin, new CustomComparer());
var pendingleft = saleNumbersDK.Except(innerjoin, new CustomComparer());
var salesNumber= innerjoin.Concat(pendingright).Concat(pendingleft);
foreach (var sale in salesNumber)
{
Console.WriteLine(sale);
}
//Console.ReadLine();
}
}
public class SaleNumber
{
public string VariantId { get; set; }
public string ProductId { get; set; }
public int TotalSales { get; set; }
public int TotalSalesDK { get; set; }
public int TotalSalesSE { get; set; }
public override string ToString()
{
return VariantId+"-"+ProductId+"-"+TotalSales+"-"+TotalSalesDK+"-"+TotalSalesSE;
}
}
public class CustomComparer : IEqualityComparer<SaleNumber>
{
public bool Equals(SaleNumber x, SaleNumber y)
{
return x.VariantId == y.VariantId;
}
public int GetHashCode(SaleNumber obj)
{
return obj.VariantId.GetHashCode();
}
}
Assuming ProductId is the same for DK and SE you can use a group by function like this to get the result you want.
testDK.ForEach(s => s.TotalSalesDK = s.TotalSales);
testSE.ForEach(s => s.TotalSalesSE = s.TotalSales);
testDK.Concat(testSE)
.GroupBy(s => s.VariantId)
.Select(g => new SaleNumber() {
VariantId = g.First().VariantId,
ProductId=g.First().ProductId,
TotalSales = g.Sum(s=>s.TotalSalesDK) + g.Sum(s=>s.TotalSalesSE),
TotalSalesDK=g.Sum(s=>s.TotalSalesDK),
TotalSalesSE=g.Sum(s=>s.TotalSalesSE)
}).ToList()
You can use Concat and ToList methods:
var allProducts = productCollection1.Concat(productCollection2)
.Concat(productCollection3)
.ToList();

Optimizing LINQ Query to avoid multiple enumerations

I have written a code like this in my .NET project:
var v = ctx.Items
.Where(x => x.userid== user.userid)
.Select(e => new MyViewModel
{
Title = e.Title,
CurrentPrice = e.CurrenctPrice.Value,
ItemID = e.ItemID.ToString(),
Sales = e.Transactions.Where(p => p.TransactionDate >= intoPast && p.TransactionDate <= endDate).Sum(x => x.QuantityPurchased)
})
.Where(x => x.Sales > 0 && ((filterWord == "") || (filterWord != "" && x.Title.ToLower().Contains(filterWord.ToLower()))));
where "ctx" is my object context variable...
And this is the ViewModelClass that I use:
public class MyViewModel
{
public string Title { get; set; }
public int Sales { get; set; }
public string ItemID { get; set; }
public double CurrentPrice { get; set; }
}
The thing that most bugs me here is the sales property... As you can see i set its value in select statement. This way all my data gets enumerated every time...
What I was thinking here is to create a method called "getsales()"... And then to just simply call the GetSales method in my where statement like this:
.Where(x=>X.GetSales(/*neccesary parameters here*/)...)
In order to avoid having multiple enumerations...
But I'm not really sure how to do it...
Can someone help me out here?
I think this is what you're looking for:
var v = ctx.Items
.Where(x =>
x.userid == user.userid &&
(filterWord == "" || x.Title.ToLower().Contains(filterWord.ToLower())))
.Select(e => new MyViewModel
{
Title = e.Title,
CurrentPrice = e.CurrentPrice.Value,
ItemID = e.ItemID.ToString(),
Sales = e.Transactions
.Where(p =>
p.TransactionDate >= intoPast &&
p.TransactionDate <= endDate)
.Sum(x => x.QuantityPurchased)
})
.Where(x => x.Sales > 0);

can't select into class using linq

I have a query that works fine when using an anonymous type but as soon as I try to un-anonymize it it fails to select all values into the class.
here is the linq i'm using (in combination with Subsonic 3):
var producten = (from p in Premy.All()
join pr in Producten.All() on p.dekking equals pr.ID
where p.kilometragemax >= 10000 &&
p.CCmin < 3000 &&
p.CCmax >= 3000 &&
p.leeftijdmax >= DateTime.Today.Subtract(car.datumEersteToelating).TotalDays / 365
group p by new { pr.ID, pr.Naam, pr.ShortDesc, pr.LongDesc } into d
select new
{
ID = d.Key.ID,
Dekking = d.Key.Naam,
ShortDesc = d.Key.ShortDesc,
LongDesc = d.Key.LongDesc,
PrijsAlgemeen = d.Min(x => x.premie),
PrijsAlgemeenMaand = d.Min(x => x.premie),
PrijsMerkdealerMaand = d.Min(x => x.premie),
PrijsMerkdealer = d.Min(x => x.premie)
}).ToList();
When I change it to:
List<QuotePremies> producten = (from p in Premy.All()
join pr in Producten.All() on p.dekking equals pr.ID
where p.kilometragemax >= 10000 &&
p.CCmin < 3000 &&
p.CCmax >= 3000 &&
p.leeftijdmax >= DateTime.Today.Subtract(car.datumEersteToelating).TotalDays / 365
group p by new { pr.ID, pr.Naam, pr.ShortDesc, pr.LongDesc } into d
select new QuotePremies
{
ID = d.Key.ID,
Dekking = d.Key.Naam,
ShortDesc = d.Key.ShortDesc,
LongDesc = d.Key.LongDesc,
PrijsAlgemeen = d.Min(x => x.premie),
PrijsAlgemeenMaand = d.Min(x => x.premie),
PrijsMerkdealerMaand = d.Min(x => x.premie),
PrijsMerkdealer = d.Min(x => x.premie)
}).ToList();
in combination with this class:
public class QuotePremies
{
public byte ID { get; set; }
public string Dekking { get; set; }
public string ShortDesc { get; set; }
public string LongDesc { get; set; }
public decimal PrijsAlgemeen { get; set; }
public decimal PrijsAlgemeenMaand { get; set; }
public decimal PrijsMerkdealer { get; set; }
public decimal PrijsMerkdealerMaand { get; set; }
}
it doesn't give me an error but all values in the class are 0 except for QuotePremies.ID, QuotePremies.ShortDesc and QuotePremies.LongDesc. No clue why that happens.
See if using conversion helps
PrijsAlgemeen = Convert.ToDecimal(d.Min(x => x.premie))
I believe the problem has to do with casting. Why not write and extension method for IEnumberable which would take this query result and return a collection of List. It could look something like this:
public static class Extensions
{
// extends IEnumerable to allow conversion to a custom type
public static TCollection ToMyCustomCollection<TCollection, T>(this IEnumerable<T> ienum)
where TCollection : IList<T>, new()
{
// create our new custom type to populate and return
TCollection collection = new TCollection();
// iterate over the enumeration
foreach (var item in ienum)
{
// add to our collection
collection.Add((T)item);
}
return collection;
}
}
Thanks to kek444 for helping me with a similar problem

Categories

Resources