Group List of Dictionaries by a Dictionary-key (C#) - c#

I have a List of Dictionaries of type <String, String>. Dictionaries have the keys Revenue and Month.
A typical entry could be: Revenue = "10.080", Month = "1/2011"
I would like to get the revenue totals for each month, so I tried:
List<decimal> monthsTotals = data.Select(d => Convert.ToDecimal(d["Revenue"]))
.GroupBy(d => d["Month"]).ToList<decimal>();
This does not work. The expression d["Month"]) is underlined.
Cannot apply indexing with [] to an expression of type 'decimal'.

The result of your Select is just the revenue. You're losing all the rest of that information. I suspect you want:
Dictionary<string, decimal> revenueByMonth =
data.GroupBy(d => d["Month"], d => decimal.Parse(d["Revenue"]))
.ToDictionary(group => group.Key, group => group.Sum());
The first step creates an IGrouping<string, decimal> - i.e. for each month, a sequence of revenue values.
The second step converts this into a Dictionary<string, decimal> by taking the group key (the month) as the dictionary key, and the sum of the group values as the dictionary value.

List<decimal> monthsTotals = data
.GroupBy(d => d["Month"])
.Select(d => d.Sum( r => Convert.ToDecimal(r["Revenue"])))
.ToList<decimal>();

Turn those dictionaries into something useful.
public class RevenueData
{
public decimal Revenue {get;set;}
public string Month {get;set;}
}
List<RevenueData> result = data
.Select(d => new RevenueData()
{ Revenue = Convert.ToDecimal(d["Revenue"]), Month = d["Month"] })
.GroupBy(x => x.Month)
.Select(g => new RevenueData()
{ Revenue = g.Sum(x => x.Revenue), Month = g.Key })
.ToList();

Related

How to make 2 dictionary and group it twice

