I have a List of Customers
List<Customers> cust = new List<Customers>();
cust.Add(new Customers(){ID=1, Name="Sam", PurchaseDate=DateTime.Parse("01/12/2008")});
cust.Add(new Customers(){ID=2, Name="Lolly" PurchaseDate=DateTime.Parse("03/18/2008")});
I want to show 2 seperate results like:
Purchases by Customer in Yr // Grouping by yr and display id,name
Purchases by Customer in Yr - Month // Grouping by yr then month and display id,name
Also What if i want to order the yr?
Update:
Just one more addition. If I have a field called "Status" in the Customer class with either of these values 'Y', 'N', 'C' standing for yes, no and cancel how will i create a query to give ratio in %
Y - 20%
N - 30%
C - 50%
Grouping by year:
var groupByYear = customers.GroupBy(customer => customer.PurchaseDate.Year);
foreach (var group in groupByYear)
{
Console.WriteLine("Year: {0}", group.Key);
foreach (var customer in group)
{
Console.WriteLine("{0}: {1}", customer.ID, customer.Name);
}
}
Grouping by year and month:
var groupByYearMonth = customers.GroupBy(customer =>
new DateTime(customer.PurchaseDate.Year, customer.PurchaseDate.Month, 1));
foreach (var group in groupByYear)
{
Console.WriteLine("Year/month: {0}/{1}", group.Key.Year, group.Key.Month);
foreach (var customer in group)
{
Console.WriteLine("{0}: {1}", customer.ID, customer.Name);
}
}
Ordering:
var ordered = customers.OrderBy(customer => customer.PurchaseDate.Year);
All of these use "dot notation" instead of query expressions because they're so simple - but you could use a query expression if you wanted to.
EDIT: For the status part, just use David B's answer.
int total = customer.Count()
var counts = customers.GroupBy( c => c.Status )
.Select( g => new
{
Status = g.Key,
TheRatio = (g.Count() * 100) / total;
})
Related
I need to get topic name and number of times (count) a given topic occurs.
Example:
Name: John
Topic: X, Y, Z => these are List<string>
Name: Bob
Topic: Y
Name: Suzy
Topic: Y, Z
Should generate output:
X: 1
Y: 3
Z: 2
I've tried this but it doesn't return correct result:
var result = from r in items
orderby r.Topic
group r by r.Topic
into grp
select new { key = grp.Key, cnt = grp.Count() };
You can flatten the contained collection with another from:
var result = from r in lineItems
from t in r.Topic // List<string>
group t by t into grp
orderby grp.Key
select new { key = grp.Key, cnt = grp.Count() };
I like to do this using SelectMany( to flatten the inner collection)
Just another way to do this
var result = items.SelectMany((w) => w.Topic)
.GroupBy((q) => q)
.Select((p) => new { Grp = p.Key, Cnt = p.Count() })
.OrderBy((g) => g.Cnt);
This is my records:
Id EmpName Stats
1 Abc 1000
1 Abc 3000
1 Abc 2000
2 Pqr 4000
2 Pqr 5000
2 Pqr 6000
2 Pqr 7000
I am trying to group by on Id fields and after doing group by i want output like this:
Expected output:
Id EmpName Stats
1 Abc 3000
2 Pqr 3000
For 1st output record calculation is like this:
3000 - 1000=2000 (i.e subtract highest - lowest from 1st and 2nd records)
3000 - 2000=1000 (i.e subtract highest - lowest from 2nd and 3rd records)
Total=2000 + 1000 =3000
For 2nd output record calculation is like this:
5000 - 4000=1000 (i.e subtract highest - lowest from first two records)
6000 - 5000=1000
7000 - 6000=1000
total=1000 + 1000=2000
This is 1 sample fiddle i have created:Fiddle
So far i have manage to group records by id but now how do i perform this calculation on group records??
You can use the Aggregate method overload that allows you to maintain custom accumulator state.
In your case, we'll be maintaining the following:
decimal Sum; // Current result
decimal Prev; // Previous element Stats (zero for the first element)
int Index; // The index of the current element
The Index is basically needed just to avoid accumulating the first element Stats into the result.
And here is the query:
var result = list.GroupBy(t => t.Id)
.Select(g => new
{
ID = g.Key,
Name = g.First().EmpName,
Stats = g.Aggregate(
new { Sum = 0m, Prev = 0m, Index = 0 },
(a, e) => new
{
Sum = (a.Index < 2 ? 0 : a.Sum) + Math.Abs(e.Stats - a.Prev),
Prev = e.Stats,
Index = a.Index + 1
}, a => a.Sum)
}).ToList();
Edit: As requested in the comments, here is the foreach equivalent of the above Aggregate usage:
static decimal GetStats(IEnumerable<Employee> g)
{
decimal sum = 0;
decimal prev = 0;
int index = 0;
foreach (var e in g)
{
sum = (index < 2 ? 0 : sum) + Math.Abs(e.Stats - prev);
prev = e.Stats;
index++;
}
return sum;
}
Firstly, like mentioned in my comment, this can be done using a single linq query but would have many complications, one being unreadable code.
Using a simple foreach on the IGrouping List,
Updated (handle dynamic group length):
var list = CreateData();
var groupList = list.GroupBy(t => t.Id);
var finalList = new List<Employee>();
//Iterate on the groups
foreach(var grp in groupList){
var part1 = grp.Count()/2;
var part2 = (int)Math.Ceiling((double)grp.Count()/2);
var firstSet = grp.Select(i=>i.Stats).Take(part2);
var secondSet = grp.Select(i=>i.Stats).Skip(part1).Take(part2);
var total = (firstSet.Max() - firstSet.Min()) + (secondSet.Max() - secondSet.Min());
finalList.Add(new Employee{
Id = grp.Key,
EmpName = grp.FirstOrDefault().EmpName,
Stats = total
});
}
*Note -
You can optimize the logic used in getting the data for calculation.
More complicated logic is to divide the group into equal parts in case it is not fixed.
Updated Fiddle
The LinQ way,
var list = CreateData();
var groupList = list.GroupBy(t => t.Id);
var testLinq = (from l in list
group l by l.Id into grp
let part1 = grp.Count()/2
let part2 = (int)Math.Ceiling((double)grp.Count()/2)
let firstSet = grp.Select(i=>i.Stats).Take(part2)
let secondSet = grp.Select(i=>i.Stats).Skip(part1).Take(part2)
select new Employee{
Id = grp.Key,
EmpName = grp.FirstOrDefault().EmpName,
Stats = (firstSet.Max() - firstSet.Min()) + (secondSet.Max() - secondSet.Min())
}).ToList();
I have made a group by statement on a datatable like this:
var finalResult = (from r in result.AsEnumerable()
group r by new
{
r.Agent,
r.Reason
} into grp
select new
{
Agent = grp.Key.Agent,
Reason = grp.Key.Reason,
Count = grp.Count()
}).ToList();
The finalResult will be like this:
agent1 reason1 4
agent1 reason2 7
agent2 reason1 8
agent2 reason2 3
..
...
...
agentn reason1 3
agentn reason2 11
I want to loop over agent name in order to get the reasons and the counts for each reason for each agent. In other words: i need to build this :
can you tell me please how to loop over agent name from the finalResult variable?
You need one more GroupBy and you are done:
var solution =
finalResult
.GroupBy(x => x.Agent);
foreach (var group in solution)
{
// group.Key is the agent
// All items in group are a sequence of reasons and counts for this agent
foreach (var item in group)
{
// Item has <Agent, Reason, Count> and belongs to the agent from group.Key
}
}
Outer loop goes over all the agents (so Agent1, Agent2, etc.) while inner loop will go through all reasons for the current agent.
You might want to try GroupBy in LINQ :
You can read more about it here
Perhaps:
var agentGroups = finalResult
.GroupBy(x => x.Agent)
.Select(ag => new
{
Agent = ag.Key,
ReasonCounts = ag.GroupBy(x => x.Reason)
.Select(g => new
{
Agent = ag.Key,
Reason = g.Key,
Count = g.Sum(x => x.Count)
}).ToList(),
Total_Count = ag.Sum(x => x.Count)
});
foreach (var agentGroup in agentGroups)
{
string agent = agentGroup.Agent;
int totalCount = agentGroup.Total_Count;
foreach (var reasonCount in agentGroup.ReasonCounts)
{
string reason = reasonCount.Reason;
int count = reasonCount.Count;
}
}
I am matching Cases to Controls, basically records in the Case list, need to have the number of matches that is specified in the string m_ctrlno.
So far I have two lists, the where clause is correct, however I'm unsure how to use SelectMany to get the 3 Controls that match 1 Case. I decided to use the .Take() function however it doesn't seem to be working. I'm not getting the same case with 3 different controls when i cycle on the var query.
Here is the code:
List<CaseSelection> CurrentCaseList = new List<CaseSelection>();
foreach (CaseSelection CurrentCase in m_casesarraylist)
CurrentCaseList.Add(CurrentCase);
List<ControlSelection> CurrentControlList = new List<ControlSelection>();
foreach (ControlSelection CurrentControlRec in ControlList)
CurrentControlList.Add(CurrentControlRec);
var query = CurrentCaseList.SelectMany(
c => CurrentControlList.Where(o => o.pracid == c.pracid && o.sex == c.sex &&
CaseSelectionList.AgeIsInRange(c.yob, o.yob, m_years)),
(c, o) =>
new { o, c }).Take(m_ctrlno);
In your code You define 2 list CurrentCaseList,CurrentControlList but not define CaseSelectionList.
To get the 3 controls that matches one case, for this see below code:
the SelectMany method to select all orders where TotalDue is less than 500.00.
Here is the code:
decimal totalDue = 500.00M;
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
ObjectSet<Contact> contacts = context.Contacts;
ObjectSet<SalesOrderHeader> orders = context.SalesOrderHeaders;
var query =
contacts.SelectMany(
contact => orders.Where(order =>
(contact.ContactID == order.Contact.ContactID)
&& order.TotalDue < totalDue)
.Select(order => new
{
ContactID = contact.ContactID,
LastName = contact.LastName,
FirstName = contact.FirstName,
OrderID = order.SalesOrderID,
Total = order.TotalDue
}));
foreach (var smallOrder in query)
{
Console.WriteLine("Contact ID: {0} Name: {1}, {2} Order ID: {3} Total Due: ${4} ",
smallOrder.ContactID, smallOrder.LastName, smallOrder.FirstName,
smallOrder.OrderID, smallOrder.Total);
}
}
I would like a third column "items" with the values that are grouped.
var dic = new Dictionary<string, int>();
dic.Add("a", 1);
dic.Add("b", 1);
dic.Add("c", 2);
dic.Add("d", 3);
var dCounts =
(from i in dic
group i by i.Value into g
select new { g.Key, count = g.Count()});
var a = dCounts.Where(c => c.count>1 );
dCounts.Dump();
a.Dump();
This code results in:
Key Count
1 2
2 1
3 1
I would like these results:
Key Count Items
1 2 a, b
2 1 c
3 1 d
var dCounts =
(from i in dic
group i by i.Value into g
select new { g.Key, count = g.Count(), Items = string.Join(",", g.Select(kvp => kvp.Key)) });
Use string.Join(",", {array}), passing in your array of keys.
You can use:
var dCounts =
from i in dic
group i by i.Value into g
select new { g.Key, Count = g.Count(), Values = g };
The result created by grouping (value g) has a property Key that gives you the key, but it also implements IEnumerable<T> that allows you to access individual values in the group. If you return just g then you can iterate over all values using foreach or process them using LINQ.
Here is a simple dump function to demonstrate this:
foreach(var el in dCounts) {
Console.Write(" - {0}, count: {1}, values:", el.Key, el.Count);
foreach(var item in el.Values) Console.Write("{0}, ", item);
|
from i in dic
group i.Key by i.Value into g
select new
{
g.Key,
count = g.Count(),
items = string.Join(",", g.ToArray())
});