Compare two lists to get waterfall chart data in LinQ - c#

My object has 3 fields - Term, Subject and Marks. I want to have a list of items whose marks are different for any subject.
eg:
First list
Term1, English,90
Term1, Maths, 60
Term1, Physics, 30
Second list
Term2, English, 95
Term2,Maths, 60
Term2, Chemistry, 20
Finally what i want is
English : +5
Physics : +30
Chemistry : -20.
I am using the below query to get difference, but it fails if the key field(subject in this case) values are not same in the two list(eg:-Chemistry present in List2 but not in List1)
var diffData = list1.Union(list2)
.GroupBy(m => m.Subject)
.Select(d=>
{
Subject= d.Key,
Difference = d.OrderBy(m =>m.Term).Select(s => s.Mark).Aggregate(t1, t2) => t2 - t1)
}).Where(m => m.Difference != 0).ToList();
Please help

var diffs = list1.Union(list2)
//Create groups where the key is subject and the value is the
//list of positive marks for Term2 and negative marks for Term1
.GroupBy(c => c.Subject, c => c.Term == "Term2" ? c.Mark : -c.Mark)
.Select(s => new
{
Subject = s.Key,
Difference = s.Sum()
})
.Where(s => s.Difference != 0);
var diffs2 = list1.Union(list2)
.GroupBy(c => c.Subject)
.Select(s =>
{
//For a more general and slighly different algorithm, you can
//subtract all the marks for a each subject except the last term
//mark from the last term mark (e.g. 95 - 90 for English or 30 -
//n/a because there's only one term for Physics
var marks = s.OrderByDescending(c => c.Term).Select(c => c.Mark);
var lastTermMark = marks.First();
return new
{
Subject = s.Key,
Difference = marks.Skip(1)
.Aggregate(lastTermMark, (diff, mark) => diff - mark)
};
})
.Where(s => s.Difference != 0);

Try it this way you simply got your Output
var Outpt = (from a in List1
from b in List2
where a.Subject == b.Subject
Select new {subject = a.subject , marks = a.marks - b.marks , a.Term}).ToList();

Related

Simple LINQ query to count by month