I am calculating totals per month for each country. I have managed to group data by country, but I get error
An item with the same key has already been added.
when trying to put monthly totals into inner dictionary:
var totalPerMonth = data.AsEnumerable()
.Select(x => new
{
Date = Convert.ToDateTime(x.ItemArray[0]).ToString("yyyy-MM"),
Country = x.ItemArray[1],
Revenue = x.ItemArray[2]
})
.GroupBy(x => x.Country)
.ToDictionary(x => x.Key, x => x.ToDictionary(p => p.Date,////this is not unique/// p => Convert.ToDouble(p.Revenue)));
how to group it to make Date key unique?
You can either use ToLookup instead of ToDictionary to allow several values for same date.
Or you can use grouping to get unique dates only (assume you want to calculate totals for each month, so use Sum of revenue for each date group dg ):
var totalPerMonth = data.AsEnumerable()
.Select(x => new {
Date = Convert.ToDateTime(x.ItemArray[0]).ToString("yyyy-MM"),
Country = x.ItemArray[1],
Revenue = Convert.ToDouble(x.ItemArray[2]) // convert here
})
.GroupBy(x => x.Country)
.ToDictionary(
g => g.Key,
g => g.GroupBy(x => x.Date).ToDictionary(dg => dg.Key, dg => dg.Sum(x => x.Revenue))
);

Select single item from each group in multiple groups

I have a list (specifically IEnumerable) of items of a specific class:
internal class MyItem
{
public MyItem(DateTime timestamp, string code)
{
Timestamp= timestamp;
Code = code;
}
public DateTime Timestamp { get; private set; }
public string Code { get; private set; }
}
Within this list, there will be multiple items with the same code. Each will have a timestamp, which may or may not be unique.
I'm attempting to retrieve a dictionary of MyItem's (Dictionary<string, MyItem>) where the key is the code associated with the item.
public Dictionary<string, MyItem> GetLatestCodes(IEnumerable<MyItem> items, DateTime latestAllowableTimestamp)
Given this signature, how would I retrieve the MyItem with a timestamp closest to, but not after latestAllowableTimestamp for each code?
For example, given the following for input:
IEnumerable<MyItem> items = new List<MyItem>{
new MyItem(DateTime.Parse("1/1/2014"), "1"),
new MyItem(DateTime.Parse("1/2/2014"), "2"),
new MyItem(DateTime.Parse("1/3/2014"), "1"),
new MyItem(DateTime.Parse("1/4/2014"), "1"),
new MyItem(DateTime.Parse("1/4/2014"), "2")};
If the latestAllowableTimestamp is 1/3/2014, the result would contain only the following items:
Timestamp | Code
----------------
1/3/2014 | 1
1/2/2014 | 2
I can manage to filter the list down to only those timestamps prior to latestAllowableTimestamp, but I don't know linq well enough to pick the most recent for each code and insert it into a dictionary.
var output = items.Where(t => (t.Timestamp <= latestAllowableTimestamp)).GroupBy(t => t.Code);
At this point, I've ended up with two groups, but don't know how to select a single item across each group.
Here is the actual method you are trying to write. It even returns a dictionary and everything:
static Dictionary<string, MyItem> GetLatestCodes(
IEnumerable<MyItem> items, DateTime latestAllowableTimestamp)
{
return items
.Where(item => item.TimeStamp <= latestAllowableTimestamp)
.GroupBy(item => item.Code)
.Select(group => group
.OrderByDescending(item => item.TimeStamp)
.First())
.ToDictionary(item => item.Code);
}
See Enumerable.ToDictionary
This is the your part you should have posted in your question (as LB pointed out)
var list = new List<MyItem>()
{
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,1)},
new MyItem(){ code = "2" , timestamp = new DateTime(2014,1,2)},
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,3)},
new MyItem(){ code = "1" , timestamp = new DateTime(2014,1,4)},
new MyItem(){ code = "2" , timestamp = new DateTime(2014,1,4)}
};
DateTime latestAllowableTimestamp = new DateTime(2014, 1, 3);
This is my answer
var result = list.GroupBy(x => x.code)
.Select(x => x.OrderByDescending(y => y.timestamp)
.FirstOrDefault(z => z.timestamp <= latestAllowableTimestamp))
.ToList();
To create your Dictionary, could construct your query like so:
var newDict = items.Where(a => a.Timestamp <= latestAllowableTimestamp)
.GroupBy(b => b.Timestamp)
.ToDictionary(c => c.First().Timestamp, c => c.First());
This should create a Dictionary from your data, with no duplicate days. Note that without the GroupBy query, you'll raise an exception, because ToDictionary doesn't filter out keys it's already seen.
And then if you wanted to get only one MyItem for any given code number, you could use this query:
newDict.Select(a => a.Value)
.OrderByDescending(b => b.Timestamp)
.GroupBy(c => c.Code)
.Select(d => d.First());
The FirstOrDefault query will return only one element from each group. This will give you the MyItem closest to the latest date for any given code.

How can I get the value with most occurrences in a collection?

I have a list of int? that can have 3 different values: null, 1, and 2.
I would like to know which of them occurs the most in my list. To group them by value I tried to use:
MyCollection.ToLookup(r => r)
How can I get the value with most occurrence?
You don't need a Lookup, a simple GroupBy would do:
var mostCommon = MyCollection
.GroupBy(r => r)
.Select(grp => new { Value = grp.Key, Count = grp.Count() })
.OrderByDescending(x => x.Count)
.First()
Console.WriteLine(
"Value {0} is most common with {1} occurrences",
mostCommon.Value, mostCommon.Count);

Grouping a list of list using linq

