I am struggling to do a group by. I have the following list:
Date = 10/03 06:40 AM, Val = 10
Date = 10/03 08:55 PM, Val = 5
Date = 11/03 06:40 AM, Val = 5
Date = 11/03 10:50 AM, Val = 9
Date = 11/03 06:40 PM, Val = 14
And I want this list:
Date = 10/03, Val = 5
Date = 11/03, Val = 14
So a list grouped by Date.Date but with Val depending on Max(d => d.Date).
I did it with a foreach but I am pretty sure that we can do something better using LINQ (groupby,select). Any ideas?
Cheers
I believe you want something like
static T MaxBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
where TKey: IComparable<TKey> =>
source.OrderByDescending(selector).First();
Then
from item in items
group item by item.Date.Date into byDate
select new
{
Date = byDate.Key,
byDate.MaxBy(item => item.Date).Val
}
If you do not wish to create a dedicated method you can also do it inline of course
from item in items
group item by item.Date.Date into byDate
let ordered = from item in byDate
orderby item.Date descending
select item.Val
select new
{
Date = byDate.Key,
Val = ordered.First()
}
Note: Thanks to mjwills who pointed out a bug which I was then able to correct
It should be something like this:
var result = from d in list
group d by d.Date.Date into grouped
let max = grouped.Max(d => d.Date)
select new {
Date = grouped.Key,
list.FirstOrDefault(c => c.Date == max)?.Val
};
Something along the line of:
values
.GroupBy(value => value.Date.Date)
.Select(grouped => new
{
Date = grouped.Key,
Val = values.First(value => value.Date == grouped.Date.Max()).Val
});
Here's what i have end up with :
Example entity :
public class ExampleEntity
{
public DateTime Date { get; set; }
public int Val { get; set; }
}
Example code :
var listOfExampleEntity = new List<ExampleEntity>()
{
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 4, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 5, 40, 00),
Val =5
},new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 7, 40, 00),
Val =3
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 8, 40, 00),
Val =4
},
};
var temp = listOfExampleEntity.GroupBy(
p => p.Date.Date,
p => p.Val,
(date, val) => new { Date = date, Value = val.Last() });
Related
I get a question and have no idea about it ....
I have a list here called list<Promotion>
The structure of Promotion is like this:
new promotion()
{
string id,
money amount,
date startDate,
date endDate
}
The many elements of this list is like this,
list[0] = {id =1, amount = 2, startDate = 2015-10-14, endDate= 2015-12-31}
list[1] = {id =1, amount = 3, startDate = 2015-11-01, endDate= 2015-11-15}
list[2] = {id =3, amount = 10, startDate = 2015-11-01, endDate= 2015-12-01}
list[3] = {id =5, amount = 32, startDate = 2015-11-01, endDate= 2015-12-01}
So I want to merge list[0] and list[1] together but also keep list[2] and list[3], the result will be something like this
list[0] = {id =1, amount = 5, startDate = 2015-10-14, endDate= 2015-11-15}
list[1] = {id =3, amount = 10, startDate = 2015-11-01, endDate= 2015-12-01}
list[2] = {id =5, amount = 32, startDate = 2015-11-01, endDate= 2015-12-01}
Because list[0] and list[1] has the same id, they are merged. I want to add amount together, and keep the lower date time for both startDate and endDate. After merging list[0] and list[1] become list[0]. And list[2] and list[3] became list[1] and list[2]
Any idea? Thank you!
It seems like you are looking for a simple group by query like this:
var promotions = new List<Promotion>();
promotions.Add(new Promotion { Id = 1, Amount = 2, StartDate = new DateTime(2015, 10, 14), EndDate = new DateTime(2015, 12, 31) });
promotions.Add(new Promotion { Id = 1, Amount = 3, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 11, 15) });
promotions.Add(new Promotion { Id = 3, Amount = 10, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 12, 01) });
promotions.Add(new Promotion { Id = 5, Amount = 32, StartDate = new DateTime(2015, 11, 01), EndDate = new DateTime(2015, 12, 01) });
promotions = promotions.GroupBy(p => p.Id).Select(p => new Promotion
{
Id = p.Key,
Amount = p.Sum(i => i.Amount),
StartDate = p.Min(i => i.StartDate),
EndDate = p.Min(i => i.EndDate)
}).ToList();
First group them by the id. Then for each Group, select a new element according to your rules:
IEnumerable<promotion> merged = list.GroupBy(p => p.id)
.Select(g => new Promotion
{
id = g.Key,
amount = g.Sum(p => p.amount),
startDate = g.Min(p => p.startDate),
endDate = g.Min(p => p.endDate)
};
(Note that lower-case property and class names is not the usual convention in .Net. It's technically fine, but not the style generally used).
I have LINQ sql (see below, thanks to Cameron ). I am trying to get a property (ItemCode) from class First without using that in Group by clause.
How do I do that?
Don't use First.ItemCode in group by but still want it in output by First.Begin, First.End order by decending.
public class First
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
public string ItemCode;
}
public class Second
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
}
List<First> firstAccount = new List<First>();
List<Second> secondAccount = new List<Second>();
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 7),
Amount = 1000,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 0,
ItemCode = ""
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 7, 7),
End = new DateTime(2014, 7, 14),
Amount = 1000,
ItemCode = "BBB"
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 2000
});
var result = from account in (from first in firstAccount
join second in secondAccount
on first.Account equals second.Account
where
((first.Begin >= second.Begin && first.Begin <= second.Begin) &&
(first.End >= second.Begin && first.End <= second.End))
select new
{
first.Account,
second.Begin,
second.End,
first.Amount,
first.ItemCode
})
group account by new {account.Account, account.Begin, account.End }
into groupedAccounts
select new
{
groupedAccounts.Key.Account,
groupedAccounts.Key.Begin,
groupedAccounts.Key.End,
Sum = groupedAccounts.Sum(a => a.Amount)
};
One way to get the itemcode is to change the last select.
Add this line
Itemcode = String.Join(" ",groupedAccounts.Select(q=> q.ItemCode))
after Sum = groupedAccounts.Sum(a => a.Amount),
It should produce itemcode
foreach (var data in result)
{
Console.WriteLine(data.Account + " " + data.Itemcode);
}
Output
1234 AAA
1234 AAA
I have the requirement to create a query using Linq to Entities where the birthday must fall within 2 days ago and the next 30 days.
The following returns nothing:
DateTime twoDaysAgo = DateTime.Now.AddDays(-2);
int twoDaysAgoDay = twoDaysAgo.Day;
int twoDaysAgoMonth = twoDaysAgo.Month;
DateTime MonthAway = DateTime.Now.AddDays(30);
int monthAwayDay = MonthAway.Day;
int monthAwayMonth = MonthAway.Month;
var bdays = from p in db.Staffs where EntityFunctions.TruncateTime(p.BirthDate) > EntityFunctions.TruncateTime(twoDaysAgo) &&
EntityFunctions.TruncateTime(p.BirthDate) < EntityFunctions.TruncateTime(MonthAway)
orderby p.BirthDate select p;
return bdays;
The problem I'm having is that I need something where if the birthday falls from 11/3 to 12/5, it should return it. The reason it fails because the birthdays include the Year. However, when I use something like:
p.BirthDate.Value.Month
I receive the error that this isn't support with Linq to Entities. Any assistance would be appreciated.
Year-wrapping independent solution:
void Main()
{
var birthdays = new List<DateTime>();
birthdays.Add(new DateTime(2013, 11, 08));
birthdays.Add(new DateTime(2012, 05, 05));
birthdays.Add(new DateTime(2014, 05, 05));
birthdays.Add(new DateTime(2005, 11, 08));
birthdays.Add(new DateTime(2004, 12, 31));
foreach(var date in birthdays.Where(x => x.IsWithinRange(twoDaysAgo, MonthAway))){
Console.WriteLine(date);
}
}
public static class Extensions {
public static bool IsWithinRange(this DateTime #this, DateTime lower, DateTime upper){
if(lower.DayOfYear > upper.DayOfYear){
return (#this.DayOfYear > lower.DayOfYear || #this.DayOfYear < upper.DayOfYear);
}
return (#this.DayOfYear > lower.DayOfYear && #this.DayOfYear < upper.DayOfYear);
}
}
Output with
DateTime twoDaysAgo = DateTime.Now.AddDays(-2);
DateTime MonthAway = DateTime.Now.AddDays(30);
8/11/2013 0:00:00
8/11/2005 0:00:00
Output with
DateTime twoDaysAgo = new DateTime(2012, 12, 25);
DateTime MonthAway = new DateTime(2013, 01, 05);
31/12/2004 0:00:00
If you want to ignore the value of the year, what about using DayOfYear function ?
var bdays = from p in db.Staffs
where EntityFunctions.DayOfYear(p.BirthDate) > EntityFunctions.DayOfYear(twoDaysAgo) &&
EntityFunctions.DayOfYear(p.BirthDate) < EntityFunctions.DayOfYear(MonthAway)
orderby p.BirthDate select p;
You can change all the years to now since year is irrelevant and then you can check it this way
DateTime twoDaysAgo = DateTime.Today.AddDays(-2);
DateTime monthAway = DateTime.Today.AddMonths(1);
List<DateTime> checkDates = new List<DateTime>
{ new DateTime(2011, 11, 3), new DateTime(2011, 12, 5), new DateTime(2011, 12, 6), new DateTime(2011, 11, 2) };
checkDates = checkDates.Select(x => new DateTime(DateTime.Today.Year, x.Month, x.Day)).ToList();
var bdays = from p in checkDates
where (p >= twoDaysAgo && p <= monthAway) ||
(p>= twoDaysAgo.AddYears(-1) && p <= monthAway.AddYears(-1))
orderby p
select p;
This results in
11/3/2013 12:00:00 AM
12/5/2013 12:00:00 AM
This also works with the following list of dates when today is new DateTime(2013, 12, 31)
List<DateTime> checkDates = new List<DateTime>
{ new DateTime(2011, 12, 29), new DateTime(2011, 12, 28), new DateTime(2011, 1, 30), new DateTime(2011, 2, 2) };
Giving the results
1/30/2013 12:00:00 AM
12/29/2013 12:00:00 AM
How about if you add the the nr. of years from the birthdate to today?
Something like:
(untested)
var now = DateTime.Now;
var twoDaysAgo = now.AddDays(-2);
var monthAway = now.Now.AddDays(30)
var bdays =
from p in db.Staffs
let bDay = EntityFunctions.AddYears(p.BirthDate,
EntityFunctions.DiffYears(now, p.BirthDate))
where
bDay > twoDaysAgo &&
bDay < monthAway
orderby p.BirthDate
select p;
I have 2 lists in C#:
public class AvailableSlot
{
public DateTime DateTime;
public string Name
}
List<AvailableSlot> list1 = GetList();
List<AvailableSlot> list2 = GetAnotherList();
I want to call intersect on these lists to find out where there are items in both lists for the same date. I know i can use .Intersect to get this info but I have a slightly more complicated requirement. I want to return a intersected list but i want to this list to contain a list of objects with all of the name in them. so something like this:
List<AvailableSlot2> intersectedList . ..
where AvailableSlot2 is this below:
public class AvailableSlot2
{
public DateTime DateTime;
public string[] Names;
}
Is there any ability to do this transformation after trying to intersect between two lists?
I would just union the two lists, group by DateTime and then pull out the names from the group:
var list1 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Alpha" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bravo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Charlie" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Delta" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Echo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Foxtrot" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 4), Name = "Golf" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hotel" }
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Apple" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bannana" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Dog" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Egg" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hi" }
};
var list3 = list1.Where (l => list2.Where (li => l.DateTime == li.DateTime).Any ())
.Union(list2.Where (l => list1.Where (li => l.DateTime == li.DateTime).Any ()));
var groupedItems = from slot in list3
group slot by slot.DateTime into grp
select new AvailableSlot2 {
DateTime = grp.Key,
Names = grp.Select (g => g.Name).ToArray()
};
foreach(var g in groupedItems)
{
Console.WriteLine(g.DateTime);
foreach(var name in g.Names)
Console.WriteLine(name);
Console.WriteLine("---------------------");
}
Output:
2/1/2013 12:00:00 AM
Alpha
Delta
Apple
Dog
---------------------
2/2/2013 12:00:00 AM
Bravo
Echo
Bannana
Egg
---------------------
2/5/2013 12:00:00 AM
Hotel
Hi
---------------------
You can use a LINQ to Objects Join() to line up items with the same DateTime property and then collect all the names into an array
var joinedItems = from slot1 in list1
join slot2 in list2
on slot1.DateTime equals slot2.DateTime into g
where g.Any()
select new AvailableSlot2
{
DateTime = slot1.DateTime,
Names = Enumerable.Range(slot1.Name,1).Union(g.Select(s => s.Name)).ToArray()
}
You can make use of ToLookup:
DateTime dt1 = new DateTime(2013, 2, 1);
DateTime dt2 = new DateTime(2013, 3, 1);
DateTime dt3 = new DateTime(2013, 4, 1);
var list1 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt1, Name = "n3",},
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt3, Name = "n3",},
};
var intersected = list1.Select (l => l.DateTime).
Intersect(list2.Select (l => l.DateTime));
var lookup = list1.Union(list2).ToLookup (
slot => slot.DateTime, slot => slot);
lookup.Where (l => intersected.Contains(l.Key)).Select (
slot => new
{
DateTime=slot.Key,
Names=slot.Select (s => s.Name)
});
Which in this case gives the result:
DateTime Names
01/02/2013 00:00 n1
n3
n1
01/03/2013 00:00 n2
n2
You could of course use Names=slot.Select(s => s.Name).Distinct() to get a distinct list of names.
Have a list like this:
01/01/2009, 120
04/01/2009, 121
30/12/2009, 520
01/01/2010, 100
04/01/2010, 101
31/12/2010, 540
I need to find the last value for each year, e.g. the result would be 520, 540?
var lastValues = records.OrderByDescending(r => r.Date)
.GroupBy(r => r.Date.Year)
.Select(g => g.First().Value);
class Program
{
static void Main()
{
var list = new[]
{
new { Date = new DateTime(2009, 1, 1), Value = 120 },
new { Date = new DateTime(2009, 4, 1), Value = 121 },
new { Date = new DateTime(2009, 12, 30), Value = 520 },
new { Date = new DateTime(2010, 1, 1), Value = 100 },
new { Date = new DateTime(2009, 4, 1), Value = 101 },
new { Date = new DateTime(2010, 12, 31), Value = 540 },
};
var result = list
.GroupBy(x => x.Date.Year)
.Select(g => new { Date = g.Key, MaxValue = g.Max(x => x.Value) });
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}