I stumbled upon a scenario where I have to group a w.r.t to multiple set of columns. I was able to solve it but then came the trouble. The rows were supposed to be grouped only if the grouping key satisfied a condition.
I have put across a sample scenario to display an Employees Roles for various projects.
public class EmployeeRoleDept
{
public int RoleId { get; set; }
public string RoleName { get; set; }
public int DeptartmentId { get; set; }
public string DeptartmentName { get; set; }
public int ProjectId { get; set; }
public string ProjectName { get; set; }
}
var listEmployee = new List<EmployeeRoleDept>
{
new EmployeeRoleDept{ RoleId = 1 ,RoleName="CEO",DeptartmentId = 1 , DeptartmentName = string.Empty, ProjectId = 1,ProjectName=string.Empty},
new EmployeeRoleDept{ RoleId = 2, RoleName="PM", DeptartmentId = 2, DeptartmentName = "Dept A", ProjectId = 2,ProjectName="Project 2"},
new EmployeeRoleDept{ RoleId = 2, RoleName="PM", DeptartmentId = 2 ,DeptartmentName = "Dept A", ProjectId = 3,ProjectName="Project 3"},
new EmployeeRoleDept{ RoleId = 2, RoleName="PM", DeptartmentId = 3, DeptartmentName = "Dept B", ProjectId = 4,ProjectName="Project 4"},
new EmployeeRoleDept{ RoleId = 2, RoleName="PM", DeptartmentId = 3 ,DeptartmentName = "Dept B", ProjectId = 5,ProjectName="Project 5"},
new EmployeeRoleDept{ RoleId = 3, RoleName="Dev",DeptartmentId = 4 ,DeptartmentName = "Dept C", ProjectId = 6,ProjectName="Project 6"},
new EmployeeRoleDept{ RoleId = 3, RoleName="Dev",DeptartmentId = 4 ,DeptartmentName = "Dept C", ProjectId = 7,ProjectName="Project 7"},
new EmployeeRoleDept{ RoleId = 3, RoleName="Dev",DeptartmentId = 5 ,DeptartmentName = "Dept D", ProjectId = 8,ProjectName="Project 8"},
new EmployeeRoleDept{ RoleId = 4, RoleName="Tester",DeptartmentId = 4,DeptartmentName = "Dept C" , ProjectId = 6,ProjectName="Project 6"},
new EmployeeRoleDept{ RoleId = 4, RoleName="Tester",DeptartmentId = 6,DeptartmentName = "Dept E" , ProjectId = 9,ProjectName="Project 9"},
};
And the grid to be obtained should be something like this
| RoleName | Dept Name | Project |
----------------------------------------------
| CEO | | |
| PM | Dept A | |
| PM | Dept B | |
| Dev | Dept C | Project 6 |
| Dev | Dept C | Project 7 |
| Dev | Dept D | Project 8 |
| Tester | Dept C | Project 6 |
| Tester | Dept E | Project 9 |
If the Employee is the Project Manager (PM) of a Department, he would be the PM of all the projects under the Department. So the project name need not be shown in the Grid.
So I guess we have to group it w.r.t to the RoleId if an only if he is the PM.
I tried to group them with RoleId just as 2
var grouplist = listEmployee.GroupBy(x => x.RoleId==2).ToList();
But I am just getting a group with just a role of PM and the rest of the roles in the second group.
The GroupBy method takes a function that returns a value to group by. Because you're returning a boolean expression you are grouping the data into two groups: true (RoleId == 2), and false (everyone else).
You might not need to do a group by here. This should give you the data you're looking for...
var summarizedList = listEmployee
.Select(e => new {
RoleName = e.RoleName,
DeptName = e.DeptartmentName,
Project = (e.RoleId == 2 ? string.Empty : e.ProjectName)
})
.Distinct()
.ToList();
Related
I have a table:
Name | account info| AccountNumber
-----| ------------| ------
abc | IT | 3000
bdc | Desk | 2000
sed | Kitchen | 3000
afh | work | 4000
hsfs | home | 2000
I want to achieve something like this:
Name | account info| DisguiseInfo
-----| ------------| ------
abc | IT | Acc1
bdc | Desk | Acc2
sed | Kitchen | Acc1
afh | work | Acc3
hsfs | home | Acc2
I tried doing this:
int count = 1;
var disguise = listResults.GroupBy(x => x.ID).Select(y =>
y.First()).Distinct();
foreach (var i in disguise)
{
i.DisguiseName = "Acc " + count;
count++;
}
Which gives a results like this (very close to what I want):
Name | account info| DisguiseInfo
-----| ------------| ------
abc | IT | Acc1
bdc | Desk | Acc2
sed | Kitchen |
afh | work | Acc3
hsfs | home |
The problem with that is that, it doesn't give the ability to add the same string value 'Acc1' to the same duplicate value in the list, (the rest of the table comes blank only the fist values gets replaced), So how do I replace the entire value with matching IDs?
//EDIT
the data is being populated using sqlcommand in a class called SQLQuery, in this class there's a method called Account which execute like this:
SqlDataReader reader = command.ExecuteReader();
List<ViewModel> returnList = new List<ViewModel>();
if (reader.HasRows)
{
while (reader.Read())
{
ViewModel vm = new ViewModel();
vm.Name = reader.GetString(2);
vm.AccountInfo= reader.GetString(3);
vm.AccountNumber = reader.GetInt32(4);
returnList.Add(vm)
}
}
so this method return the first table above no issues.
In my controller action, is where I want to perhaps copy the SQLQuery return list into another list to filter so I'm doing (in the action method):
public async Task<IActionResult> DisguiseAction(string accNum)
{
List<ViewModel> executeSQL = new List<ViewModel>();
SQLQuery getQuery = new SQLQuery();
executeSQL = getQuery.Account(accNum); //at this point the sql
//gets executed with the correct value. Now I need to disguise the
//value. which I did
int count = 1;
var disguise = listResults.GroupBy(x => x.ID).Select(y =>
y.First()).Distinct();
foreach (var i in disguise)
{
i.DisguiseName = "Acc " + count;
count++;
}
}
Your problem is the .Distinct() call, that only takes the first element out of each group. Due to the fact, that you need to memoize all already seen values, it is easier to use a dictionary to hold the already mapped values. One possibility could be:
var accounts = new List<Account>
{
new Account { Name = "abc", Department = "IT", AccountInfo = 3000 },
new Account { Name = "bdc", Department = "Desk", AccountInfo = 2000 },
new Account { Name = "sed", Department = "Kitchen", AccountInfo = 3000 },
new Account { Name = "afh", Department = "work", AccountInfo = 4000 },
new Account { Name = "hsfs", Department = "home", AccountInfo = 2000 },
};
var mappings = new Dictionary<int, string>();
var summary = accounts
.Select(acc => new AccountSummary
{
Name = acc.Name,
Department = acc.Department,
DisguiseInfo = GetOrAddMapping(acc.AccountInfo, mappings)
})
.ToList();
foreach (var item in summary)
{
Console.WriteLine(JsonSerializer.Serialize(item));
}
And the helper method would in this case be:
private static string GetOrAddMapping(int accountInfo, Dictionary<int, string> mappings)
{
if (!mappings.TryGetValue(accountInfo, out var value))
{
value = $"Acc{mappings.Count + 1}";
mappings.Add(accountInfo, value);
}
return value;
}
using System;
using System.Collections.Generic;
public class Ent
{
public Ent(string a, string b, string c)
{
name = a;
location = b;
id = c;
}
public string name;
public string location;
public string id;
public override string ToString()
{
return $"{name} | {location} | {id}";
}
}
public class Program
{
public static void Main()
{
var input = new List<Ent>
{
new Ent("abc", "IT", "3000"),
new Ent("bcd", "Desk", "2000"),
new Ent("sed", "Kitchen", "3000"),
new Ent("afh", "work", "4000"),
new Ent("hsf", "home", "2000"),
};
var output = input
.GroupBy(x => x.id) // x is of type Ent
.SelectMany(y => // y is of type IGrouping<string, IEnumerable<Ent>>
y.Select(z => // z is of type Ent
new Ent(z.name, z.location, "Acc" + y.Key.Substring(0, 1))));
foreach(var line in output)
Console.WriteLine(line);
}
}
Gives an output that looks like:
abc | IT | Acc3
sed | Kitchen | Acc3
bcd | Desk | Acc2
hsf | home | Acc2
afh | work | Acc4
This code works using GroupBy on the id, then unroll the groups using SelectMany, but now we have the Key for each group. So when unrolling, re-create each line, but replace the id with a transformed value of the Key.
After grouping by AccountInfo, you could take advantage of the .SelectMany() overload that provides an indexer for the source element (i.e. an indexer for the AccountInfo values).
In the following example, I am assuming that you have two separate classes for the original (identifiable) accounts and the disguised accounts, e.g.:
public class BasicAccount
{
public string Name { get; set; }
public string AccountType { get; set; }
}
public class Account : BasicAccount
{
public int AccountInfo { get; set; }
}
public class DisguisedAccount : BasicAccount
{
public string DisguisedInfo { get; set; }
}
If your original accounts are collected in a variable List<Account> accounts as such:
List<Account> accounts = new()
{
new() { Name = "abc", AccountType = "IT", AccountInfo = 3000 },
new() { Name = "bdc", AccountType = "Desk", AccountInfo = 2000 },
new() { Name = "sed", AccountType = "Kitchen", AccountInfo = 3000 },
new() { Name = "afh", AccountType = "work", AccountInfo = 4000 },
new() { Name = "hsfs",AccountType = "home", AccountInfo = 2000 }
};
, your disguised accounts could be produced as follows:
IEnumerable<DisguisedAccount> disguisedAccounts = accounts
.GroupBy(a => a.AccountInfo)
.SelectMany(( accountsByInfo, counter ) => accountsByInfo
.Select(account => new DisguisedAccount
{
Name = account.Name,
AccountType = account.AccountType,
DisguisedInfo = $"Acc{counter}"
}));
Note: Using this approach, you lose the ordering given by the original accounts collection. The resulting collection is:
Name
AccountType
DisguisedInfo
abc
IT
Acc1
sed
Kitchen
Acc1
bdc
Desk
Acc2
hsfs
home
Acc2
afh
work
Acc3
Example fiddle here.
I'm using this code to group the rows by year, month and employeeId.
var dtrSummary = from dtr in db.DTRs
group dtr by new { dtr.Date.Year, dtr.Date.Month, dtr.EmployeeId } into g
select new
{
Year = g.Key.Year,
Month = g.Key.Month,
NoOfDays = g.Count(),
FullName = db.EmployeeRates.Where(e => e.Id == g.Key.EmployeeId).Select(e => e.FullName).FirstOrDefault(),
Time1 = // Get total hours between two datetime
// DateTime timein = new DateTime(year, month, day, tihh1, timm1, 0);
// DateTime timeout = new DateTime(year, month, day, tohh1, tomm1, 0);
// Timespan totalTime1 = timeout - timein;
};
But i don't know how to get total hours using linq group by like you see above.
This is my table:
+----------------------------------------------------------------------+
| Date | Timeinhh | Timeinmm | Timeouthh | Timeoutmm | EmployeeId |
+----------------------------------------------------------------------+
| 10/1/2015 | 9 | 0 | 18 | 0 | 1 |
| 10/2/2015 | 9 | 0 | 18 | 0 | 2 |
| 10/3/2015 | 9 | 0 | 18 | 0 | 1 |
| 10/4/2015 | 9 | 0 | 18 | 0 | 2 |
+----------------------------------------------------------------------+
You might want to calculate each hours before grouping DTRs.
var DTRs2 = db.DTRs.ToList().Select(d => new
{
Date = d.Date,
Hours = new DateTime(d.Date.Year, d.Date.Month, d.Date.Day, d.Timeouthh, d.Timeoutmm, 0)
.Subtract(new DateTime(d.Date.Year, d.Date.Month, d.Date.Day, d.Timeinhh, d.Timeinmm, 0)).TotalHours,
EmployeeId = d.EmployeeId
}).ToList();
var dtrSummary =
(from dtr in DTRs2
group dtr by new { dtr.Date.Year, dtr.Date.Month, dtr.EmployeeId } into g
select new
{
Year = g.Key.Year,
Month = g.Key.Month,
NoOfDays = g.Count(),
EmpId = g.Key.EmployeeId,
FullName = db.EmployeeRates.Where(e => e.Id == g.Key.EmployeeId).Select(e => e.FullName).FirstOrDefault(),
Time1 = g.Sum(dtr => dtr.Hours) // Get total hours between two datetime
});
I also recommend to define another class/table like this rather than using anonymous type every time:
public class DTRDetails
{
public DTR DTR { get; set; }
public EmployeeRate EmployeeRate { get; set; }
public string FullName
{
get { return EmployeeRate.FullName; }
}
public int Hours
{
get
{
return (int)(new DateTime(DTR.Date.Year, DTR.Date.Month, DTR.Date.Day, DTR.Timeinhh, DTR.Timeinmm, 0)
.Subtract(new DateTime(DTR.Date.Year, DTR.Date.Month, DTR.Date.Day, DTR.Timeouthh, DTR.Timeoutmm, 0))).TotalHours;
}
}
public int Hours2
{
get
{
// return something
}
}
public int Hours3
{
get { return Hours - Hours2; }
}
}
I need to make a query to filter records, when get distinct records, get these records information by difference conditions. Also I need these to be dynamic(quantity filter in first select)
Let me show you an example:
I have 2 tables:
tblCustomers:
id customerName
1 John
2 Philip
3 Steve
tblOrders
id customerId ordId payment
1 1 100 True
2 1 101 True
3 1 102 False
4 2 101 True
5 2 102 True
6 2 103 False
7 3 101 True
My condition is:
where (orderId = 101 and orderId = 102)
but get all records of this customer that payment = true I mean my condition is different from what I need to see.
I want to receive all records with payment=True without care of orderId
I must get:
john 100
john 101
Philip 101
Philip 102
Clearing: I need two step - first filter customer who has orderId=101&102, in second step i want to show these selected customers' orderId which payment is true. so for example in first step i get john(who has order id =101&102) then show john 100 - john 101 (which payment istrue). consider tblorder.id=1 isn't in first query but I must show in final result.
#Raphael direct me to better expression:I want to see all payment true order for the customers that have orders (101 & 102). but orderids may be more than 2 (thanks #Raphael).
2nd problem is: it must be dynamic. Sometimes I have more than 10 orderId that must be checked - sometimes less. I mean my query must be flexible.
In SQL Server select command, I can prepare a string variable and use but in linq I can't do it.
From what I understood from your post and the comments, you need all customers, where the orderId is 101 or 102 and the payment is true.
You need the where clause with the orderIds to be dynamic so you can change the Ids to be checked against outside of the query.
List<int> IDList = new List<int>();
IDList.Add(101);
IDList.Add(102);
IDList.Add(110);
//...
var result = from cust in tblCustomers
join order in tblOrders on cust.id equals order.customerId
where IDList.Contains(order.ordId) && order.payment == true
select new {
Name = cust.customerName
OrderId = order.ordId
payment = order.payment
//...
}
With this you can store all orderIds which need to be checked against in a list, which in turn you can edit from your code.
EDIT
I really haven't found a clean solution to your problem, so I took a detour, which isn't very clean but should work. In my example I created 2 classes, Customer & Order and filled it with your data from above. Then I took my first query and attached a groupBy to it and a where-clause comparing the length of the grouping with the length of the list
var result = (from cust in Customers
join order in Orders on cust.Id equals order.customerId
where IDList.Contains(order.orderId) &&
order.payment == true
select new {
Name = cust.Name,
OrderId = order.orderId,
Payment = order.payment
//...
}).GroupBy (r => r.Name)
.Where (r => r.Count() == IDList.Count());
Output:
Name OrderId Payment
Philip 101 True
Philip 102 True
If you want/need it, I can provide you with the whole Linqpad query, so you can see my whole code and what I have done. Speaking of Linqpad: ignore the result.Dump() line. It won't work on visual Studio.
void Main()
{
List<Customer> customers = new List<Customer>
{
new Customer { Id = 1, Name = "John" },
new Customer { Id = 2, Name = "Philip" },
new Customer { Id = 3, Name = "Steve" }
};
List<Order> orders = new List<Order>
{
new Order { Id = 1, CustomerId = 1, OrderId = 100, Payment = true },
new Order { Id = 2, CustomerId = 1, OrderId = 101, Payment = true },
new Order { Id = 3, CustomerId = 1, OrderId = 102, Payment = false },
new Order { Id = 4, CustomerId = 2, OrderId = 101, Payment = true },
new Order { Id = 5, CustomerId = 2, OrderId = 102, Payment = true },
new Order { Id = 6, CustomerId = 2, OrderId = 103, Payment = false },
new Order { Id = 7, CustomerId = 3, OrderId = 101, Payment = true }
};
List<int> orderIds = new List<int> { 101, 102 };
var customersWithRelevantOrders =
from ord in orders
group ord by ord.CustomerId into customerOrders
where orderIds.All (
i => customerOrders.Select (co => co.OrderId).Contains(i))
select customerOrders.Key;
var paymentTrueOrdersForTheseCustomers =
from ord in orders
join cust in customers on ord.CustomerId equals cust.Id
where ord.Payment
where customersWithRelevantOrders.Contains(cust.Id)
select new
{
Name = cust.Name,
OrderId = ord.OrderId
};
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
public bool Payment { get; set; }
}
I have a structure like this:
No ID Name Status
1 1 A 1
2 1 B 1
3 1 c 1
4 1 D 1
5 2 E 3
6 2 F 3
7 2 G 3
I want to run a linq when I get a list results get maximum row where for each status and row details as well.Like:
No ID Name Status
4 1 D 1
7 2 G 3
Means latest entry for the status.
Is there a way around, as I have tried all Max, Orderby descending but I get single result but I need a List as a result.
You have to extract the groups of a same id (GroupBy), and then export the max No for each group, with a SelectMany :
public void Exec()
{
var items = new List<Item>{
new Item{ No = 1, Id = 1, Name = "A", Status = 1} ,
new Item{ No = 2, Id = 1, Name = "B", Status = 1} ,
new Item{ No = 3, Id = 1, Name = "C", Status = 1} ,
new Item{ No = 4, Id = 1, Name = "D", Status = 1} ,
new Item{ No = 5, Id = 2, Name = "E", Status = 1} ,
new Item{ No = 6, Id = 2, Name = "F", Status = 1} ,
new Item{ No = 7, Id = 2, Name = "G", Status = 1} ,
};
var result = items
.GroupBy(groupedItems => groupedItems.Id)
.SelectMany(i => items
.Where(innerItem => innerItem.Id == i.Key && innerItem.No == i.Max(ii => ii.No))
.Select(innerItem => innerItem)
);
foreach (var item in result)
Console.WriteLine("Max item of Id {0} : No = {1}, Name = {2}, Status = {3}", item.Id, item.No, item.Name, item.Status);
}
private class Item
{
public Int32 No { get; set; }
public Int32 Id { get; set; }
public String Name { get; set; }
public Int32 Status { get; set; }
}
output :
Max item of Id 1 : No = 4, Name = D, Status = 1
Max item of Id 2 : No = 7, Name = G, Status = 1
Alternative:
items.GroupBy(groupedItems => groupedItems.Id)
.Select(g => g.OrderByDescending(x => x.No).First())
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;
}