Related
There are two entities, for example, job and solution.
Each of them has a date field and a level field and a quantity field.
It is necessary to combine them so that they are grouped first by level, then by month, and at the same time, their quantity must be summed up.
I tried different options, but nothing comes out at all. The main problem is grouping by months and summing the numbers in the enclosed sheets.
That is, the output should be one sequence of summed numbers, grouped by level, and then by month.
For example:
var jobs = new List<Job>()
{
new Job { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 111 },
new Job { Level = 1, Date = new DateTime(2019, 1, 20), Quantity = 222 },
new Job { Level = 2, Date = new DateTime(2019, 2, 1), Quantity = 333 },
new Job { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 444 }
};
var solutions = new List<Solution>()
{
new Solution { Level = 1, Date = new DateTime(2019, 2, 1), Quantity = 555 },
new Solution { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 666 },
new Solution { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 777 },
new Solution { Level = 2, Date = new DateTime(2019, 1, 20), Quantity = 888 }
};
Output:
Level 1 -> 1 Jan 2019 -> 1110 (111 + 222 + 777)
Level 1 -> 1 Feb 2019 -> 555
Level 2 -> 1 Jan 2019 -> 888
Level 2 -> 1 Feb 2019 -> 1443 (333 + 444 + 666)
And so on. And yes, all this is in EF6.
Try following which uses Concat. I create a class for the merging. It can also be done anonymously.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication116
{
class Program
{
static void Main(string[] args)
{
var jobs = new List<Job>()
{
new Job { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 111 },
new Job { Level = 1, Date = new DateTime(2019, 1, 20), Quantity = 222 },
new Job { Level = 2, Date = new DateTime(2019, 2, 1), Quantity = 333 },
new Job { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 444 }
};
var solutions = new List<Solution>()
{
new Solution { Level = 1, Date = new DateTime(2019, 2, 1), Quantity = 555 },
new Solution { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 666 },
new Solution { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 777 },
new Solution { Level = 2, Date = new DateTime(2019, 1, 20), Quantity = 888 }
};
List<LevelDateQuantity> concat = jobs.Select(x => new LevelDateQuantity() { Date = x.Date, Level = x.Level, Quantity = x.Quantity})
.Concat( solutions.Select(x => new LevelDateQuantity() { Date = x.Date, Level = x.Level, Quantity = x.Quantity})).ToList();
List<LevelDateQuantity> results = concat.OrderBy(x => x.Level).ThenBy(x => x.Date)
.GroupBy(x => new { level = x.Level, date = new DateTime(x.Date.Year, x.Date.Month,1)})
.Select(x => new LevelDateQuantity() { Level = x.Key.level, Date = x.Key.date, Quantity = x.Sum(y => y.Quantity)})
.ToList();
}
}
public class LevelDateQuantity
{
public int Level { get; set; }
public DateTime Date { get; set; }
public int Quantity { get; set; }
}
public class Job : LevelDateQuantity
{
public int Level { get; set; }
public DateTime Date { get; set; }
public int Quantity { get; set; }
}
public class Solution : LevelDateQuantity
{
public int Level { get; set; }
public DateTime Date { get; set; }
public int Quantity { get; set; }
}
}
Oath, because we can not see your poco class structure we don't know if the two tables are seperate or has a one to many relation to a master table, so by the code you have provided I would do this ;
var jobs = new List<Job>()
{
new Job { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 111 },
new Job { Level = 1, Date = new DateTime(2019, 1, 20), Quantity = 222 },
new Job { Level = 2, Date = new DateTime(2019, 2, 1), Quantity = 333 },
new Job { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 444 }
};
var solutions = new List<Solution>()
{
new Solution { Level = 1, Date = new DateTime(2019, 2, 1), Quantity = 555 },
new Solution { Level = 2, Date = new DateTime(2019, 2, 20), Quantity = 666 },
new Solution { Level = 1, Date = new DateTime(2019, 1, 1), Quantity = 777 },
new Solution { Level = 2, Date = new DateTime(2019, 1, 20), Quantity = 888 }
};
foreach (var sol in solutions)
{
var jb = new Job();
jb.Level = sol.Level;
jb.Date = sol.Date ;
jb.Quantity= sol.Quantity;
jobs.Add(jb);
}
var result = Jobs.GroupBy(x=> new { x.Level, x.Date}).Select(x=> new
{
level = x.Key.Level,
date = x.Key.Date,
sumQ = x.Sum(y => y.Quantity )
});
I haven't tested the code and not wrote in in a compiler so there might be some typeerrors apart from that this should solve your problem.
I am trying to retrieve the month name and the year using LINQ from a list that has 2 properties without repeating the name of the months and the year.
public class Record
{
public int Id { get; set; }
public DateTime Date { get; set; }
}
DateTime d1 = new DateTime(2015, 1, 14);
DateTime d2 = new DateTime(2016, 3, 12);
DateTime d3 = new DateTime(2016, 4, 17);
DateTime d4 = new DateTime(2015, 5, 19);
DateTime d5 = new DateTime(2016, 6, 10);
List<Record> dates = new List<Record>
{
new Record { Id= 1, Date = d1 },
new Record { Id= 2, Date = d2 },
new Record { Id= 3, Date = d3 },
new Record { Id= 4, Date = d4 },
new Record { Id= 5, Date = d5 }
};
//Month should be in string format (January,June, etc)
// Get Year and Months from that list withour repeating the names
//List<string> months =
//List < string > years =
For months and using Linq:
List<string> months = dates.Select(d => d.Date.ToString("MMMM"))
.Distinct()
.ToArray();
Information on the ToStirng format for the month name can be found on MSDN here.
and for years:
List<string> years = dates.Select(d => d.Date.Year.ToString())
.Distinct()
.ToArray();
Although it is unclear how you want the list of years to look.
Information on Distinct can be found on MSDN here.
With an extension method to simplify it (taken from here):
static class DateTimeExtensions
{
public static string ToMonthName(this DateTime dateTime)
{
return CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(dateTime.Month);
}
}
You can do this:
var months = dates.Select(r => r.Date.ToMonthName())
.Distinct();
var years = dates.Select(r => r.Date.Year)
.Distinct();
Note that I've given years as int here, if you want strings, then just add ToString().
I have a datatable e.g.
ID Agent Date
1 A1 2016-02-19
2 A1 2016-02-20
3 A2 2016-02-19
4 A3 2016-02-20
i want to group these records by Date and return the ID and Agent is list like:
Date:2016-02-19 ,{(1,A1),(3,A2)}
Date:2016-02-20 ,{(2,A1),(4,A3)}
A collection object of ID and Agent Group by Date.
Please suggect how to achieve it using LINQ.
I think that something like the below would do that you want.
var result = datatable.AsEnumerable()
.GroupBy(row=>row.Field<DateTime>("Date"))
.Select(gr=>new
{
Date = gr.Key,
Agents = gr.Select(x => new
{
Id = x.Field<int>("ID"),
Agent = x.Field<string>("Agent")
})
});
Update
If you need for each date the agents to be a comma separated list of the agents,
like this {(2,A1),(4,A3)}, you could try the following approach.
var result = datatable.AsEnumerable()
.GroupBy(row=>row.Field<DateTime>("Date"))
.Select(gr=>new
{
Date = gr.Key,
Agents = "{"+ string.Join(",",
gr.Select(x => new
string.Format("({0},{1})",
x.Field<int>("ID"),
x.Field<string>("Agent"))+"}"
})
});
Here is my example class:
public class Log
{
public int ID { get; set; }
public string Agent { get; set; }
public DateTime Date { get; set; }
public Log(int id, string agent, DateTime date)
{
ID = id;
Agent = agent;
Date = date;
}
}
And here the LINQ statement with some test data:
List<Log> list = new List<Log>()
{
new Log(1, "A", new DateTime(2016, 01, 01, 0, 0, 0)),
new Log(2, "B", new DateTime(2016, 01, 01, 0, 0, 0)),
new Log(3, "C", new DateTime(2016, 01, 01, 0, 0, 0)),
new Log(4, "A", new DateTime(2016, 01, 02, 0, 0, 0)),
new Log(5, "A", new DateTime(2016, 01, 03, 0, 0, 0))
};
var result = from entry in list
group entry by entry.Date
into g
select g;
This will group all your data into groups with a Key based on Log.Date, where each group consits of multiple entries, each with an ID, Agent and Date property.
Here some example code how to access result:
result.ToList().ForEach(group =>
{
Console.WriteLine(group.Key); // The date
group.ToList().ForEach(entry => Console.WriteLine(entry.ID + " - " + entry.Agent)); // Print out each entry per group
});
I'm working on the following problem:
I want to fill a two-dimensional [365,2] Array. The first value is supposed to hold the date: starting with January 1st and ending with December the 31st. The second value is supposed to hold the corresponding Zodiac Sign to each date:
e.g. array[0, 0] holds 101 and array[0, 1] holds Aries and so on.
I've written a function:
public static void fill_array(string[,] year_zodiac, int days, string zodiac, string startdate, int starting_day)
{
//function to fill array with date and zodiac
int startdate_int = 0;
for (int i = starting_day; i <= days; i++)
{
year_zodiac[i, 0] = startdate;
year_zodiac[i, 1] = zodiac;
startdate_int = Int32.Parse(startdate);
startdate_int++;
startdate = startdate_int.ToString();
}
and call it like this:
fill_array(main_array, 18, "Aries", "101", 0);
This has to be done for every Zodiac sign. I circumvented the month break problem by simply calling fill_array twice (i.e. I call it once for the part of Aries in December and once for the part of aries in January).
This Solution works, but it seems incredibly crude to me.
Can anybody give me some pointers towards a more elegant solution?
Here is a class that does what you want, it has been tested. I did not fill out all signs on the first example, but when I re-factored it I did. I suggest you test this well as I only tested a few cases and might have missed some edge cases.
As has been pointed out to me by #ClickRick, I did start with his array/enum design, but found that lacking when using Linq and moved to a List. I also had to fix his data array so it would compile. I'm giving credit as is due.
public class Signs
{
static List<string> SignList = new List<string>() { "Aquarius", "Pisces", "Aries", "Taurus", "Not Found"};
static DateTime[] startDates = {
new DateTime(DateTime.Now.Year,1,21),
new DateTime(DateTime.Now.Year,2,20),
new DateTime(DateTime.Now.Year,3,21),
new DateTime(DateTime.Now.Year,4,21),
new DateTime(DateTime.Now.Year,12,31) };
static public string Sign(DateTime inDate)
{
return SignList.Zip(startDates, (s, d) => new { sign = s, date = d })
.Where(x => (inDate.Month*100)+inDate.Day <= (x.date.Month*100)+x.date.Day)
.Select(x => x.sign)
.First();
}
}
re-factored (this is clearer with the example above first)
public class Signs
{
static List<string> SignList = new List<string>() {
"Capricorn", "Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Not Found" };
static List<int> startDates = new List<int>() {
// month * 100 + day of month
120, 219, 320, 420, 521, 621, 722, 821, 923, 1023, 1122, 1222, 1232, 9999 // edge marker
};
static public string Sign(DateTime inDate)
{
return SignList[startDates.TakeWhile(d => (inDate.Month*100)+inDate.Day > d).Count()];
}
}
Why do you specifically want an array? Surely a more sophisticated data structure would help to encapsulate the functionality and help you to isolate your remaining code from knowing about the workings of it, and would also let you take account in the future of more subtle variations in the exact dates where they vary by year if you ever want to do that.
For example:
public class SO23182879
{
public enum StarSign
{
Aquarius, Pisces, Aries, Taurus, Gemini, Cancer,
Leo, Virgo, Libra, Scorpio, Sagittarius, Capricorn
};
static DateTime[] starSignStartDates = new DateTime[]
{
new DateTime(DateTime.Now.Year, 1, 20),
new DateTime(DateTime.Now.Year, 2, 19),
new DateTime(DateTime.Now.Year, 3, 21),
new DateTime(DateTime.Now.Year, 4, 20),
new DateTime(DateTime.Now.Year, 5, 21),
new DateTime(DateTime.Now.Year, 6, 21),
new DateTime(DateTime.Now.Year, 7, 23),
new DateTime(DateTime.Now.Year, 8, 23),
new DateTime(DateTime.Now.Year, 9, 23),
new DateTime(DateTime.Now.Year, 10, 23),
new DateTime(DateTime.Now.Year, 11, 22),
new DateTime(DateTime.Now.Year, 12, 22),
new DateTime(DateTime.Now.Year, 1, 20),
};
private class StarSignDateRange
{
public StarSign Sign { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
private List<StarSignDateRange> signStartDates = new List<StarSignDateRange>();
public SO23182879()
{
int date = 0;
foreach (StarSign sign in Enum.GetValues(typeof(StarSign)))
{
signStartDates.Add(new StarSignDateRange
{
Sign = sign,
StartDate = starSignStartDates[date],
EndDate = starSignStartDates[date + 1]
});
++date;
}
}
public StarSign Sign(DateTime date)
{
return signStartDates.First(
sd => date.Month == sd.StartDate.Month && date.Day >= sd.StartDate.Day ||
date.Month == sd.EndDate.Month && date.Day < sd.EndDate.Day
).Sign;
}
public void Test()
{
IList<DateTime> testDates = new List<DateTime>
{
new DateTime(2014,1,1),
new DateTime(2014,1,19),
new DateTime(2014,1,20),
new DateTime(2014,4,19),
new DateTime(2014,4,20),
new DateTime(2014,12,21),
new DateTime(2014,12,22),
new DateTime(2014,12,31),
};
foreach (DateTime d in testDates)
Console.WriteLine(string.Format("{0} is in {1}", d, Sign(d)));
}
}
As you'll see, I have completed the code which I had started earlier, and added a Test() method for you to see that the edge conditions work. I am grateful to Hogan for pointing out the "year zero" problem and other similar "gotchas" in my earlier sketch.
Given a list of dates (which may not be sorted), I want to build a list of date ranges -
E.g. Assuming MM/DD format,
Input - 5/1, 5/5, 5/6, 5/15, 5/7, 5/8, 5/19,5/20, 5/23
Output -
Date Range 1: 5/1 to 5/1
Date Range 2: 5/5 to 5/8
Date Range 3: 5/15 to 5/15
Date Range 4: 5/19 to 5/20
Date Range 5: 5/23 to 5/23
Basically, a range should be continuous.
Sort the dates
Start a range containing the next date (to start with it will be the first one)
Is the second "valid" date the next date which would be in the range? If so, keep going. If not, close the current range and start a new one.
Repeat until you've run out of dates, at which point you close the current range and you're done.
You can create a list of DateTime (possibly using the same year for them all) and sort it.
It is then fairly easy to find if a day and the next day exist in the list (using DateTime.AddDays(1)).
public class DateRange
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
[TestClass]
public class DateRangerTest
{
private List<DateRange> GetDateRanges(List<DateTime> dates)
{
if (dates == null || !dates.Any()) return null;
dates = dates.OrderBy(x => x.Date).ToList();
var dateRangeList = new List<DateRange>();
DateRange dateRange = null;
for (var i = 0; i < dates.Count; i++)
{
if (dateRange == null)
{
dateRange = new DateRange { Start = dates[i] };
}
if (i == dates.Count - 1 || dates[i].Date.AddDays(1) != dates[i + 1].Date)
{
dateRange.End = dates[i].Date;
dateRangeList.Add(dateRange);
dateRange = null;
}
}
return dateRangeList;
}
[TestMethod]
public void GetDateRanges_MultiDateRangeTest()
{
var dates = new List<DateTime>
{
new DateTime(1999,5,1),
new DateTime(1999,5,5),
new DateTime(1999,5,6),
new DateTime(1999,5,15),
new DateTime(1999,5,7),
new DateTime(1999,5,8),
new DateTime(1999,5,19),
new DateTime(1999,5,20),
new DateTime(1999,5,23)
};
var dateRanges = GetDateRanges(dates);
Assert.AreEqual(new DateTime(1999, 5, 1), dateRanges[0].Start);
Assert.AreEqual(new DateTime(1999, 5, 1), dateRanges[0].End);
Assert.AreEqual(new DateTime(1999, 5, 5), dateRanges[1].Start);
Assert.AreEqual(new DateTime(1999, 5, 8), dateRanges[1].End);
Assert.AreEqual(new DateTime(1999, 5, 15), dateRanges[2].Start);
Assert.AreEqual(new DateTime(1999, 5, 15), dateRanges[2].End);
Assert.AreEqual(new DateTime(1999, 5, 19), dateRanges[3].Start);
Assert.AreEqual(new DateTime(1999, 5, 20), dateRanges[3].End);
Assert.AreEqual(new DateTime(1999, 5, 23), dateRanges[4].Start);
Assert.AreEqual(new DateTime(1999, 5, 23), dateRanges[4].End);
}
}