How to Linq query group by 2 hours interval - linq

I would like to query such that, the data is grouped by time (SQL time data type, not datetime), with 2 hours interval.
For example, the result would be something like this:
+---------------+-------+
| Time Group | Count |
+---------------+-------+
| 00:01 - 02:00 | 3 |
| 02:01 - 04:00 | 5 |
| 04:01 - 06:00 | 8 |
| ... | ... |
| 22:01 - 24:00 | 2 |
+---------------+-------+
Note that the time group is predefined.
I managed to have a naive query like this:
var temp = from a in myTable select a;
var result0To2 = temp.Where(a => a.activityTime.Hours > 0 && a.activityTime.Hours <= 2).Count();
var result2To4 = temp.Where(a => a.activityTime.Hours > 2 && a.activityTime.Hours <= 4).Count();
var result4To6 = temp.Where(a => a.activityTime.Hours > 4 && a.activityTime.Hours <= 6).Count();
...
...
var result20To24 = temp.Where(a => a.activityTime.Hours > 22 && a.activityTime.Hours <= 24).Count();
Which I think should work. But is there a better way?

Here is a test program I ran, you can adapt the code based on your specific requirements. It uses LINQ's GroupBy() predicate, and is based completely on this answer to a similar question (I changed it to check hours instead of minutes).
public class Temp
{
public DateTime activityTime { get; set; }
public object item_info { get; set; }
}
Of course you can use your own class, mine is just for testing.
List<Temp> items = new List<Temp> //populate with dummy data
{
new Temp { activityTime = DateTime.Parse("2017-12-11 11:43:00"), item_info = "something1" },
new Temp { activityTime = DateTime.Parse("2017-12-11 11:46:00"), item_info = "something2" },
new Temp { activityTime = DateTime.Parse("2017-12-11 11:57:00"), item_info = "something3" },
new Temp { activityTime = DateTime.Parse("2017-12-11 12:02:00"), item_info = "something4" },
new Temp { activityTime = DateTime.Parse("2017-12-11 12:04:00"), item_info = "something5" },
new Temp { activityTime = DateTime.Parse("2017-12-11 12:06:00"), item_info = "something6" },
new Temp { activityTime = DateTime.Parse("2017-12-11 12:58:00"), item_info = "something7" },
new Temp { activityTime = DateTime.Parse("2017-12-11 13:05:00"), item_info = "something8" },
new Temp { activityTime = DateTime.Parse("2017-12-11 13:29:00"), item_info = "something9" },
new Temp { activityTime = DateTime.Parse("2017-12-11 14:53:00"), item_info = "something10" },
new Temp { activityTime = DateTime.Parse("2017-12-11 14:55:00"), item_info = "something11" },
new Temp { activityTime = DateTime.Parse("2017-12-11 14:59:00"), item_info = "something12" },
new Temp { activityTime = DateTime.Parse("2017-12-11 14:59:00"), item_info = 13 },
new Temp { activityTime = DateTime.Parse("2017-12-11 15:26:00"), item_info = 15 }
};
var groups = items.GroupBy(x =>
{
var stamp = x.activityTime;
stamp = stamp.AddMinutes(-(stamp.Minute));
stamp = stamp.AddMilliseconds(-stamp.Millisecond - 1000 * stamp.Second);
return stamp;
})
.Select(g => new { TimeGroup = g.Key.ToString("h tt") + " - " + g.Key.AddHours(1).ToString("h tt"), Value = g.Count() })
.ToList();
To change the grouping, you can modify the GroupBy() predicate.
The TimeGroup variable can be changed based on what display you need (date formatting, or you can add 1 minute like in your case to have "02:01 - 04:00".
For an interval of 2 hours:
var groups = items.GroupBy(x =>
{
var stamp = x.activityTime;
stamp = stamp.AddHours(-(stamp.Hour % 2));
stamp = stamp.AddMinutes(-(stamp.Minute));
stamp = stamp.AddMilliseconds(-stamp.Millisecond - 1000 * stamp.Second);
return stamp;
})
.Select(g => new { TimeGroup = g.Key.ToString("h tt") + " - " + g.Key.AddHours(2).ToString("h tt"), Value = g.Count() })
.ToList();
Warning, this operation may be time-heavy for larger datasets.

Related

I want to Group questions and answers LINQ and have them display properly

I'm trying to group properly to have my cell distribute eveningly. The printout is coming very odd and uneven.
is it the table i'm creating or group or both? I think the group is correct. My results are as shown in the image below.
Gold is the heading,
Green are the questions,
Red are the answers
Mt table is is below
var Sections = new OncologySection().SelectSections(projectID.ToString());
int iSection = 0;
int igroups = 0;
int ianswer = 0;
tb.CssClass = "";
tb.BorderWidth = 1;
tb.Width = new Unit("780px");
tb.Attributes.Add("runat", "server");
foreach (OncologySection section in Sections)
{
TableRow row1 = new TableRow();
iSection++;
// var getDistinctQuestion = getVoterAnswerstoList.Select(s => s.QuestionText ,s.Id).Distinct().ToList();
var getVoterAnswerstoList = new OncologyGeneratePDFDAL().DataforPDFCreation(Convert.ToInt32(projectID), Convert.ToInt32(voterid), Convert.ToInt32(caseId), Convert.ToInt32(voteSurveyId), Convert.ToInt32(section.SectionID)).OrderBy(os => os.SortOrder);
//var groupedCustomerList = getVoterAnswerstoList
// .GroupBy(u => u.QuestionText, u.QuestionText)
// .Select(grp => grp.ToList())
// .ToList();
var groupedCustomerList = getVoterAnswerstoList.GroupBy(x => new { x.QuestionText, x.DynamicValue }).ToList();
TableCell cell1 = new TableCell();
cell1.BorderWidth = 1;
cell1.Text = section.SectionName;
cell1.BorderColor = System.Drawing.Color.Goldenrod;
cell1.ColumnSpan = groupedCustomerList.Count();
row1.Cells.Add(cell1);
tb.Rows.Add(row1);
TableRow row2 = new TableRow();
foreach (var groups in groupedCustomerList)
{
igroups++;
TableCell cell2 = new TableCell();
var q = (from s in groups select s.QuestionText).FirstOrDefault();
cell2.BorderWidth = 1;
cell2.Text = q;
cell2.BorderColor = System.Drawing.Color.Green;
cell2.ColumnSpan = groupedCustomerList.Count();
row2.Cells.Add(cell2);
if (igroups == groupedCustomerList.Count())
{
tb.Rows.Add(row2);
}
else
{
row2.Cells.Add(cell2);
}
TableRow row3 = new TableRow();
foreach (var answers in groups)
{
ianswer++;
TableCell cell3 = new TableCell();
cell3.BorderWidth = 1;
cell3.BorderColor = System.Drawing.Color.DarkRed;
if (answers.DataTypeId == 7)
{
cell3.Text = answers.DynamicValue.ToString();
}
else if ((answers.DataTypeId == 5) || (answers.DataTypeId == 6) || (answers.DataTypeId == 8))
{
if (answers.VotingValue != 0)
{
cell3.Text = answers.VotingValue.ToString();
}
else
{
cell3.Text = " ";
}
}
else
{
cell3.Text = " ";
}
row3.Cells.Add(cell3);
tb.Rows.Add(row3);
}
}
}
}

