GROUPBY and SUM on List items using LINQ - c#

I have a List of type DailySummary
public class DailySummary
{
public string AffiliateID { get; set; }
public string TotalCalls { get; set; }
public string Date{ get; set; }
}
with following sample data:
List<DailySummary> DealerTFNDatesTable = new List<DailySummary>();
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "10"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/13/2016", TotalCalls = "74"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="1", Date = "12/22/2016", TotalCalls = "63"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "58"});
Now I want to retrieve Date and TotalCalls grouped by AffiliateID and assign in another list.
for(int i =0; i < DealerTFNDatesTable.Count; i++)
{
List<NewList> newList = new List<NewList>();
newList.Date = //Assign Dintinct dates WHERE AffiliateId = 0
newList.AffiliateID = //AffiliateID=0
newList.TotalCalls= //TotalCalls SUM GROUPBY DATE and AffiliateID = 0
//For Date '12/12/2016' it will be 68, For '12/13/2016' it will be 74 and so on
}
I'm sorry, I'm new to LINQ. Can someone help me or share resources where I can get a hint to achieve this?

This should work for grouping by AffilateID and Date and then getting the sum (though it's weird to store a number as a string for something like this, but whatever floats your boat).
var results = DealerTFNDatesTable
.GroupBy(x => new { x.AffiliateID, x.Date })
.Select(x => new DailySummary {
AffiliateID = x.First().AffiliateID,
Date = x.First().Date,
TotalCalls = x.Sum(y => Convert.ToInt32(y.TotalCalls)).ToString()
});
If you now look at the result, for example with this code, you get exactly the values you wanted:
foreach (var x in results) {
Console.WriteLine($"id = {x.AffiliateID}, date = {x.Date}, totalCalls = {x.TotalCalls}");
}
> id = 0, date = 12/12/2016, totalCalls = 68
> id = 0, date = 12/13/2016, totalCalls = 74
> id = 1, date = 12/22/2016, totalCalls = 63

First off,
Since DealerTFNDatesTable is a variable, you should use camel case. Thus it is dealerTFNDatesTable
Then to complete #andy his answer, as you also want to do a select. You can select it as follows:
var newVariable = from item in dealerTFNDatesTable
group item by new
{
item.Date,
item.AffiliateID,
}
into g
select new
{
Date = g.Key.Date,
Id = g.Key.AffiliateID,
Total = g.Sum(a => a.TotalCalls)
};
This will give you an IEnumerable, of which you can put the relevant parts in a list by doing var otherList = new List<object>(newVariable
.Where(a => a.Total > 0)); or simply add .ToList() after the select if you want the collection as-is.
Note that this is simply another notation than LINQ, the result is the same.

var results = DealerTFNDatesTable.GroupBy(T => new { T.AffiliateID })
Link

Related

Except on Lists in c#

I count months and years from a given date to the present date
and from this list I have to subtract the months that were returned to me in the sql (linq) query.
I try to use "Except" on the results, but gives me an error in the picture below
var list = _ecpContext.Akceptacje_UnionAll_V
.Where(f => f.ADLogin == user)
.Select(f => new
{
Miesiac= f.Miesiac, //month
Rok= f.Rok // year
})
.ToList();
//-------------------------------------------------------------------
DateTime employmentDate = _ecpContext.Ustawienia.FirstOrDefault(x => x.UstLogin == user).EmploymentDate;
int employmentYear = employmentDate.Year;
int employmentMonth = employmentDate.Month;
DateTime now = DateTime.Now;
int currentYear = now.Year;
int currentMonth = now.Month;
var newList = Array.Empty<object>().Select(x => new { Month = 1, Year = 1 }).ToList();
for (var i = employmentYear; i <= currentYear; i++)
{
for (var x = employmentMonth; x <= currentMonth; x++)
{
newList.Add(new { Month = x, Year = i });
}
}
//-------------------------------------------------------------------
// i try
IEnumerable<DatesOfShortages> listMissingDates = list.Except(newList);
public class DatesOfShortages
{
public int Year { get; set; }
public int Month { get; set; }
}
new error
The Except method is a method which produces the set difference of two sequences so you need to invoke it.
IEnumerable<DatesOfShortages> listMissingDates = newList.Except(list);
You can't have one list A full of anonymous types, and another list B full of Tuples, and run a.Except(b) on them
Make a list of anonymous types instead of tuples:
var newList = Array.Empty<object>().Select(x => new { Month = 1, Year = 1 }).ToList();
for (var i = employmentYear; i <= currentYear; i++)
{
for (var x = employmentMonth; x <= currentMonth; x++)
{
newList.Add(new{ Month = x, Year = i});
}
}
For newList I suppose something like new [] { list.ElementAtOrDefault(-1) }.ToList(); would work too.. Whatever trick you feel like pulling to get a list of ATs!

Get first item of IGrouping

IEnumerable<IGrouping<long, MyClass>> datas = list.GroupBy(x => x.PropertyXYOfMyClass);
// get all items from each group
foreach (var grouping in datas)
{
long groupKey = groupingByMyClass.Key;
//iterating through values
foreach (var item in groupingByMyClass)
{
long key = item.PropertyIntOfClassA;
string property = item.PropertyA;
}
}
Each group contains some items, wow to get values from first item of each group?
UPDATE
void Extract()
{
List<DataHolder> data = new List<DataHolder>();
List<DateTime> randomTimes = new List<DateTime>();
Random r = new Random();
DateTime d = new DateTime(2019, 9, 19, 7, 0, 0);
for (int i = 0; i < 100; i++)
{
DataHolder dh = new DataHolder();
TimeSpan t = TimeSpan.FromSeconds(r.Next(0, 14400));
dh.OID = i;
dh.Value = r.Next(50);
dh.Snapshottime = d.Add(t);
data.Add(dh);
}
data.OrderBy(o => o.Snapshottime).ToList();
List<DataHolder> SortedList = data.OrderBy(o => o.Snapshottime).ToList();
TimeSpan interval = new TimeSpan(0, 15, 0);
var result = SortedList.GroupBy(x => x.Snapshottime.Ticks / interval.Ticks) .OrderBy(x => x.Key);
}
public class DataHolder
{
public int OID { get; set; }
public double Value { get; set; }
public DateTime Snapshottime { get; set; }
}
Here from result i need to take first item from each group.
try this:
var finalResult = result.Select(gpr=>grp.First());
or if you want the earliest/Latest/etc you could order by first:
var finalResult = result.Select(gpr=>grp.OrderBy(x=>x.SnapShotTime).First());
You've already done the heavy lifting. Make a simple loop over the result:
var result = SortedList.GroupBy(x => x.Snapshottime.Ticks / interval.Ticks) .OrderBy(x => x.Key);
var resultList = new List<DataHolder>();
foreach(var group in result)
{
resultList.Add(group.First());
}
I hope this helps.

c# linq add a column to a new list from another single column list

My project is MVC5. I have a table with multiple rows for the same day, I need to get the total of this entry for each day, I use the following:
var days = db.Nutrition.Where(x => x.Day >= fromDate
&& x.Day <= toDate).DistinctBy(x => x.Day).AsEnumerable().ToList();
List<double?> calories = new List<double?>();
foreach (var item in days)
{
calories.Add(days.Where(c => c.Day==item.Day).Select(x => x.Calories).Sum());
}
I get a list containing the totals. Now I need to make a new list that has two columns.
I made the following model:
public class Consumption
{
public virtual double? Calories { get; set; }
public virtual string Name { get; set; }
}
I tried to use the following to generate the new list:
List<Consumption> newList = new List<Consumption>();
var name = new Consumption { Name = "name" };
foreach (var item in calories)
{
newList.Add(name, Calories = (double)item.Value);
}
I get the following error:
The name 'Calories' does not exist in the current context
Edit
Thanks to Stephen's comment:
I just used one line to achieve same result
var results = db.Nutrition.Where(x => x.Day >= fromDate && x.Day <= toDate).GroupBy(l => l.Day)
.Select(cl => new { Name = "name", Calories = cl.Sum(c => c.Calories)}).ToList();
Try with:
List<Consumption> newList = new List<Consumption>();
var name = new Consumption { Name = "name" };
foreach (var item in calories)
{
var cal = new Consumption{ Name = "name", Calories = (double)item.Value });
newList.Add(cal);
}
You received this compiler error
The name 'Calories' does not exist in the current context
because the List<Consumption>.Add(Comsumption item) method on your variable newList only accepts one argument of type Consumption.
Regarding your intentions, and the discussion in your comments with #StephenMuecke, it became clear that your intention is to Sum a property double Calories, and GroupBy by property DateTime Day and then project that into a List<Consumption>.
var dateTimeFormat = "yyyy-dd-MM";
var results = db.Nutrition.Where(x => x.Day >= fromDate && x.Day <= toDate)
.GroupBy(x => x.Day)
.Select(groupedX => new Consumption
{
Name = groupedX.Key.ToString(dateTimeFormat),
Calories = groupedX.Sum(y => y.Calories)
}).ToList();