This should be a fairly basic LINQ query but I am confused on how to get this in the format I need.
Here's what I have right now, which I realize isn't even close:
var accidents = DataParser
.Parser
.ParseData()
.Where(w => w.EventDate.Year == year)
.GroupBy(g => g.EventDate.Month)
.Count()
.ToList();
The ToList isn't going to work here due to the Count.
I need the data in the simple format of:
Month Number | EventCount
1 | 45
2 | 62
3 | 42
... etc through month 12, preferably as a List<Events> (the DataParser returns Events objects).
You need to use your group like this:
var accidents = DataParser
.Parser
.ParseData()
.Where(w => w.EventDate.Year == year)
.GroupBy(g => g.EventDate.Month)
.Select(g => new { Month = g.Key, Count = g.Count() }
.ToList();
That will give a list of anonymous objects. If you need list of Events, create Event object instead: .Select(g => new Event() { Month = g.Key, Count = g.Count() }

how to aggregate a linq query by different groupings

How do you perform multiple seperate aggregations on different grouping in linq?
for example, i have a table:
UNO YOS Ranking Score
123456 1 42 17
645123 3 84 20
I want to perform an set of aggregations on this data both grouped and ungrouped, like:
var grouped = table.GroupBy(x => x.score )
.Select(x => new
{
Score = x.Key.ToString(),
OverallAverageRank = x.Average(y => y.Ranking),
Year1RankAvg = x.Where(y => y.YOS == 1).Average(y => y.Ranking),
Year2RankAvg = x.Where(y => y.YOS == 2).Average(y => y.Ranking)
//...etc
});
I also want to perform different aggregations (standard deviation) on the same slices and whole-set data.
I can't figure out how to both group and not group the YOS at the same time and while this compiles fine, when it comes to runtime, I get "Sequence contains no elements", if any of the YOS averages are in.
Like anything programming, when you have a sequence of similar items, use a collection. In this case, I left it IEnumerable, but you could make it a List, or a Dictionary by YOS, if desired.
var ans = table.GroupBy(t => t.Score)
.Select(tg => new {
Score = tg.Key,
OverallAverageRank = tg.Average(t => t.Ranking),
YearRankAvgs = tg.GroupBy(t => t.YOS).Select(tyg => new { YOS = tyg.Key, RankAvg = tyg.Average(t => t.Ranking) })
});
If you need the range of years from 1 to max (or some other number) filled in, you can modify the answer:
var ans2 = ans.Select(soryr => new {
soryr.Score,
soryr.OverallAverageRank,
YearRankDict = soryr.YearRankAvgs.ToDictionary(yr => yr.YOS),
YearMax = soryr.YearRankAvgs.Max(yr => yr.YOS)
})
.Select(soryr => new {
Score = soryr.Score,
OverAverageRank = soryr.OverallAverageRank,
YearRankAvgs = Enumerable.Range(1, soryr.YearMax).Select(yos => soryr.YearRankDict.ContainsKey(yos) ? soryr.YearRankDict[yos] : new { YOS = yos, RankAvg = 0.0 }).ToList()
});
If you preferred, you could modify the original ans to return RankAvg as double? and put null in place of 0.0 when adding missing years.

Filter some unique Data with LINQ and C#

i am very new with C# and MVC.
My Problem:
I have a list OF IDs
int[] mylist = {10, 23}
I try to query some data from DB
var result = db.tableName.Where(o => mylist.Any(y => y == o.item_ID && o.readed)).ToList();
This is what I get with the query:
item_ID Product_ID readed
277 1232 1
277 1233 1
277 1235 1
280 1235 1
What I need is:
item_ID Product_ID readed
277 1235 1
280 1235 1
If I change "any" to "all" i don't get any results, but I have definitely one item where the condition fits.
I think its more like make a query with id 277, then a query with 280 and then merge the list and return only where where "Product_ID" match.
Any ideas?
I assume that what you need is this:
var temp = db.tableName.Where(o => mylist.Any(y => y == o.item_ID && o.readed))
.ToList();
// Find the Product_id which appeared more than one time
// By the way, this assumes that there is at least one product_Id whihc has appeared more than one time
var neededProductID = temp.GroupBy(x => x.Product_ID)
.Where(x => x.Count() > 1)
.First()
.Key;
// Filter the result by neededProductID
var result = temp.Where(x => x.Product_ID == neededProductID).ToList();
Also, if there could be more tha one Product_ID which has apperaed more than one time, then you can consider this:
var neededProductID = temp.GroupBy(x => x.Product_ID)
.Where(x => x.Count() > 1)
.Select(x => x.Key)
.ToList();
var result = temp.Where(x => neededProductID.Any(y => y == x.Product_ID)).ToList();
By the way, you don't need All(). It tells you if all the elements in a collection match a certain condition.
You can use the following
var result = db.tableName.Where(o => mylist.conains(o.item_ID)
&& o.readed).ToList();

Compare two lists and get the different items only in Linq

My object has 3 fields - Student, Term, Subject and Marks. I want to have a list of items whose marks are different for any subject.
eg:
First list is
Alice, Term1, English,90
Alice, Term1, Maths, 60
Alice, Term1, Physics, 30
Second list is
Alice, Term2, English, 95
Alice, Term2,Maths, 60
Alice, Term2, Physics, 20
In the final list I want to get the difference values only
Alice, English +5
Alice, Physics -10
How can i do this using Linq(c#)
There are quite a few ways to do it, actually. One mentioned using a custom comparer. One used a dictionary, though thats not necessary.
// 1) use group by and only select groups with one item in them
var qqq = from r in one.Union(two)
group r by new { r.Term, r.Mark } into g
where g.Count() == 1
select g.First();
// 2) work from a complete list and then remove stuff
var all = one.Union(two).ToList();
// select only the records that have a count of one
var uniq = from a in all
where all.Count(y => y.Term == a.Term && y.Mark == a.Mark) == 1
select a;
// -or- find all the duplicates and the remove them
var dupes = all.Where(x => all.Count(y => y.Term == x.Term && y.Mark == x.Mark) > 1).ToList();
var uniq = all.Except(dupes).ToList();
There are far too many ways to go about this.
Edit
Now that I've had a second to think, it seems there is an easy way to do this all in one step. Not sure the entire requirements, but here it is:
var calcd = from t1 in one
let t2 = two.FirstOrDefault(x => DataMatch(t1, x))
where t2 != null
select new {
Name = t1.Name,
Class = t1.Class,
Change = t2.Mark - t1.Mark
};
public static bool DataMatch(Data x, Data y)
{
return x.Name == y.Name && x.Class == y.Class &&
x.Term != y.Term && x.Mark != y.Mark;
}
this may be your desired (but not tested unfortunately...):
var result = secondList.Union(firstList)
.GroupBy(m => Tuple.Create(m.Student, m.Subject))
.ToDictionary(m => m.Key, n => n.OrderBy(m=>m.Term).Select(s => s.Marks).Aggregate((t1, t2) => t2 - t1))
.Where(m=>m.Value != 0);
var comparer = new SomeClassComparer(); /*must implement IEaualityComparer<SomeClass>*/
var list1 = new List<SomeClass>() { /*...*/ };
var list2 = new List<SomeClass>() { /*...*/};
var result = list1.Except(list2, comparer).Union(list2.Except(list1, comparer));

Multiple group by with aggregate in Linq

I currently have this code:
foreach (var newsToPolitician in news.NewsToPoliticians)
{
var politician = newsToPolitician.Politician;
var votes = (from s in db.Scores
where o.IDPolitician == politician.IDPolitician
&& o.IDNews == IDNews
group o by o.IDAtribute
into g
select new{
Atribute= g.Key,
TotalScore= g.Sum(x => x.Score)
}).ToList();
}
It works alright, but I want to avoid making multiple queries to my database in foreach loop.
My table Scores looks like this:
IDScore | IDNews | IDUser | IDPolitician | IDAtribute | Score
1 40 1010 35 1 1
2 40 1010 35 2 -1
3 40 1002 35 1 1
4 40 1002 35 2 1
5 40 1002 40 1 -1
...
My goal is to aggregate all the scores for all politicians in a news. A news can have up to 7 politicians.
Is it expensive to call my database up to seven times in a foreach loop. I know that isn't best practice so I'm interested is there any way to avoid it in this particular case and make one call to database and then process it on the server side?
Update - Due to user comments have re-jigged to try and ensure aggregation on the server.
In this case we can group on the server by both IDPolitician and IDAttribute and then pull the groups in with ToLookup locally as so:
var result = db.Scores.Where(s => s.IDNews == IDNews)
.Where(s => news.NewsToPoliticians
.Select(n => n.Politician.IDPolitician)
.Contains(s.IDPolitician))
.GroupBy(s => new
{
s.IDPolitician,
s.IDAttribute
},
(k,g ) => new
{
k.IDPolitician,
k.IDAttribute,
Sum = g.Sum(x => x.Score)
})
.ToLookup(anon => anon.IDPolitician,
anon => new { anon.IDAttribute, anon.Sum })
Legacy -
You want to use GroupJoin here, it would be something along the lines of:
var result = news.NewsToPoliticians
.GroupJoin( db.Scores.Where(s= > s.IDNews == IDNews),
p => p.IDPolitician,
s => s.IDPolitician,
(k,g) => new
{
PoliticianId = k,
GroupedVotes = g.GroupBy(s => s.IDAtribute,
(id, group) => new
{
Atribute = id,
TotalScore = group.Sum(x => x.Score)
})
})
.ToList();
However you are at the mercy of your provider as to how it translates this so it might still be multiple queries to get round this you could use something like:
var politicianIds = news.NewsToPoliticians.Select(p => p.IDPolitician).ToList()
var result = db.Scores.Where(s= > s.IDNews == IDNews)
.Where(s => politicianIds.Contains(s.IDPolitician))
.GroupBy(p => p.IDPolitician,
(k,g) => new
{
PoliticianId = k,
GroupedVotes = g.GroupBy(s => s.IDAtribute,
(id, group) => new
{
Atribute = id,
TotalScore = group.Sum(x => x.Score)
})
})
.ToList();
Which hopefully should be at most 2 query (depending on whether NewsToPoliticians is db dependent). You'll just have to try it out and see.
Use a stored procedure and get the SQL server engine to do all the work. You can still use Linq to call the stored procedure and this will minimize all the calls to the database

Categories