get sum from list of objects in linq C#

I have list of objects as described below:
List<Maths> mObjs = new List<Maths>();
mObjs.Add(new Maths{ Name = "Jack", M1 = 10, M2 = 5, M3 = 0, M4 = 2, M5 =1 });
mObjs.Add(new Maths { Name = "Jill", M1 = 2, M2 = 3, M3 = 4, M4 = 1, M5 = 0 });
mObjs.Add(new Maths { Name = "Michel", M1 = 12, M2 = 15, M3 = 10, M4 = 12, M5 = 11 });
Now I need to calculated the total aggregated value for all three people.
I need to get the below results, probably a new other class
List<Results> mRes = new List<Results>();
public class Results{
public string Name { get; set; }
public int TotalValue { get; set; }
}
mRes.Name = "M1"
mRes.TotalValue = 24;
mRes.Name = "M2"
mRes.TotalValue = 23;
mRes.Name = "M3"
mRes.TotalValue = 14;
mRes.Name = "M4"
mRes.TotalValue = 15;
mRes.Name = "M5"
mRes.TotalValue = 12;
How can I get this data from mObjs using linq query? I know we can do it using for, but want to know if there are any better ways to get this using linq query because that reduces lines of code and I have similar requirements in many other places and dont want to write number of foreach or fors every time.
You can use a pre selection list to list both the name and the field to select
var lookups = new Dictionary<string,Func<Maths,int>> {
{"M1", x => x.M1 },
{"M2", x => x.M2 },
{"M3", x => x.M3 },
{"M4", x => x.M4 },
{"M5", x => x.M5 },
};
Then you can simply do
var mRes = dlookups.Select(x => new Results {
Name= x.Key,
TotalValue = mObjs.Sum(x.Value)
}).ToList();
BEGIN UPDATED*
In response to comments
The lambda expression is just a function from your source class to an int.
For example
class Sub1 {
string M3 {get;set;}
int M4 {get;set;}
}
class Math2 {
string Name {get;set;}
string M1 {get;set;}
string M2 {get;set;}
Sub1 Sub {get;set;}
}
var lookups = new Dictionary<string,Func<Math2,int>> {
{ "M1", x => int.Parse(x.M1) },
{ "M2", x => int.Parse(x.M2) },
{ "M3", x => int.Parse(x.Sub.M3) },
{ "M4", x => int.Parse(x.Sub.M4} }
};
Or if you want to put a little error checking in, you can either use functions or embed the code.
int GetInt(string source) {
if (source == null) return 0;
int result;
return int.TryParse(source, out result) ? result : 0;
}
var lookups = new Dictionary<string,Func<Math2,int>> {
{ "M1", x => {
int result;
return x == null ? 0 : (int.TryParse(x,out result) ? result : 0);
},
{ "M2", x => GetInt(x) },
{ "M3", x => x.Sub == null ? 0 : GetInt(x.Sub.M3) },
{ "M4", x => x.Sub == null ? 0 : x.Sub.M4}
};
END UPDATED
If you want to go further you could use reflection to build the lookups dictionary.
Here is a helper function that will generate the lookups for all Integer properties of a class.
public Dictionary<string,Func<T,int>> GenerateLookups<T>() where T: class {
// This just looks for int properties, you could add your own filter
var properties = typeof(T).GetProperties().Where(pi => pi.PropertyType == typeof(int));
var parameter = Expression.Parameter(typeof(T));
return properties.Select(x => new {
Key = x.Name,
Value = Expression.Lambda<Func<T,int>>(Expression.Property(parameter,x),parameter).Compile()
}).ToDictionary (x => x.Key, x => x.Value);
}
Now you can just do:
var mRes=GenerateLookups<Maths>().Select( x => new Results
{
Name = x.Key,
TotalValue = mObjs.Sum(x.Value)
}).ToList();
Not very smart but efficient and readable:
int m1Total= 0;
int m2Total= 0;
int m3Total= 0;
int m4Total= 0;
int m5Total= 0;
foreach(Maths m in mObjs)
{
m1Total += m.M1;
m2Total += m.M2;
m3Total += m.M3;
m4Total += m.M4;
m5Total += m.M5;
}
List<Results> mRes = new List<Results>
{
new Results{ Name = "M1", TotalValue = m1Total },
new Results{ Name = "M2", TotalValue = m2Total },
new Results{ Name = "M3", TotalValue = m3Total },
new Results{ Name = "M4", TotalValue = m4Total },
new Results{ Name = "M5", TotalValue = m5Total },
};
Result:
Name: "M1" TotalValue: 24
Name: "M2" TotalValue: 23
Name: "M3" TotalValue: 14
Name: "M4" TotalValue: 15
Name: "M5" TotalValue: 12
Edit: since you've explicitly asked for LINQ, if the properties are always these five i don't see why you need to use LINQ at all. If the number can change i would use a different structure.
You could for example use
a single List<Measurement> instead of multiple properties where Measurement is another class that stores the name and the value or you could use
a Dictionary<string, int> for efficient lookup.
You can try out some thing like this :
mRes.Add(new Results() { Name = "M1", TotalValue = mObjs.Sum(x => x.M1) });
To programmatically iterate through all the class properties, you might need to employ reflection.

Put value in datatable group by week

I have a datatable which comprises values from 1st January to march, something like this:
DATE Employer Job1 Job2
1/4/2013 A 1.3 2
1/4/2013 B 2.5 6
1/6/2013 C 3.7 2.4
1/7/2013 D 11
1/7/2013 F 334 0
1/8/2013 A 1.87 1
1/8/2013 B 6.85 2
1/9/2013 C 58 226
1/16/2013 A 9.43 1.45
1/16/2013 B 5.27 0.6
1/122/2013 C 45.4 5
1/23/2013 A 44 4.78
1/29/2013 B 45 40
2/2/2013 C 45 54.12
2/2/2013 D 7 4.4587
2/3/2013 F 265 11.486
Update:
DataTable datatable = new DataTable("Employee");
datatable.Columns.Add("Date", typeof(string));
datatable.Columns.Add("Employee", typeof(string));
datatable.Columns.Add("Job1", typeof(double));
datatable.Columns.Add("Job2", typeof(double));
datatable.Rows.Add(new Object[] { "1/4/2013", "A", 1.3, 2 });
datatable.Rows.Add(new Object[] { "1/4/2013", "B", 2.5, 6 });
datatable.Rows.Add(new Object[] { "1/6/2013", "C", 3.7, 2.4 });
datatable.Rows.Add(new Object[] { "1/7/2013", "D", 11, 0.0 });
datatable.Rows.Add(new Object[] { "1/7/2013", "F", 334, 0 });
datatable.Rows.Add(new Object[] { "1/8/2013", "A", 1.87, 1 });
datatable.Rows.Add(new Object[] { "1/8/2013", "B", 6.85, 2 });
datatable.Rows.Add(new Object[] { "1/9/2013", "C", 58, 226 });
datatable.Rows.Add(new Object[] { "1/16/2013", "A", 9.43, 1.45 });
datatable.Rows.Add(new Object[] { "1/16/2013", "B", 5.27, 0.6 });
datatable.Rows.Add(new Object[] { "1/22/2013", "C", 45.4, 5 });
datatable.Rows.Add(new Object[] { "1/23/2013", "A", 44, 4.78 });
datatable.Rows.Add(new Object[] { "1/29/2013", "B", 45, 40 });
datatable.Rows.Add(new Object[] { "2/2/2013", "C", 45, 54.12 });
datatable.Rows.Add(new Object[] { "2/2/2013", "D", 7, 4.4587 });
datatable.Rows.Add(new Object[] { "2/3/2013", "F", "265", 11.486 });
datatable.Rows.Add(new Object[] { "3/3/2013", "A", "25", 28.124 });
I want to sum the values for job1 week wise where week starts from Monday to Sunday. This is the code I have written so far.
DateTime minDate = datatable.AsEnumerable()
.Min(r => DateTime.Parse(r.Field<string>("DATE")));
DateTime startDate = minDate.Date.Date.AddDays(+((6 + minDate.DayOfWeek
- DayOfWeek.Monday) % 7));
DateTime nextDate = startDate.AddDays(6);
DateTime maxDate = datatable.AsEnumerable()
.Max(r => DateTime.Parse(r.Field<string>("DATE")));
while (nextDate < maxDate)
{
var weekEmpGroups = datatable.AsEnumerable()
.Select(r => new
{
Row = r,
Employee = r.Field<String>("Employee"),
Date = DateTime.Parse(r.Field<string>("DATE"))
// week = minDate.Date.Date.AddDays(+((6 + minDate.DayOfWeek
- DayOfWeek.Monday) % 7))
})
.GroupBy(x => x.Employee);
DataTable dtWeeklyResults = new DataTable();
dtWeeklyResults.Columns.Add("Employee", typeof(string));
var dtf = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat;
double weekCount = 0.0;
string expression;
DataRow[] foundRows;
foreach (var empGroup in weekEmpGroups)
{
string employee = empGroup.Key;
var newRow = dtWeeklyResults.Rows.Add();
newRow["Employee"] = employee;
expression = "Employee=" + employee + " AND Date Between " + startDate
+ " And " + nextDate;
foundRows = datatable.Select(expression);
if (foundRows.Length > 0)
{
// add values using linq
}
}
Please suggest if this is correct way to do this and also how to add all values week wise? The result should look like this for Job1:
Employee 1/7-1/13 1/14-1/20 1/21-1/27 1/28-2/3 and so on...
A sum of values for this 7 days
B
C
D
Can anybody suggest how to achieve this by LINQ?
Helper methods
private static string GetColumnName(int weekNumber)
{
DateTime jan1 = new DateTime(2013, 1, 1);
int daysOffset = DayOfWeek.Monday - jan1.DayOfWeek;
DateTime firstMonday = jan1.AddDays(daysOffset);
var cal = ci.Calendar;
int firstWeek = cal.GetWeekOfYear(firstMonday, ci.DateTimeFormat.CalendarWeekRule, ci.DateTimeFormat.FirstDayOfWeek);
if (firstWeek <= 1)
{
weekNumber -= 1;
}
DateTime result = firstMonday.AddDays((weekNumber-1) * 7);
return string.Format("{0}-{1}", result.ToString("M/d", ci), result.AddDays(6).ToString("M/d", ci));
}
private static int GetWeekOfYear(DateTime value)
{
return ci.Calendar.GetWeekOfYear(value, ci.DateTimeFormat.CalendarWeekRule, ci.DateTimeFormat.FirstDayOfWeek);
}
CultureInfo instance
static CultureInfo ci = new CultureInfo("en-us");
Logic
// load parsed data from DataTable to a list
var data = (from row in dt.AsEnumerable()
select new
{
Date = DateTime.Parse(row.Field<string>("Date"), ci),
Employee = row.Field<string>("Employee"),
Value = row.Field<double>("Job1")
}).ToList();
// find min/max date and week number
var minDateTime = data.Select(i => i.Date).Min();
var maxDateTime = data.Select(i => i.Date).Max();
var minWeekNumber = GetWeekOfYear(minDateTime);
var maxWeekNumber = GetWeekOfYear(maxDateTime);
// prepare result DataTable
var resultDt = new DataTable("Job1");
resultDt.Columns.Add("Employee", typeof(string));
for (int i = minWeekNumber; i <= maxWeekNumber; i++)
resultDt.Columns.Add(i.ToString(), typeof(double));
// prepare grouped data query
var employeeData = from d in data
group d by d.Employee into g
select new
{
Employee = g.Key,
Items = g.GroupBy(x => GetWeekOfYear(x.Date))
.Select(x => new
{
Week = x.Key,
Value = x.Sum(xx => xx.Value)
})
};
// iterate over query results and fill resultsDt
foreach (var e in employeeData)
{
var newRow = resultDt.NewRow();
newRow["Employee"] = e.Employee;
foreach (var d in e.Items)
newRow[d.Week.ToString()] = d.Value;
resultDt.Rows.Add(newRow);
}
// change column names from week numbers to proper start-end dates
foreach(DataColumn col in resultDt.Columns)
{
int weekNumber;
if (int.TryParse(col.ColumnName, out weekNumber))
col.ColumnName = GetColumnName(weekNumber);
}
Results:
Job1
Employee 1/7-1/13 1/14-1/20 1/21-1/27 1/28-2/3 2/4-2/10 2/11-2/17 2/18-2/24 2/25-3/3 3/4-3/10 3/11-3/
A 1,3 1,87 9,43 44 2
B 2,5 6,85 5,27 45
C 61,7 45,4 45
D 11 7
F 334 265
If anybody have the same requirement that is to show the results week wise then here is the code:
private static DataTable GetWeeklyColumnsAndData(DataTable datatable, string resultFor)
{
DateTime minDate = datatable.AsEnumerable()
.Min(r => DateTime.Parse(r.Field<string>("DATE")));
DateTime maxDate = datatable.AsEnumerable()
.Max(r => DateTime.Parse(r.Field<string>("DATE")));
var distinctValues = datatable.AsEnumerable()
.Select(row => new
{
Employee = row.Field<string>("Employee")
})
.Distinct()
.ToList();
int totalEmployeeCount = System.Linq.Enumerable.Count(distinctValues);
DataTable resultDt = new DataTable();
resultDt.Columns.Add("Employee", typeof(string));
DateTime firstMonday = (minDate.DayOfWeek == DayOfWeek.Monday) ? minDate : GetNextWeekday(minDate, DayOfWeek.Monday);
DateTime startingMonday = firstMonday;
// add columns first
while (firstMonday < maxDate)
{
string weekName = string.Format("{0}-{1}", firstMonday.ToString("M/d", ci), firstMonday.AddDays(6).ToString("M/d", ci));
resultDt.Columns.Add(weekName, typeof(string));
firstMonday = firstMonday.AddDays(7);
}
for (int row = 0; row < totalEmployeeCount; row++)
{
DateTime startDate = startingMonday;
DateTime endDate = startingMonday.AddDays(6);
DataRow newRow = resultDt.NewRow();
string employee = distinctValues[row].Employee.ToString();
// first column for entity
newRow[0] = employee;
for (int col = 1; col < resultDt.Columns.Count; col++)
{
bool isBlank = false;
double total = 0;
string formattedMonday = endDate.ToString("M/d/yyyy");
string expression = String.Format("Employee = '{0}' AND DATE >= #{1}# AND DATE <= #{2}#", employee, startDate.ToString("M/d/yyyy"), formattedMonday);
DataView dv = datatable.DefaultView;
dv.RowFilter = expression;
if (dv.Count > 0)
{
foreach (DataRowView rowView in dv)
{
DataRow r = rowView.Row;
string value = r[resultFor].ToString();
if (value != "")
{
total += Convert.ToDouble(value);
}
else
{
isBlank = true;
}
}
}
else
{
isBlank = true;
}
if (total == 0 && isBlank)
{
newRow[col] = "";
}
else
{
newRow[col] = total;
}
startDate = endDate.AddDays(1);
endDate = startDate.AddDays(6);
}
resultDt.Rows.Add(newRow);
}
return resultDt;
}
public static DateTime GetNextWeekday(DateTime start, DayOfWeek day)
{
// The (... + 7) % 7 ensures we end up with a value in the range [0, 6]
int daysToAdd = ((int)day - (int)start.DayOfWeek + 7) % 7;
return start.AddDays(daysToAdd);
}