How to group items in a list with the same max date and filter them

I have List<TakenBMI> with these 4 columns and data:
TakenDate UerID TakenItem TakenValue
Aug-10-2014 34 Weight 140
Aug-10-2014 34 Height 5.5
Mar-15-2015 34 Weight 141
Mar-15-2015 34 Height 5.5
I want to group them in separate lists based on the TakenDate and find out which list I should use that has details with the latest taken date.
Here is what I have tried:
var q = from n in TakenBMI
group n by n.TakenDate into g
select g.OrderByDescending(t=>t.TakenDate )
.FirstOrDefault();
var m = from n in TakenBMI
group n by n.TakenDate into g
select new { TakenDate = Convert.ToDateTime(q) };
Also, if anyone can suggest after getting the list with the max date one, how do I get to the 2nd latest date ones if possible? Thank you to everyone who replied
I'd approach by finding the latest date:
var latestDate = items.Select(i => i.TakenDate).Max();
Then by filtering items with that date:
var itemsWithLatestDate = items.Where(i => i.TakenDate == latestDate).ToList();
If you require all of them grouped in date order, then:
var itemsByDate = items.GroupBy(i => i.TakenDate).OrderBy(g => g.Key);
The latest date group would be the last of those groups:
var itemsWithLatestDate = itemsByDate.Last();
I usually use a dictionary
class Program
{
static void Main(string[] args)
{
List<TakenBMI> data = new List<TakenBMI>() {
new TakenBMI() {TakenDate = DateTime.Parse("Aug-10-2014"), UerID = 34, TakenItem = "Weight", TakenValue = 140},
new TakenBMI() {TakenDate = DateTime.Parse("Aug-10-2014"), UerID = 34, TakenItem = "Height", TakenValue = 5.5},
new TakenBMI() {TakenDate = DateTime.Parse("Aug-15-2014"), UerID = 34, TakenItem = "Weight", TakenValue = 141},
new TakenBMI() {TakenDate = DateTime.Parse("Aug-15-2014"), UerID = 34, TakenItem = "Height", TakenValue = 5.5},
};
Dictionary<DateTime, List<TakenBMI>> dict = data.AsEnumerable()
.GroupBy(x => x.TakenDate, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
}
}
public class TakenBMI
{
public DateTime TakenDate {get;set;}
public int UerID {get;set;}
public string TakenItem {get;set;}
public double TakenValue {get;set;}
}
}
I want to group them in separate lists based on the TakenDate
You can group by TakenDate using GroupBy() LINQ query operator and enumerate over the result as follows:
var groups = TakenBMIList.GroupBy(x => x.TakenDate);
foreach (var group in groups)
{
Console.WriteLine("TakenDate: {0}", group.Key);
List<TakenBMI> list = group.ToList(); //List of TakenBMI with current TakenDate
}
find out which list I should use that has details with the latest
taken date
You can find elements with latest (max) TakenDate as follows:
var latestTakenBMIs = TakenBMIList.Where(x => x.TakenDate == TakenBMIList.Max(y => y.TakenDate)).ToList();

