There is a request:
"Take two older (max age) children and complete their stay in kindergarten"
var s = db.Child.Max(e => (DateTime.Today - e.Birthday));
foreach (var n in db.Child.Where(e => (DateTime.Today - e.Birthday) == s).Take(2))
db.DeleteObject(n);
"Birthday" - data type => "datetime".
Error: DbArithmeticExpression arguments must have a numeric common type
Where did I go wrong!?
You overcomplicating things. The oldest children are the one... which are born first.
So
var oldestChildren = db.Child.OrderBy(c => c.Birthday).Take(2).ToList();
//if "complete they stay in kindergarten means delete
foreach (var child in oldestChildren)
db.DeleteObject(child);
EF do not supports arithmetic operations with DateTime.
var s = db.Child.Max(e => EntityFunctions.DiffDays(DateTime.Today - e.Birthday));
EntityFunctions
pull you where statement out of the foreach statement and check your database DataType.
class Program
{
static void Main(string[] args)
{
List<Test> Tests = new List<Test>();
Tests.Add(new Test(new DateTime(2014, 01, 01)));
Tests.Add(new Test(new DateTime(2013, 01, 01)));
Tests.Add(new Test(new DateTime(2012, 01, 01)));
Tests.Add(new Test(new DateTime(2011, 01, 01)));
Tests.Add(new Test(new DateTime(2010, 01, 01)));
Tests.Add(new Test(new DateTime(2009, 01, 01)));
Tests.Add(new Test(new DateTime(2008, 01, 01)));
var s = Tests.Max(e => (DateTime.Today - e.Birthday));
var d =Tests.Where(e => (DateTime.Today - e.Birthday) == s).Take(2);
}
}
public class Test
{
public DateTime Birthday { get; set; }
public Test(DateTime dateTime)
{
Birthday = dateTime;
}
}
but I would think your logic is incorrect if you want the two oldest children. This is give you oldest children if their birthdates are the same, look at
var oldestChildren = db.Child.OrderBy(c => c.Birthday).Take(2).ToList();
Related
I have a collection of printed documents:
orderDetailId,submittedDate,printedCount
1,1 Jan 2106,1
1,1 Jan 2106,1
2,3 Jan 2106,1
3,5 Jan 2106,1
4,6 Jan 2106,1
I can get all the unique records with:
private static IQueryable<OrderFields> GetUniqueJobs(IQueryable<OrderFields> jobs)
{
IQueryable<OrderFields> uniqueJobs = jobs.GroupBy(x => x.OrderDetailId).Select(group => group.First());<br/>
return uniqueJobs.OrderBy(x => x.SubmittedDate);
}
but I would like the field printedCount to have the number of times each document was printed:
1,1 Jan 2106,2
2,3 Jan 2106,1
3,5 Jan 2106,1
4,6 Jan 2106,1
I would be grateful for any help
Thanks for the previous swift answers, but I did not as the question correctly. I can illustrate what I want with some slow ugly code, but this does not actually work ;-)
private static List<OrderFields> GetUniqueJobs(IQueryable<OrderFields> jobs)
{
Guid lastOrderDetailId = Guid.Empty;
List<OrderFields> uniqueJobs = new List<OrderFields>();
jobs = jobs.OrderBy(x => x.OrderDetailId);
foreach (var job in jobs)
{
Guid orderDetailId = job.OrderDetailId;
if (orderDetailId != lastOrderDetailId)
{
uniqueJobs.Add(job);
}
else
{
uniqueJobs[uniqueJobs.Count - 1].Printed++;
}
}
return uniqueJobs;
}
Can you help?
Try this
var uniqueJobs = jobs.GroupBy(x => new {x.orderDetailId, x.submittedDate})
.Select(group => new
{
orderDetailId = x.Key.orderDetailId,
submittedDate = x.Key.submittedDate,
printedCount = x.Sum(i => i.printedCount) // Or Count() I dont know your values.
});
return uniqueJobs.OrderBy(x => x.SubmittedDate);
You could use IEnumerable< KeyValuePair < int,int>>:
private static IEnumerable<KeyValuePair<int,int>> GetUniqueJobs(IQueryable jobs)
{
var uniqueJobs = jobs.GroupBy(x => x.OrderDetailId).Select(group => new KeyValuePair<int, int>(group.Key, group.Sum(x => x.printedCount))).OrderBy(x => x.Key);
return uniqueJobs;
}
Or to use an AnonymusType:
var uniqueJobs = jobs.GroupBy(x => x.OrderDetailId).Select(group => new {OrderDetailId = group.Key, printedCount = group.Sum(x => x.printedCount)});
You can achieve it with Sum and an anonymous type:
var result = jobs.GroupBy(x => x.OrderDetailId)
.Select(group => new {
// You can take the first if they are equivalent.
Value = group.First(),
PrintedCount = group.Sum(g => g.PrintedCount)
});
The user can select any number of week days from a list. An algorithm shall find the longest continuous group of selected days. The start day can be after the end day, if the group spans two weeks. If it makes it simpler, only a group of at least 3 days needs to be detected. With crossing the week border, this makes for a maximum of one group. (There can be no two groups of 3 days within a week that are not connected.)
For example, if the user selects Monday, Tuesday, Wednesday and Saturday from a list, the display should be something like "Monday-Wednesday and Saturday".
Another example is: Wed, Fri, Sat, Sun, Mon -> "Wed, Fri-Mon".
Is there an efficient algorithm for that, preferrably in C# or a similar language? My C# hackwork is now over a page long (incl. few comments) and still not finished.
Use this answer, slightly changed:
Use a modified version of dtb's GroupAdjacentBy which accepts a minCount as a parameter:
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate, int minCount)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
// if adjacent, add to list
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
// otherwise return previous elements:
// if less than minCount elements,
// return each element separately
if (list.Count < minCount)
{
foreach (var i in list)
yield return new List<T> { i };
}
else
{
// otherwise return entire group
yield return list;
}
// create next group
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
and change the criteria for GroupAdjacentBy to group on week transitions also:
// week starts with Monday, so this should
// represent: Wed, Fri, Sat, Sun, Mon
int[] array = new int[] { 1, 2, 4, 5, 6, 0 };
Func<int, int, bool> adjacentCriteria = (x, y) => (x+1==y) || (x==6 && y==0);
string result = string.Join(", ", array
.GroupAdjacentBy(adjacentCriteria, 3)
.Select(g => new int[] { g.First(), g.Last() }.Distinct())
.Select(g => string.Join("-", g)));
Console.WriteLine(result); // output: 1, 2, 4-0
I've finished my version of it. It's a bit longer than the other one, but then again it also handles the text representation and does exactly this task. How about that?
using System;
using System.Text;
namespace WeekMathTest
{
class Program
{
static void Main(string[] args)
{
string[] weekDayNames = new string[] {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
};
WeekDays weekDays = WeekDays.Monday | WeekDays.Tuesday | WeekDays.Thursday | WeekDays.Saturday | WeekDays.Sunday;
Console.WriteLine(WeekDayGroup(weekDays, weekDayNames));
}
static string WeekDayGroup(WeekDays weekDays, string[] weekDayNames)
{
int groupStart = 0, groupEnd = 0, groupLength = 0;
int maxGroupStart = 0, maxGroupEnd = 0, maxGroupLength = 0;
// Iterate all days in a repeated range
// (Sat/Sun doesn't need to be repeated or it would be in the first group)
for (int day = 1; day <= 7 + 5; day++)
{
// Is this day set?
int bitValue = 1 << ((day - 1) % 7);
bool daySet = ((int) weekDays & bitValue) != 0;
if (daySet)
{
if (groupStart == 0)
{
// First day set, remember it as group start
groupStart = day;
groupEnd = day;
groupLength = 1;
}
else
{
// Group has already been started, set new end
groupEnd = day;
groupLength = groupEnd - groupStart + 1;
if (groupLength == 7)
{
// Seen every day of the week, stop here
break;
}
}
}
else
{
if (groupLength >= 3 && groupLength > maxGroupLength)
{
// Group was long enough and longer than the last one, save it
maxGroupStart = groupStart;
maxGroupEnd = groupEnd;
maxGroupLength = groupLength;
}
// Reset operation variables
groupStart = 0;
groupEnd = 0;
groupLength = 0;
}
}
// Final check
if (groupLength >= 3 && groupLength > maxGroupLength)
{
// Group was long enough and longer than the last one, save it
maxGroupStart = groupStart;
maxGroupEnd = groupEnd;
maxGroupLength = groupLength;
}
// Clear all group days from the original value
for (int day = maxGroupStart; day <= maxGroupEnd; day++)
{
int bitValue = 1 << ((day - 1) % 7);
weekDays = (WeekDays) ((int) weekDays & ~bitValue);
}
// Generate output string
StringBuilder sb = new StringBuilder();
for (int day = 1; day <= 7; day++)
{
int bitValue = 1 << ((day - 1) % 7);
bool daySet = ((int) weekDays & bitValue) != 0;
if (daySet)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(weekDayNames[day - 1]);
}
else if (day == maxGroupStart)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(weekDayNames[day - 1]);
sb.Append("-");
sb.Append(weekDayNames[(maxGroupEnd - 1) % 7]);
}
}
return sb.ToString();
}
[Flags]
enum WeekDays
{
Monday = 1,
Tuesday = 2,
Wednesday = 4,
Thursday = 8,
Friday = 16,
Saturday = 32,
Sunday = 64
}
}
}
I have a collection of Car:
var cars = new List<Car>();
cars.Add(new Car { ProductionDate = new DateTime(2011,02,02) });
cars.Add(new Car { ProductionDate = new DateTime(2011, 01, 01) });
cars.Add(new Car { ProductionDate = new DateTime(2011,04,04,04,04,04) });
cars.Add(new Car { ProductionDate = new DateTime(2011, 03, 03, 3, 3, 3) });
I need to sort it by ProductionDate.
Result should be that at first I will have cars with date with time, so it should be car with 2011, 03, 03, 3, 3, 3 as production date, and at the end should be car with 2011, 02, 02 as production date. Second should be car with 2011, 04, 04, 04, 04, 04, and the third with 2011, 02, 02.
I can do it by using foreach but I believe that there is nicer way to do it.
cars.Sort((p, q) => {
var tm1 = p.ProductionDate.TimeOfDay;
var tm2 = q.ProductionDate.TimeOfDay;
if (tm1.Ticks == 0) {
if (tm2.Ticks == 0) {
return p.ProductionDate.CompareTo(q.ProductionDate);
}
return 1;
} else if (tm2.Ticks == 0) {
return -1;
} else {
return p.ProductionDate.CompareTo(q.ProductionDate);
}
});
But remember: what happens if a car is built at 0:00? A DateTime is made of a Data+Time. You can't see if the Time part is missing!
I'll add that if you need to use the Enumerable.OrderBy, then you can use my lamdba function with the LambdaComparer that you can find around the internet (as suggested by sll)
TimeSpan zeroTime = new TimeSpan(0);
var sortedCars = cars.OrderBy(c => c.ProductionDate.TimeOfDay.Equals(zeroTime) ? 1 : 0)
.ThenBy(c => c.ProductionDate)
.ToList();
I have something like that
var carsWithoutProductionTime = from car in cars
where car.ProductionDate.Hour == 0
orderby car.ProductionDate
select car;
var carsWithProductionTime = from car in cars
where car.ProductionDate.Hour != 0
orderby car.ProductionDate
select car;
var mergedCars = carsWithProductionTime.Union(carsWithoutProductionTime);
but It looks ugly. I would like to see something more sophisticated :)
I am looking to created batches from a Dictionary<string, DateTime> with the following constraints:
All items in the batch much share the same date
There can be no more than X items in a single batch. If there are more items with the same date, another batch must be created.
I have worked out the following logic, but was wondering if there was some other more succinct way of doing this with just linq.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace dictionary_sort_by_value_test
{
class Program
{
static void Main(string[] args)
{
int maxBatchSize = 3;
Dictionary<string, DateTime> secs = new Dictionary<string, DateTime>();
secs.Add("6571 JT", new DateTime(2011, 1, 10));
secs.Add("6572 JT", new DateTime(2011, 1, 12));
secs.Add("6573 JT", new DateTime(2011, 1, 12));
secs.Add("6574 JT", new DateTime(2011, 1, 12));
secs.Add("6575 JT", new DateTime(2011, 1, 10));
secs.Add("6576 JT", new DateTime(2011, 1, 11));
secs.Add("6577 JT", new DateTime(2011, 1, 11));
secs.Add("6578 JT", new DateTime(2011, 1, 11));
secs.Add("6579 JT", new DateTime(2011, 1, 11));
var sorted = secs.OrderBy(o => o.Value).GroupBy(o => o.Value);
foreach (var date in sorted)
{
Console.Write("\nNew batch at {0} \n", date.Key);
int batchsize = 0;
foreach (var sec in date)
{
if (batchsize < maxBatchSize)
{
Console.Write(" {0} {1} \n", sec.Key, sec.Value);
batchsize++;
}
else
{
Console.Write("\nNew batch at {0} \n", date.Key);
Console.Write(" {0} {1} \n", sec.Key, sec.Value);
batchsize = 1;
}
}
}
}
}
}
You group by your key, then inside the result you group by the item index divided by the desired chunk size.
var chunkSize = 3;
var sorted = secs
.OrderBy(kv => kv.Key)
.GroupBy(o => o.Value)
.Select(g => new {Chunks = g.Select((o,i) => new {Val = o, Index = i})
.GroupBy(item => item.Index / chunkSize)});
And displaying it:
foreach(var item in sorted.SelectMany(item => item.Chunks))
{
Console.WriteLine("New batch at " + item.First().Val.Value);
foreach(var element in item)
Console.WriteLine(element.Val.Key);
}
Not strictly using linq to solve your problems but a more succinct way of handling the iteration:
static void Main(string[] args)
{
int maxBatchSize = 3;
Dictionary<string, DateTime> secs = new Dictionary<string, DateTime>();
secs.Add("6571 JT", new DateTime(2011, 1, 10));
secs.Add("6572 JT", new DateTime(2011, 1, 12));
secs.Add("6573 JT", new DateTime(2011, 1, 12));
secs.Add("6574 JT", new DateTime(2011, 1, 12));
secs.Add("6575 JT", new DateTime(2011, 1, 10));
secs.Add("6576 JT", new DateTime(2011, 1, 11));
secs.Add("6577 JT", new DateTime(2011, 1, 11));
secs.Add("6578 JT", new DateTime(2011, 1, 11));
secs.Add("6574 JT", new DateTime(2011, 1, 11));
secs.Add("6579 JT", new DateTime(2011, 1, 11));
secs.Add("6580 JT", new DateTime(2011, 1, 11));
secs.Add("6581 JT", new DateTime(2011, 1, 11));
secs.Add("6582 JT", new DateTime(2011, 1, 11));
secs.Add("6583 JT", new DateTime(2011, 1, 11));
secs.OrderBy(o => o.Value).GroupBy(o => o.Value).ToList().ForEach(date =>
{
Console.Write("\nNew batch at {0} \n", date.Key);
int batchsize = 0;
foreach (var sec in date)
{
if (batchsize >= maxBatchSize)
{
Console.Write("\nNew batch at {0} \n", date.Key);
batchsize = 0;
}
Console.Write(" {0} {1} \n", sec.Key, sec.Value);
batchsize++;
}
});
Console.ReadLine();
}
You can do it with 2 GroupBys. First you group by DateTime, and then group by page. I had to specify the generic arguments explicitly, because the compiler was picking the wrong overload, and that made the query code longer.
var groups = secs.GroupBy<KeyValuePair<string, DateTime>, DateTime, string, Group>(
p => p.Value,
p => p.Key,
(d, g) => new Group {
Date = d,
Pages = g.Select((s, i) => new KeyValuePair<string, int>(s, i / maxBatchSize))
.GroupBy<KeyValuePair<string, int>, int, string, Page>(
p => p.Value,
p => p.Key,
(p, g2) => new Page { Id = p, Items = g2.ToList() }) });
foreach (var group in groups)
{
Console.WriteLine("Date: {0}", group.Date);
foreach (var page in group.Pages)
{
Console.WriteLine("Page: {0}", page.Id);
foreach (var key in page.Items)
Console.WriteLine(key);
}
}
As you can see, I had to define 2 classes because as I said, I had to specify the generic arguments, because using anonymous types made the overload resolution pick another overload.
class Group
{
public DateTime Date;
public IEnumerable<Page> Pages;
}
class Page
{
public int Id;
public IEnumerable<string> Items;
}
Hope this helps.
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;