PIVOT with LINQ from Datatable [duplicate]

I have a collection of items that contain an Enum (TypeCode) and a User object, and I need to flatten it out to show in a grid. It's hard to explain, so let me show a quick example.
Collection has items like so:
TypeCode | User
---------------
1 | Don Smith
1 | Mike Jones
1 | James Ray
2 | Tom Rizzo
2 | Alex Homes
3 | Andy Bates
I need the output to be:
1 | 2 | 3
Don Smith | Tom Rizzo | Andy Bates
Mike Jones | Alex Homes |
James Ray | |
I've tried doing this using foreach, but I can't do it that way because I'd be inserting new items to the collection in the foreach, causing an error.
Can this be done in Linq in a cleaner fashion?
I'm not saying it is a great way to pivot - but it is a pivot...
// sample data
var data = new[] {
new { Foo = 1, Bar = "Don Smith"},
new { Foo = 1, Bar = "Mike Jones"},
new { Foo = 1, Bar = "James Ray"},
new { Foo = 2, Bar = "Tom Rizzo"},
new { Foo = 2, Bar = "Alex Homes"},
new { Foo = 3, Bar = "Andy Bates"},
};
// group into columns, and select the rows per column
var grps = from d in data
group d by d.Foo
into grp
select new {
Foo = grp.Key,
Bars = grp.Select(d2 => d2.Bar).ToArray()
};
// find the total number of (data) rows
int rows = grps.Max(grp => grp.Bars.Length);
// output columns
foreach (var grp in grps) {
Console.Write(grp.Foo + "\t");
}
Console.WriteLine();
// output data
for (int i = 0; i < rows; i++) {
foreach (var grp in grps) {
Console.Write((i < grp.Bars.Length ? grp.Bars[i] : null) + "\t");
}
Console.WriteLine();
}
Marc's answer gives sparse matrix that can't be pumped into Grid directly.
I tried to expand the code from the link provided by Vasu as below:
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
return source.GroupBy(key1Selector).Select(
x => new
{
X = x.Key,
Y = source.GroupBy(key2Selector).Select(
z => new
{
Z = z.Key,
V = aggregate(from item in source
where key1Selector(item).Equals(x.Key)
&& key2Selector(item).Equals(z.Key)
select item
)
}
).ToDictionary(e => e.Z, o => o.V)
}
).ToDictionary(e => e.X, o => o.Y);
}
internal class Employee
{
public string Name { get; set; }
public string Department { get; set; }
public string Function { get; set; }
public decimal Salary { get; set; }
}
public void TestLinqExtenions()
{
var l = new List<Employee>() {
new Employee() { Name = "Fons", Department = "R&D", Function = "Trainer", Salary = 2000 },
new Employee() { Name = "Jim", Department = "R&D", Function = "Trainer", Salary = 3000 },
new Employee() { Name = "Ellen", Department = "Dev", Function = "Developer", Salary = 4000 },
new Employee() { Name = "Mike", Department = "Dev", Function = "Consultant", Salary = 5000 },
new Employee() { Name = "Jack", Department = "R&D", Function = "Developer", Salary = 6000 },
new Employee() { Name = "Demy", Department = "Dev", Function = "Consultant", Salary = 2000 }};
var result5 = l.Pivot3(emp => emp.Department, emp2 => emp2.Function, lst => lst.Sum(emp => emp.Salary));
var result6 = l.Pivot3(emp => emp.Function, emp2 => emp2.Department, lst => lst.Count());
}
* can't say anything about the performance though.
You can use Linq's .ToLookup to group in the manner you are looking for.
var lookup = data.ToLookup(d => d.TypeCode, d => d.User);
Then it's a matter of putting it into a form that your consumer can make sense of. For instance:
//Warning: untested code
var enumerators = lookup.Select(g => g.GetEnumerator()).ToList();
int columns = enumerators.Count;
while(columns > 0)
{
for(int i = 0; i < enumerators.Count; ++i)
{
var enumerator = enumerators[i];
if(enumator == null) continue;
if(!enumerator.MoveNext())
{
--columns;
enumerators[i] = null;
}
}
yield return enumerators.Select(e => (e != null) ? e.Current : null);
}
Put that in an IEnumerable<> method and it will (probably) return a collection (rows) of collections (column) of User where a null is put in a column that has no data.
I guess this is similar to Marc's answer, but I'll post it since I spent some time working on it. The results are separated by " | " as in your example. It also uses the IGrouping<int, string> type returned from the LINQ query when using a group by instead of constructing a new anonymous type. This is tested, working code.
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 1, UserName = "Mike Jones"},
new { TypeCode = 1, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
var Columns = from i in Items
group i.UserName by i.TypeCode;
Dictionary<int, List<string>> Rows = new Dictionary<int, List<string>>();
int RowCount = Columns.Max(g => g.Count());
for (int i = 0; i <= RowCount; i++) // Row 0 is the header row.
{
Rows.Add(i, new List<string>());
}
int RowIndex;
foreach (IGrouping<int, string> c in Columns)
{
Rows[0].Add(c.Key.ToString());
RowIndex = 1;
foreach (string user in c)
{
Rows[RowIndex].Add(user);
RowIndex++;
}
for (int r = RowIndex; r <= Columns.Count(); r++)
{
Rows[r].Add(string.Empty);
}
}
foreach (List<string> row in Rows.Values)
{
Console.WriteLine(row.Aggregate((current, next) => current + " | " + next));
}
Console.ReadLine();
I also tested it with this input:
var Items = new[] {
new { TypeCode = 1, UserName = "Don Smith"},
new { TypeCode = 3, UserName = "Mike Jones"},
new { TypeCode = 3, UserName = "James Ray"},
new { TypeCode = 2, UserName = "Tom Rizzo"},
new { TypeCode = 2, UserName = "Alex Homes"},
new { TypeCode = 3, UserName = "Andy Bates"}
};
Which produced the following results showing that the first column doesn't need to contain the longest list. You could use OrderBy to get the columns ordered by TypeCode if needed.
1 | 3 | 2
Don Smith | Mike Jones | Tom Rizzo
| James Ray | Alex Homes
| Andy Bates |
#Sanjaya.Tio I was intrigued by your answer and created this adaptation which minimizes keySelector execution. (untested)
public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
this IEnumerable<TSource> source
, Func<TSource, TKey1> key1Selector
, Func<TSource, TKey2> key2Selector
, Func<IEnumerable<TSource>, TValue> aggregate)
{
var lookup = source.ToLookup(x => new {Key1 = key1Selector(x), Key2 = key2Selector(x)});
List<TKey1> key1s = lookup.Select(g => g.Key.Key1).Distinct().ToList();
List<TKey2> key2s = lookup.Select(g => g.Key.Key2).Distinct().ToList();
var resultQuery =
from key1 in key1s
from key2 in key2s
let lookupKey = new {Key1 = key1, Key2 = key2}
let g = lookup[lookupKey]
let resultValue = g.Any() ? aggregate(g) : default(TValue)
select new {Key1 = key1, Key2 = key2, ResultValue = resultValue};
Dictionary<TKey1, Dictionary<TKey2, TValue>> result = new Dictionary<TKey1, Dictionary<TKey2, TValue>>();
foreach(var resultItem in resultQuery)
{
TKey1 key1 = resultItem.Key1;
TKey2 key2 = resultItem.Key2;
TValue resultValue = resultItem.ResultValue;
if (!result.ContainsKey(key1))
{
result[key1] = new Dictionary<TKey2, TValue>();
}
var subDictionary = result[key1];
subDictionary[key2] = resultValue;
}
return result;
}

