Get averages for each day of week with LINQ - c#

I'm new to C# and This is the first time I'm using LINQ queries. I have a Dictionary as Dictionary<(int MeterId, DateTime TimeStamp), float> With data as follows,
<(10411, 12/1/2022 12:30:00 AM), 5700>
<(10411, 13/1/2022 12:30:00 AM), 5200>
<(10412, 12/1/2022 12:30:00 AM), 200>
Then there's a method to add them to a list with meterId and average value for each day of week. I'm using LINQ queries to calculate the averages and get them to a list.
public static List<AveragedMeter> CalculateAverageValues(Dictionary<(int MeterId, DateTime TimeStamp), float> groupedMeterValues)
{
var lstResult = groupedMeterValues
.GroupBy(m => new { m.Key.MeterId, m.Key.TimeStamp.DayOfWeek })
.Select(g => new AveragedMeter
{
MeterId = g.Key.MeterId,
AverageMondayValue,
AverageTuesdayValue,
AverageWednesdayValue,
AverageThursdayValue,
AverageFridayValue,
AverageSaturdayValue,
AverageSundayValue
})
.ToList();
return lstResult;
}
I'm not certain how to get for these average values. My AveragedMeter class is as follows,
class AveragedMeter
{
public int MeterId { get; set; }
public float AverageMondayValue { get; set; }
public float AverageTuesdayValue { get; set; }
public float AverageWednesdayValue { get; set; }
public float AverageThursdayValue { get; set; }
public float AverageFridayValue { get; set; }
public float AverageSaturdayValue { get; set; }
public float AverageSundayValue { get; set; }
}

You could filter the data by DayOfWeek inside your select instead of grouping the dictionary, something like this:
static List<AveragedMeter> CalculateAverageValues(Dictionary<(int MeterId, DateTime TimeStamp), float> groupedMeterValues)
{
var lstResult = groupedMeterValues
.GroupBy(m => m.Key.MeterId)
.Select(g => new AveragedMeter
{
MeterId = g.Key,
AverageMondayValue = g.Where(n => n.Key.TimeStamp.DayOfWeek == DayOfWeek.Monday).Average(n => n.Value),
AverageTuesdayValue = g.Where(n => n.Key.TimeStamp.DayOfWeek == DayOfWeek.Tuesday).Average(n => n.Value),
AverageWednesdayValue = g.Where(n => n.Key.TimeStamp.DayOfWeek == DayOfWeek.Wednesday).Average(n => n.Value),
AverageThursdayValue = g.Where(n => n.Key.TimeStamp.DayOfWeek == DayOfWeek.Thursday).Average(n => n.Value),
AverageFridayValue = g.Where(n => n.Key.TimeStamp.DayOfWeek == DayOfWeek.Friday).Average(n => n.Value),
AverageSaturdayValue = g.Where(n => n.Key.TimeStamp.DayOfWeek == DayOfWeek.Saturday).Average(n => n.Value),
AverageSundayValue = g.Where(n => n.Key.TimeStamp.DayOfWeek == DayOfWeek.Sunday).Average(n => n.Value),
})
.ToList();
return lstResult;
}

The accepted the answer is not performance optimal because it iterates all values 7 times (excluding the GroupBy()). I propose a solution the iterates the values only twice:
static List<AveragedMeter> CalculateAverageValues(
Dictionary<(int MeterId, DateTime TimeStamp), float> groupedMeterValues)
=> groupedMeterValues
.GroupBy(g => g.Key.MeterId)
.Select(g => CalculateAverageDays(
g.Key,
g.ToLookup(x => x.Key.TimeStamp.DayOfWeek, x => x.Value)))
.ToList();
static AveragedMeter CalculateAverageDays(
int meterId,
ILookup<DayOfWeek, float> valuesPerDay)
=> new AveragedMeter {
MeterId = meterId,
AverageMondayValue = valuesPerDay[DayOfWeek.Monday].DefaultIfEmpty().Average(),
AverageTuesdayValue = valuesPerDay[DayOfWeek.Tuesday].DefaultIfEmpty().Average(),
AverageWednesdayValue = valuesPerDay[DayOfWeek.Wednesday].DefaultIfEmpty().Average(),
AverageThursdayValue = valuesPerDay[DayOfWeek.Thursday].DefaultIfEmpty().Average(),
AverageFridayValue = valuesPerDay[DayOfWeek.Friday].DefaultIfEmpty().Average(),
AverageSaturdayValue = valuesPerDay[DayOfWeek.Saturday].DefaultIfEmpty().Average(),
AverageSundayValue = valuesPerDay[DayOfWeek.Sunday].DefaultIfEmpty().Average()
};