I have these tables
public class TaskDetails
{
public string EmployeeName {get; set;}
public decimal EmployeeHours {get; set;}
}
public class Tasks
{
public string TaskName {get; set;}
public List<TaskDetails> TaskList {get; set;}
}
I have a function that returns a List<Tasks>. What I would need is to create a new List that groups the EmployeeNames and SUM the EmployeeHours irrespective of the TaskName. That is, I need to fetch TotalHours of each Employees. How to get that?
P.S: And to what have I done so far. I have stared at the code for a long time. Tried Rubber Duck Problem solving to no avail. I can do get the results using a foreach and placing it to a Dictionary<string, decimal>. That logic will be to check if key does not exist, add a new key and assign the value and if the key exists add the decimal value to the original value. But I feel its too much here. I feel there is a ForEach - GroupBy - Sum combination which I am missing.
Any pointers on how to do it will be very helpful for me.
var results = tasks.SelectMany(x => x.Tasks)
.GroupBy(x => x.EmployeeName)
.ToDictionary(g => g.Key, g => g.Sum(x => x.EmployeeHours));
Gives you Dictionary<string, decimal>.
To get a list just replace ToDictionary with Select/ToList chain:
var results = tasks.SelectMany(x => x.Tasks)
.GroupBy(x => x.EmployeeName)
.Select(g => new {
EmployeeName = g.Key,
Sum = g.Sum(x => x.EmployeeHours)
}).ToList();
a SelectMany would help, I think.
It will "flatten" the Lists of TaskDetail of all your Task elements into a single IEnumerable<TaskDetail>
var result = listOfTasks.SelectMany(x => x.Tasks)
.GroupBy(m => m.EmployeeName)
.Select(m => new {
empName = m.Key,
hours = m.Sum(x => x.EmployeeHours)
});
var emplWithHours = allTasks
.SelectMany(t => t.Tasks)
.GroupBy(empl => empl.EmployeeName)
.Select(empl => new
{
EmployeeName = empl.Key,
TotalHours = empl.Sum(hour => hour.EmployeeHours)
}).ToDictionary(i => i.EmployeeName, i => i.TotalHours);
Also, when both your class name and field name is Tasks, it gives a compile-time error:
Error 1 'Tasks': member names cannot be the same as their enclosing type
I would have named your class Task since it represents a single task.
I would do it this way:
var query =
(
from t in tasks
from td in t.TaskList
group td.EmployeeHours by td.EmployeeName into ghs
select new
{
EmployeeName = ghs.Key,
EmployeeHours = ghs.Sum(),
}
).ToDictionary(x => x.EmployeeName, x => x.EmployeeHours);
I slightly more succinct query would be this:
var query =
(
from t in tasks
from td in t.TaskList
group td.EmployeeHours by td.EmployeeName
).ToDictionary(x => x.Key, x => x.Sum());
There are pros and cons to each. I think the first is more explicit, but the second a little neater.

Find MAX/MIN list item using LINQ?

I have a list Having multiple Items and 3 props ID,DATE,COMMENT.ID field is Auto incremented in DATABASE.
Let say list Contains
2,16AUG,CommentMODIFIED
1,15AUG,CommentFIRST
3,18AUG,CommentLASTModified
I want to get a single ITEM.Item Having Minimum DATE and having Latest Comment. In this case
1,15AUG,CommentLASTModified
Any easy way to do it using LINQ.
orderedItems = items.OrderBy(x => x.Date);
var result = items.First();
result.Comment = items.Last().Comment;
To get a single item out of the list, you can order the items then take the first one, like this:
var result = items
.OrderByDescending(x => x.Date)
.First();
But First will throw an exception if the items collection is empty. This is a bit safer:
var result = items
.OrderByDescending(x => x.Date)
.FirstOrDefault();
To get the min / max of different columns you can do this:
var result =
new Item {
Id = 1,
Date = items.Min(x => x.Date),
Comment = items.Max(x => x.Comment)
};
But this will require two trips to the database. This might be a bit more efficient:
var result =
(from x in items
group x by 1 into g
select new Item {
Id = 1,
Date = g.Min(g => g.Date),
Comment = g.Max(g => g.Comment)
})
.First();
Or in fluent syntax:
var result = items
.GroupBy(x => 1)
.Select(g => new Item {
Id = 1,
Date = g.Min(g => g.Date),
Comment = g.Max(g => g.Comment)
})
.First();

Categories