Optimize queries for Union, Except, Join with LINQ and C#

I have 2 objects (lists loaded from XML) report and database (showed bellow in code) and i should analyse them and mark items with 0, 1, 2, 3 according to some conditions
TransactionResultCode = 0; // SUCCESS (all fields are equivalents: [Id, AccountNumber, Date, Amount])
TransactionResultCode = 1; // Exists in report but Not in database
TransactionResultCode = 2; // Exists in database but Not in report
TransactionResultCode = 3; // Field [Id] are equals but other fields [AccountNumber, Date, Amount] are different.
I'll be happy if somebody could found time to suggest how to optimize some queries.
Bellow is the code:
THANK YOU!!!
//TransactionResultCode = 0 - SUCCESS
//JOIN on all fields
var result0 = from d in database
from r in report
where (d.TransactionId == r.MovementID) &&
(d.TransactionAccountNumber == long.Parse(r.AccountNumber)) &&
(d.TransactionDate == r.MovementDate) &&
(d.TransactionAmount == r.Amount)
orderby d.TransactionId
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount,
TransactionResultCode = 0
};
//*******************************************
//JOIN on [Id] field
var joinedList = from d in database
from r in report
where d.TransactionId == r.MovementID
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount
};
//Difference report - database
var onlyReportID = report.Select(r => r.MovementID).Except(joinedList.Select(d => d.TransactionId));
//TransactionResultCode = 1 - Not Found in database
var result1 = from o in onlyReportID
from r in report
where (o == r.MovementID)
orderby r.MovementID
select new TransactionList()
{
TransactionId = r.MovementID,
TransactionAccountNumber = long.Parse(r.AccountNumber),
TransactionDate = r.MovementDate,
TransactionAmount = r.Amount,
TransactionResultCode = 1
};
//*******************************************
//Difference database - report
var onlyDatabaseID = database.Select(d => d.TransactionId).Except(joinedList.Select(d => d.TransactionId));
//TransactionResultCode = 2 - Not Found in report
var result2 = from o in onlyDatabaseID
from d in database
where (o == d.TransactionId)
orderby d.TransactionId
select new TransactionList()
{
TransactionId = d.TransactionId,
TransactionAccountNumber = d.TransactionAccountNumber,
TransactionDate = d.TransactionDate,
TransactionAmount = d.TransactionAmount,
TransactionResultCode = 2
};
//*******************************************
var qwe = joinedList.Select(j => j.TransactionId).Except(result0.Select(r => r.TransactionId));
//TransactionResultCode = 3 - Transaction Results are different (Amount, AccountNumber, Date, )
var result3 = from j in joinedList
from q in qwe
where j.TransactionId == q
select new TransactionList()
{
TransactionId = j.TransactionId,
TransactionAccountNumber = j.TransactionAccountNumber,
TransactionDate = j.TransactionDate,
TransactionAmount = j.TransactionAmount,
TransactionResultCode = 3
};
you may try something like below:
public void Test()
{
var report = new[] {new Item(1, "foo", "boo"), new Item(2, "foo2", "boo2"), new Item(3, "foo3", "boo3")};
var dataBase = new[] {new Item(1, "foo", "boo"), new Item(2, "foo22", "boo2"), new Item(4, "txt", "rt")};
Func<Item, bool> inBothLists = (i) => report.Contains(i) && dataBase.Contains(i);
Func<IEnumerable<Item>, Item, bool> containsWithID = (e, i) => e.Select(_ => _.ID).Contains(i.ID);
Func<Item, int> getCode = i =>
{
if (inBothLists(i))
{
return 0;
}
if(containsWithID(report, i) && containsWithID(dataBase, i))
{
return 3;
}
if (report.Contains(i))
{
return 2;
}
else return 1;
};
var result = (from item in dataBase.Union(report) select new {Code = getCode(item), Item = item}).Distinct();
}
public class Item
{
// You need also to override Equals() and GetHashCode().. I omitted them to save space
public Item(int id, string text1, string text2)
{
ID = id;
Text1 = text1;
Text2 = text2;
}
public int ID { get; set; }
public string Text1 { get; set; }
public string Text2 { get; set; }
}
Note that you need to either implement Equals() for you items, or implement an IEqualityComparer<> and feed it to Contains() methods.

Resources