Related

C# & ASP.NET Web API: return Ok(returnData); statement is slow

For an ASP.NET Web API using Linq, I am doing a bunch of operations that complete relatively quickly when I step through them using the debugger once the endpoint is called through Swagger.
However, once I arrive at the return statement, it takes ages to complete - several seconds up to minutes.
I have no idea why this would be happening - I imagine it is some kind of slow serialization from the classes to the JSON fields in the HTTP response?
Here is the relevant code:
View models:
public class RetriesAtGoal
{
public string goal { get; set; }
public int count { get; set; }
public string retries { get; set; }
public int total_retries { get; set; }
}
public class SuccessfulApproaches
{
public string goal { get; set; }
public int count { get; set; }
}
public class ApproachesReturnDataViewModel
{
public IEnumerable<RetriesAtGoal> retriesAtGoal { get; set; }
public IEnumerable<SuccessfulApproaches> successfulApproaches { get; set; }
}
Code in API:
var returnData = new ApproachesReturnDataViewModel { };
var totalRetries = db.message
.Select(x => new
{
type = x.type,
agvId = x.agvId,
createdTimestamp = x.createdTimestamp,
source = x.source,
details = x.details
})
.Where(x => x.type == "ErrorHandling" &&
robotList.Contains(x.agvId) &&
x.createdTimestamp >= start &&
x.createdTimestamp < end)
.ToList();
// <1 second
returnData.retriesAtGoal = totalRetries
.GroupBy(x => new { x.source, x.details })
.Select((x, y) => new RetriesAtGoal
{
goal = x.FirstOrDefault().source,
count = x.Count(),
retries = x.FirstOrDefault().details,
total_retries = x.Count() * (int.Parse(x.FirstOrDefault().details) == 0 ? 1 : int.Parse(x.FirstOrDefault().details + 1))
});
// 4 seconds
var totalSuccessful = db.job
.Include(x => x.jobGoals)
.Select(x => new
{
createdTimestamp = x.createdTimestamp,
assignedAgvId = x.assignedAgvId,
jobGoals = x.jobGoals,
id = x.id
})
.Where(x => x.createdTimestamp >= start &&
x.createdTimestamp < end &&
robotList.Contains(x.assignedAgvId) &&
x.jobGoals.Count() == 2)
.ToList();
// <1 second
returnData.successfulApproaches = totalSuccessful
.SelectMany(x => new[] { new { goal = x.jobGoals.First().goalId, job_id = x.id },
new { goal = x.jobGoals.Last().goalId, job_id = x.id } })
.Where(x => !messageCompare.Any(e => e.affectedJobId == x.job_id && e.source == x.goal))
.GroupBy(x => x.goal)
.Select(x => new SuccessfulApproaches { goal = x.Key, count = x.Count() });
// Apparently takes very, very long!
return Ok(returnData);

Order by one element and return multi properties in the same LINQ query

