I have a calendar of events. I use the following LINQ statement to load the events to display in a calendar:
var events = CalendarItems?.GroupBy(e => e.StartDate);
It results in a GroupedEnumerable with key/values like this:
Key = 6/26/2019, Value = CalendarItem
Key = 7/10/2019, Value = CalendarItem
Key = 7/18/2019, Value = CalendarItem
Some CalendarItems have "EndDate" and if it does, I would like each date in the range of StartDate to EndDate to also be included in the result set. So if the 7/18/2019 CalendarItem had an EndDate of 7/20/2019, then my result set would also include these 2 items:
Key = 7/19/2019, Value = CalendarItem
Key = 7/20/2019, Value = CalendarItem
I have no idea how I would modify my LINQ statement. Any recommendations?
Thanks!
So, if I had these 2 CalendarItems:
StartDate = 7/8/2019, EndDate = null
StartDate = 7/18/2019, EndDate = 7/20/2019
Then my result should be:
Key = 7/8/2019, Value = CalendarItem
Key = 7/18/2019, Value = CalendarItem
Key = 7/19/2019, Value = CalendarItem
Key = 7/20/2019, Value = CalendarItem
Try this:
var events = CalendarItems?.Where(x => x.EndDate.HasValue && x.StartDate < x.EndDate).GroupBy(e => e.StartDate);
(For the first check you could also do x.EndDate != null)
The biggest thing to note is just the .Where() clause. You could perform this when looping through the groups as well. So if you need to do the checks after the grouping, you could use this alternatively:
var events = CalendarItems?.GroupBy(e => e.StartDate);
foreach(var event in events)
{
var validEvent = event.Where(x => x.EndDate.HasValue && x.StartDate < x.EndDate);
}
Functionally they're the same, but if for whatever reason you need to process the other dates, that foreach() will let you pick through what you need on a group by group basis.
class Item
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
var items = new List<Item>
{
new Item {StartDate = new DateTime(2019, 2, 22)},
new Item {StartDate = new DateTime(2019, 3, 12 )},
new Item {StartDate = new DateTime(2019, 4, 15), EndDate = new DateTime(2019, 4, 25) },
new Item {StartDate = new DateTime(2019, 2, 15), EndDate = new DateTime(2019, 2, 17) },
new Item {StartDate = new DateTime(2019, 2, 23)},
new Item {StartDate = new DateTime(2019, 2, 7)},
};
var x = items.Select(i => (i.StartDate, diff: Enumerable.Range(0, ((i.EndDate == null ? i.StartDate : i.EndDate).Value - i.StartDate).Days + 1)))
.Select(x => x.diff.Select(z => x.StartDate.AddDays(z)));
var days = new List<DateTime>();
x.ToList().ForEach(dates => days.AddRange(dates));
// Output:
days.OrderBy(i => i).ToList().ForEach(d => Console.WriteLine(d.ToShortDateString()));
/*
Output:
07.02.2019
15.02.2019
16.02.2019
17.02.2019
22.02.2019
23.02.2019
12.03.2019
15.04.2019
16.04.2019
17.04.2019
18.04.2019
19.04.2019
20.04.2019
21.04.2019
22.04.2019
23.04.2019
24.04.2019
25.04.2019
*/
Related
I want to optimize this query and reduce the number of loops if possible. Atleast the one where i have to select all the client id first for iteration purpose.
any help appreciated.
public DataTable convertCollectionExpectedToDatatable(List<Invoice> lst)
{
DataTable dtcollection = new DataTable();
try
{
dtcollection.Columns.Add("ClientId", typeof(string));
dtcollection.Columns.Add("customerName", typeof(string));
dtcollection.Columns.Add("BalAmnt1", typeof(string));
dtcollection.Columns.Add("BalAmnt2", typeof(string));
dtcollection.Columns.Add("BalAmnt3", typeof(string));
dtcollection.Columns.Add("totalAmt", typeof(string));
DateTime promiseDate1 = DateTime.Today;
DateTime promiseDate2 = promiseDate1.AddDays(1);
DateTime promiseDate3 = promiseDate2.AddDays(1);
var select = (from l in lst select l.ClientId).Distinct();
List<long> lstInv = select.ToList<long>();
DataRow dr;
foreach (long inv in lstInv)
{
decimal BalAmnt1 = lst.Where(Invoice => Invoice.ExpDt ==
promiseDate1 && Invoice.ClientId == inv).Select(Invoice => Invoice.BalAmnt).Sum();
decimal BalAmnt2 = lst.Where(Invoice => Invoice.ExpDt ==
promiseDate2 && Invoice.ClientId == inv).Select(Invoice => Invoice.BalAmnt).Sum();
decimal BalAmnt3 = lst.Where(Invoice => Invoice.ExpDt ==
promiseDate3 && Invoice.ClientId == inv).Select(Invoice => Invoice.BalAmnt).Sum();
var clientName = (from l in lst where l.ClientId == inv select
l.Client.Name).FirstOrDefault();
dr = dtcollection.NewRow();
dr["ClientId"] = inv.ToString();
dr["customerName"] = clientName.ToString();
dr["BalAmnt1"] = string.Format("{0:n2}", BalAmnt1);
dr["BalAmnt2"] = string.Format("{0:n2}", BalAmnt2);
dr["BalAmnt3"] = string.Format("{0:n2}", BalAmnt3);
dr["totalAmt"] = string.Format("{0:n2}", BalAmnt1 + BalAmnt2 +
BalAmnt3);
dtcollection.Rows.Add(dr);
}
}
You can group invoices by ClientId and calculate all sums with this query:
from invoice in lst
group invoice by invoice.ClientId into g
select new {
BalAmnt1 = g.Where(i => i.ExpDt == promiseDate1).Sum(i => i.BalAmnt),
BalAmnt2 = g.Where(i => i.ExpDt == promiseDate2).Sum(i => i.BalAmnt),
BalAmnt3 = g.Where(i => i.ExpDt == promiseDate3).Sum(i => i.BalAmnt)
}
Also consider better naming. What name lst will tell to people who will read your code. If this is a list of invoices, then name it invoices. Same with promiseDates - today and tomorrow will better describe what dates stored in variables:
DateTime today = DateTime.Today;
DateTime tomorrow = today.AddDays(1);
// don't know how to name dayAfterTomorrow
// suppose it has some business-specific name in your case
Also you can extract duplicated code into separate method:
public static decimal CalculateBalanceOn(
this IEnumerable<Invoice> invoices, DateTime date)
{
return invoices.Where(i => i.ExpDt == date).Sum(i => i.BalAmnt);
}
With all above query will look like:
from invoice in invoices
group invoice by invoice.ClientId into g
select new {
BalanceToday = g.CalculateBalanceOn(today),
BalanceTomorrow = g.CalculateBalanceOn(tomorrow),
BalanceAfterTomorrow = g.CalculateBalanceOn(dayAfterTomorrow)
}
I have this linq statement that returns result and i wanted to add the specifications of how many items are displayed per page. I know the default is 10 per page how can i change it to 40?
var _roster = DataCBase.StoredProcedures.GetUser<Users>(userID, r => new Users
{
Name = RosterColumnMap.Name(r),
Email = RosterColumnMap.Email(r)
});
Get User
public virtual IEnumerable<T> GetUser<T>(int userId, Func<IDataRecord, T> modelBinder, int resultsPerPage = 10, int pageNumber = 1)
{
if (userId < 1)
throw new NullReferenceException("The sectionId cannot be null, when retreiving an element");
if (resultsPerPage < 1)
resultsPerPage = 1; // enforce bare minimum result set
if (pageNumber < 1)
pageNumber = 1; // enforce one-based page numbering
SqlCommand _command = new SqlCommand("dbo.GetUser");
_command.CommandType = CommandType.StoredProcedure;
_command.Parameters.Add(new SqlParameter { ParameterName = "userId", SqlDbType = SqlDbType.Int, Value = userId });
_command.Parameters.Add(new SqlParameter { ParameterName = "resultsPerPage", SqlDbType = SqlDbType.Int, Value = resultsPerPage });
_command.Parameters.Add(new SqlParameter { ParameterName = "pageNumber", SqlDbType = SqlDbType.Int, Value = pageNumber });
return DbInstance.ExecuteAs<T>(_command, modelBinder);
}
Neither Linq nor entity framework have any default number of records 'per page'. But since your GetUser function includes a resultsPerPage parameter, you can just do this:
var _roster = DataCBase.StoredProcedures.GetUser<Users>(userID, r => new Users
{
Name = RosterColumnMap.Name(r),
Email = RosterColumnMap.Email(r)
}, 40);
To limit the number of results in Linq use the the Enumerable.Take method:
var _roster = DataCBase.StoredProcedures.GetUser<Users>(userID, r => new Users
{
Name = RosterColumnMap.Name(r),
Email = RosterColumnMap.Email(r)
}).Take(40);
I'm new to LINQ and I have this situation. I have this table:
ID Date Range
1 10/10/10 9-10
2 10/10/10 9-10
3 10/10/10 9-10
4 10/10/10 8-9
5 10/11/10 1-2
6 10/11/10 1-2
7 10/12/10 5-6
I just want to list the Maximun of rows per date by range, like this:
Date Range Total
10/10/10 9-10 3
10/11/10 1-2 2
10/12/10 5-6 1
I want to do this by using LINQ, do you have any ideas of how to do this?
I think something along these lines should work:
List<MyTable> items = GetItems();
var orderedByMax = from i in items
group i by i.Date into g
let q = g.GroupBy(i => i.Range)
.Select(g2 => new {Range = g2.Key, Count = g2.Count()})
.OrderByDescending(i => i.Count)
let max = q.FirstOrDefault()
select new {
Date = g.Key,
Range = max.Range,
Total = max.Count
};
Using extension methods:
List<MyTable> items = GetItems();
var rangeTotals = items.GroupBy(x => new { x.Date, x.Range }) // Group by Date + Range
.Select(g => new {
Date = g.Key.Date,
Range = g.Key.Range,
Total = g.Count() // Count total of identical ranges per date
});
var rangeMaxTotals = rangeTotals.Where(rt => !rangeTotals.Any(z => z.Date == rt.Date && z.Total > rt.Total)); // Get maximum totals for each date
unfortunately I can't test this at the moment but give this a try:
List<MyTable> items = GetItems();
items.Max(t=>t.Range.Distinct().Count());
This approach:
1) Groups by Date
2) For each Date, groups by Range and calculates the Total
3) For each Date, selects the item with the greatest Total
4) You end up with your result
public sealed class Program
{
public static void Main(string[] args)
{
var items = new[]
{
new { ID = 1, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 2, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 3, Date = new DateTime(10, 10, 10), Range = "9-10" },
new { ID = 4, Date = new DateTime(10, 10, 10), Range = "8-9" },
new { ID = 5, Date = new DateTime(10, 10, 11), Range = "1-2" },
new { ID = 6, Date = new DateTime(10, 10, 11), Range = "1-2" },
new { ID = 7, Date = new DateTime(10, 10, 12), Range = "5-6" },
};
var itemsWithTotals = items
.GroupBy(item => item.Date) // Group by Date.
.Select(groupByDate => groupByDate
.GroupBy(item => item.Range) // Group by Range.
.Select(groupByRange => new
{
Date = groupByDate.Key,
Range = groupByRange.Key,
Total = groupByRange.Count()
}) // Got the totals for each grouping.
.MaxElement(item => item.Total)); // For each Date, grab the item (grouped by Range) with the greatest Total.
foreach (var item in itemsWithTotals)
Console.WriteLine("{0} {1} {2}", item.Date.ToShortDateString(), item.Range, item.Total);
Console.Read();
}
}
/// <summary>
/// From the book LINQ in Action, Listing 5.35.
/// </summary>
static class ExtensionMethods
{
public static TElement MaxElement<TElement, TData>(this IEnumerable<TElement> source, Func<TElement, TData> selector) where TData : IComparable<TData>
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
bool firstElement = true;
TElement result = default(TElement);
TData maxValue = default(TData);
foreach (TElement element in source)
{
var candidate = selector(element);
if (firstElement || (candidate.CompareTo(maxValue) > 0))
{
firstElement = false;
maxValue = candidate;
result = element;
}
}
return result;
}
}
According to LINQ in Action (Chapter 5.3.3 - Will LINQ to Objects hurt the performance of my code?), using the MaxElement extension method is one of the most effecient approaches. I think the performance would be O(4n); one for the first GroupBy, two for the second GroupBy, three for the Count(), and four for loop within MaxElement.
DrDro's approach is going to be more like O(n^2) since it loops the entire list for each item in the list.
StriplingWarrior's approach is going to be closer to O(n log n) because it sorts the items. Though I'll admit, there may be some crazy magic in there that I don't understand.
I have a List of X items. I want to have LINQ query that will convert it into batches (a List of Lists), where each batch has 4 items, except for the last one which can have 1-4 (whatever the remainder is). Also, the number 4 should be configurable so it could 5, 17, etc.
Can anyone tell me how to write that?
List<Item> myItems = ...;
List<List<Item>> myBatches = myItems.????
Thank you in advance!
If you're happy with the results being typed as IEnumerable<IEnumerable<T>> then you can do this:
int groupSize = 4;
var myBatches = myItems.Select((x, i) => new { Val = x, Idx = i })
.GroupBy(x => x.Idx / groupSize,
x => x.Val);
If you want an actual List<List<T>> then you'll need to add a couple of extra ToList calls:
int groupSize = 4;
var myBatches = myItems.Select((x, i) => new { Val = x, Idx = i })
.GroupBy(x => x.Idx / groupSize,
x => x.Val,
(k, g) => g.ToList())
.ToList();
Here is a good article about using Take and Skip to do paging, which is identical functionality to what you are requesting. It doesn't get you all of the way to a single line of LINQ, but hopefully helps.
This made me think of how we did this before LINQ.
var vessels = new List<Vessel>()
{ new Vessel() { id = 8, name = "Millennium Falcon" },
new Vessel() { id = 4, name = "Ebon Hawk" },
new Vessel() { id = 34, name = "Virago"},
new Vessel() { id = 12, name = "Naboo royal starship"},
new Vessel() { id = 17, name = "Radiant VII"},
new Vessel() { id = 7, name = "Lambda-class shuttle"},
new Vessel() { id = 23, name = "Rogue Shadow"}};
var chunksize=2;
// With LINQ
var vesselGroups = vessels.Select((v, i) => new { Vessel = v, Index = i })
.GroupBy(c => c.Index / chunksize, c => c.Vessel, (t,e)=>e.ToList())
.ToList();
// Before LINQ (most probably not optimal)
var groupedVessels = new List<List<Vessel>>();
var g = new List<Vessel>();
var chunk = chunksize;
foreach(var vessel in vessels)
{
g.Add(vessel);
chunk--;
if (chunk == 0)
{
groupedVessels.Add(g);
g = new List<Vessel>();
chunk = chunksize;
}
}
groupedVessels.Add(g);
I can't get this bit of logic converted into a Linq statement and it is driving me nuts. I have a list of items that have a category and a createdondate field. I want to group by the category and only return items that have the max date for their category.
So for example, the list contains items with categories 1 and 2. The first day (1/1) I post two items to both categories 1 and 2. The second day (1/2) I post three items to category 1. The list should return the second day postings to category 1 and the first day postings to category 2.
Right now I have it grouping by the category then running through a foreach loop to compare each item in the group with the max date of the group, if the date is less than the max date it removes the item.
There's got to be a way to take the loop out, but I haven't figured it out!
You can do something like that :
from item in list
group item by item.Category into g
select g.OrderByDescending(it => it.CreationDate).First();
However, it's not very efficient, because it needs to sort the items of each group, which is more complex than necessary (you don't actually need to sort, you just need to scan the list once). So I created this extension method to find the item with the max value of a property (or function) :
public static T WithMax<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector)
{
var max = default(TValue);
var withMax = default(T);
var comparer = Comparer<TValue>.Default;
bool first = true;
foreach (var item in source)
{
var value = selector(item);
int compare = comparer.Compare(value, max);
if (compare > 0 || first)
{
max = value;
withMax = item;
}
first = false;
}
return withMax;
}
You can use it as follows :
from item in list
group item by item.Category into g
select g.WithMax(it => it.CreationDate);
UPDATE : As Anthony noted in his comment, this code doesn't exactly answer the question... if you want all items which date is the maximum of their category, you can do something like that :
from item in list
group item by item.Category into g
let maxDate = g.Max(it => it.CreationDate)
select new
{
Category = g.Key,
Items = g.Where(it => it.CreationDate == maxDate)
};
How about this:
private class Test
{
public string Category { get; set; }
public DateTime PostDate { get; set; }
public string Post { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
List<Test> test = new List<Test>();
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 5, 12, 0, 0), Post = "A1" });
test.Add(new Test() { Category = "B", PostDate = new DateTime(2010, 5, 5, 13, 0, 0), Post = "B1" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 12, 0, 0), Post = "A2" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 13, 0, 0), Post = "A3" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 14, 0, 0), Post = "A4" });
var q = test.GroupBy(t => t.Category).Select(g => new { grp = g, max = g.Max(t2 => t2.PostDate).Date }).SelectMany(x => x.grp.Where(t => t.PostDate >= x.max));
}
Reformatting luc's excellent answer to query comprehension form. I like this better for this kind of query because the scoping rules let me write more concisely.
from item in source
group item by item.Category into g
let max = g.Max(item2 => item2.PostDate).Date
from item3 in g
where item3.PostDate.Date == max
select item3;