linq group by contiguous blocks

Let's say I have following data:
Time Status
10:00 On
11:00 Off
12:00 Off
13:00 Off
14:00 Off
15:00 On
16:00 On
How could I group that using Linq into something like
[On, [10:00]], [Off, [11:00, 12:00, 13:00, 14:00]], [On, [15:00, 16:00]]
Create a GroupAdjacent extension, such as the one listed here.
And then it's as simple as:
var groups = myData.GroupAdjacent(data => data.OnOffStatus);
You could also do this with one Linq query using a variable to keep track of the changes, like this.
int key = 0;
var query = data.Select(
(n,i) => i == 0 ?
new { Value = n, Key = key } :
new
{
Value = n,
Key = n.OnOffFlag == data[i - 1].OnOffFlag ? key : ++key
})
.GroupBy(a => a.Key, a => a.Value);
Basically it assigns a key for each item that increments when the current item does not equal the previous item. Of course this assumes that your data is in a List or Array, otherwise you'd have to try a different method
Here is a hardcore LINQ solution by using Enumerable.Zip to compare contiguous elements and generate a contiguous key:
var adj = 0;
var t = data.Zip(data.Skip(1).Concat(new TimeStatus[] { null }),
(x, y) => new { x, key = (x == null || y == null || x.Status == y.Status) ? adj : adj++ }
).GroupBy(i => i.key, (k, g) => g.Select(e => e.x));
It can be done as.
Iterate over collection.
Use TakeWhile<Predicate> condition is text of first element of collection On or Off.
Iterate over the subset of from point one and repeat above step and concatenate string.
Hope it helps..
You could parse the list and assign a contiguous key e.g define a class:
public class TimeStatus
{
public int ContiguousKey { get; set; }
public string Time { get; set; }
public string Status { get; set; }
}
You would assign values to the contiguous key by looping through, maintaining a count and detecting when the status changes from On to Off and so forth which would give you a list like this:
List<TimeStatus> timeStatuses = new List<TimeStatus>
{
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "10:00"},
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "11:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "12:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "13:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "14:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "15:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "16:00"}
};
Then using the following query you can extract the Status and grouped Times:
var query = timeStatuses.GroupBy(t => t.ContiguousKey)
.Select(g => new { Status = g.First().Status, Times = g });

Categories