I have this list
List<Order> OL = new List<Order>()
{
new Order("O-1","P1",200,2),
new Order("O-2","P1",200,3),
new Order("O-3","P1",1000,1),
new Order("O-4","P2",200,2)
};
The Order class :
class Order
{
public string ID { get; set; }
public string Product { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
public int Total { get { return Price * Quantity; } }
public Order(string _ID, string _Product, int _Price, int _Quantity)
{
ID = _ID;
Product = _Product;
Price = _Price;
Quantity = _Quantity;
}
public Order()
{
}
}
So I want to return the name and the counting (Number of times the product repeated in orders) for each product.
I tried :
var P = OL.OrderByDescending(x => x.Product.Count()).Take(2);
MessageBox.Show(P.ElementAt(0).Product);
But just getting the product name, Please any help? and thanks in advance.
How about:
var groupedProducts = OL.GroupBy(o => o.Product)
.Select(g => new { Product = g.Key, Quantity = g.Count() })
.OrderByDescending(p => p.Quantity);
Group by Product then sort by Count()
var P = OL.GroupBy(x => x.Product)
.OrderByDescending(x => x.Count())
.Select(g => new { Product = g.Key, Count = g.Count() });

How to add iOrderedQueryable results to a list?

I'm trying to add my database results to list. Here's my class:
class MyClass
{
public string Tick { get; set; }
public string Exchange { get; set; }
public double Price { get; set; }
public double Volume { get; set; }
}
Here's my routine:
var myList = new List<MyClass>();
foreach (var element in compsList)
{
myList.Add(Price_tbl
.Where(p => p.Ticker.Equals(element.Tick) && p.Tick_datetime <= DateTime.Today)
.GroupBy(p => new { Comp = p.Ticker })
.Select(p => new MyClass {
Tick = p.Key.Comp,
Exchange = element.Exchange,
Price = p.OrderByDescending(c => c.Tick_datetime).Select(c => c.Close_price).FirstOrDefault(),
Volume = (p.Sum(c => c.Volume) / 52),
}));
}
But I'm getting the error:
cannot convert from 'System.Linq.IQueryable<UserQuery.MyClass>' to 'UserQuery.MyClass'
Is there another way to add the results to a list? (I've been racking my brain on this for a few hours now.)

GroupBy in lambda expressions

from x in myCollection
group x by x.Id into y
select new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
};
How would you write the above as a lambda expression? I'm stuck on the group into part.
Query continuations (select...into and group...into, but not join...into) are equivalent to just splitting up the query expression. So I like to think of your example as:
var tmp = from x in myCollection
group x by x.Id;
var result = from y in tmp
select new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
};
Change those into dot notation:
var tmp = myCollection.GroupBy(x => x.Id);
var result = tmp.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
Then you could combine them back:
var tmp = myCollection.GroupBy(x => x.Id)
.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
Once you work out what the C# compiler does with query expressions, the rest is relatively straightforward :)
myCollection
.GroupBy(x => x.Id)
.Select(x =>
new
{
Id = x.Key,
Quantity = x.Sum(y => x.Quantity
});
myCollection.GroupBy(x => x.Id)
.Select(y => new {
Id = y.Key,
Quantity = y.Sum(x => x.Quantity)
});
var mostFrequent =
lstIn.Where(i => !string.IsNullOrEmpty(i))
.GroupBy(s => s)
.OrderByDescending(g => g.Count())
.Select(s => s.Key)
.FirstOrDefault();
So, for most of the answers here, everyone seems to be dealing with getting a simple object of Id made from count of the group, and the Key itself which is group.Key.
Although thats probably the main useage of this. Didn't really satisfy my needs.
For my own case, I basically wanted to group by some object property, then fetch a specific object from that group. Here's a sample code.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var response = new List<ResponseClass>();
var listOfStudents = new List<Student>();
// Insert some objects into listOfStudents object.
listOfStudents.GroupBy(g => g.Class).ToList()
.ForEach(g => response.Add(g.OrderByDescending(s => s.CreatedOn).Select(a =>
new ResponseClass
{
SName = a.StudentName,
SAge = a.Age,
SClass = a.Class,
SCreatedOn = a.CreatedOn,
RandomProperty = Guid.NewGuid().ToString()
})
.First()));
Console.WriteLine("This compiles and should work just fine");
}
class Student
{
public string StudentName { get; set; }
public int Age { get; set; }
public string Class { get; set; }
public DateTime CreatedOn { get; set; }
}
class ResponseClass
{
public string SName { get; set; }
public int SAge { get; set; }
public string SClass { get; set; }
public DateTime SCreatedOn { get; set; }
public string RandomProperty { get; set; }
}
}
If you would rather use a foreach loop (I prefer lambda as I find it easier to read), but if you do, you could do it like so.
foreach (IGrouping<string, Student> groupedStudents in listOfStudents.GroupBy(g => g.Class))
{
response.Add(groupedStudents.OrderByDescending(x => x.CreatedOn).Select(a =>
new ResponseClass
{
SName = a.StudentName,
SAge = a.Age,
SClass = a.Class,
SCreatedOn = a.CreatedOn,
RandomProperty = Guid.NewGuid().ToString()
}).First());
}
Hope this helps someone. :)

How to break an object into chunks based on some property?

public class InvestorMailing
{
public string To { get; set; }
public IEnumerable<string> Attachments { get; set; }
public int AttachmentCount { get; set; }
public long AttachmentSize { get; set; }
}
i have an IList<InvestorMailing> mailingList. if the attachment size is greater than x, then i need to split my object into chunks. is there an easy linq-y way to do this?
Edited:
this is how i'm generating my mailings:
var groupedMailings = mailingList.GroupBy(g => g.GroupBy);
var investorMailings = groupedMailings.Select(
g => new DistinctInvestorMailing
{
Id = g.Select(x => x.Id).FirstOrDefault(),
To = g.Key.Trim(),
From = g.Select(x => x.From).FirstOrDefault(),
FromName = g.Select(x => x.FromName).FirstOrDefault(),
Bcc = g.Select(x => x.Bcc).FirstOrDefault(),
DeliveryCode = g.Select(x => x.DeliveryCode).FirstOrDefault(),
Subject = g.Select(x => x.Subject).FirstOrDefault(),
Body = g.Select(x => x.Body).FirstOrDefault(),
CommentsOnStatus = g.Select(x => x.CommentsOnStatus).FirstOrDefault(),
Attachments = g.Select(x => x.AttachmentPath),
AttachmentCount = g.Select(x => x.AttachmentPath).Count(),
AttachmentSize = g.Sum(x => x.AttachmentSize),
MailType = g.Select(x => x.MessageType).FirstOrDefault()
}
).ToList();
It should be pretty simple to do it with a standard method. Consider this example:
class Foo
{
public Foo(int weight) { Weight = weight; }
public int Weight { get; set; }
}
...
IEnumerable<IList<Foo>> GroupFoosByWeight(IList<Foo> foos, int weightLimit)
{
List<Foo> list = new List<Foo>();
int sumOfWeight = 0;
foreach (Foo foo in foos)
{
if (sumOfWeight + foo.Weight > weightLimit)
{
yield return list;
sumOfWeight = 0;
list.Clear();
}
list.Add(foo);
sumOfWeight += foo.Weight;
}
if (list.Count > 0)
yield return list;
}
...
List<Foo> foos = new List<Foo>()
{
new Foo(15), new Foo(32), new Foo(14), new Foo(19), new Foo(27)
};
foreach (IList<Foo> list in GroupFoosByWeight(foos, 35))
{
Console.WriteLine("{0}\t{1}", list.Count, list.Sum(f => f.Weight));
}
Edit
I worked on it a bit and produced a LINQ version. It doesn't really save much code in this case, but it's a start.
int weightLimit = 35;
int fooGroup = 0;
int totalWeight = 0;
Func<Foo, int> groupIncrementer = f =>
{
if (totalWeight + f.Weight > weightLimit)
{
fooGroup++;
totalWeight = 0;
}
totalWeight += f.Weight;
return fooGroup;
};
var query = from foo in foos
group foo by new { Group = groupIncrementer(foo) }
into g
select g.AsEnumerable();
foreach (IList<Foo> list in query)
{
Console.WriteLine("{0}\t{1}", list.Count, list.Sum(f => f.Weight));
}
Here's a way to do it using some LINQ to find a chunk that has enough space left to add the attachment:
var chunks = new List<List<InvestorMailing>>();
int maxAttachmentsSize = 10;
foreach (InvestorMailing mail in mailingList)
{
var chunkWithSpace = chunks
.Where(list => list.Sum(x => x.AttachmentSize) +
mail.AttachmentSize <= maxAttachmentsSize)
.FirstOrDefault();
if (chunkWithSpace != null)
{
chunkWithSpace.Add(mail);
} else {
chunks.Add(new List<InvestorMailing> { mail });
}
}
The result is stored in chunks.
Yes:
var highs = mailingList.Where(i => i.AttachmentSize > 10000).ToList();
var lows = mailingList.Where(i => i.AttachmentSize <= 10000).ToList();
How do you need to break them apart aside from this?
HTH.

Categories